From d62d92f3976d8861aab0a50c21271fb7ac54d0bd Mon Sep 17 00:00:00 2001 From: Ralalu <101653470+Ralalu@users.noreply.github.com> Date: Sat, 6 Sep 2025 18:58:24 +0200 Subject: [PATCH 1/3] Add pin/unpin feature for side and bottom panels --- package-lock.json | 8748 +++++++++++++++++++++++++++++++++++++ package.json | 2 +- src/I18nLabel.ts | 2 + src/model/IJsonModel.ts | 510 +-- src/model/TabNode.ts | 8 +- src/view/BorderTab.tsx | 33 +- src/view/BorderTabSet.tsx | 43 +- src/view/Icons.tsx | 18 + src/view/Layout.tsx | 8 +- src/view/Tab.tsx | 5 + 10 files changed, 9114 insertions(+), 263 deletions(-) create mode 100644 package-lock.json diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..36435b3c --- /dev/null +++ b/package-lock.json @@ -0,0 +1,8748 @@ +{ + "name": "flexlayout-react", + "version": "0.8.17", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "flexlayout-react", + "version": "0.8.17", + "license": "ISC", + "devDependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.0", + "@eslint/js": "^9.24.0", + "@mui/material": "^7.0.2", + "@mui/x-data-grid": "^7.28.3", + "@playwright/test": "^1.51.1", + "@types/node": "^22.14.1", + "@types/prismjs": "^1.26.5", + "@types/react": "^19.1.2", + "@types/react-dom": "^19.1.2", + "@vitejs/plugin-react": "^4.4.0", + "ag-grid-community": "^33.2.3", + "ag-grid-react": "^33.2.3", + "chart.js": "^4.4.9", + "eslint": "^9.24.0", + "eslint-plugin-react": "^7.37.5", + "globals": "^16.0.0", + "ol": "^10.5.0", + "prettier": "^3.5.3", + "prismjs": "^1.30.0", + "react": "^19.1.0", + "react-chartjs-2": "^5.3.0", + "react-dom": "^19.1.0", + "react-scripts": "^0.0.0", + "rimraf": "^6.0.1", + "sass": "^1.86.3", + "styled-components": "^6.1.17", + "typedoc": "^0.28.2", + "typescript": "^5.8.3", + "typescript-eslint": "^8.30.1", + "vite": "^6.3.0", + "vitest": "^3.1.1" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.4.tgz", + "integrity": "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz", + "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.4", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.4", + "@babel/types": "^7.28.4", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/generator": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", + "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.3", + "@babel/types": "^7.28.2", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", + "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.4" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz", + "integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.4", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz", + "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emotion/babel-plugin": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", + "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/cache": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", + "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz", + "integrity": "sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@emotion/react": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", + "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@emotion/styled": { + "version": "11.14.1", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz", + "integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/is-prop-valid": "^1.3.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", + "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz", + "integrity": "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.9.tgz", + "integrity": "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.9.tgz", + "integrity": "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.9.tgz", + "integrity": "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.9.tgz", + "integrity": "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.9.tgz", + "integrity": "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.9.tgz", + "integrity": "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.9.tgz", + "integrity": "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.9.tgz", + "integrity": "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.9.tgz", + "integrity": "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.9.tgz", + "integrity": "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.9.tgz", + "integrity": "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.9.tgz", + "integrity": "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.9.tgz", + "integrity": "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.9.tgz", + "integrity": "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.9.tgz", + "integrity": "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.9.tgz", + "integrity": "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.9.tgz", + "integrity": "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.9.tgz", + "integrity": "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.9.tgz", + "integrity": "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.9.tgz", + "integrity": "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.9.tgz", + "integrity": "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.9.tgz", + "integrity": "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.9.tgz", + "integrity": "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.9.tgz", + "integrity": "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz", + "integrity": "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.8.0.tgz", + "integrity": "sha512-MJQFqrZgcW0UNYLGOuQpey/oTN59vyWwplvCGZztn1cKz9agZPPYpJB7h2OMmuu7VLqkvEjN8feFZJmxNF9D+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz", + "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", + "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.35.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.35.0.tgz", + "integrity": "sha512-30iXE9whjlILfWobBkNerJo+TXYsgVM5ERQwMcMKCHckHflCmf7wXDAHlARoWnh0s1U72WqlbeyE7iAcCzuCPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", + "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.15.2", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@gerrit0/mini-shiki": { + "version": "3.12.2", + "resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-3.12.2.tgz", + "integrity": "sha512-HKZPmO8OSSAAo20H2B3xgJdxZaLTwtlMwxg0967scnrDlPwe6j5+ULGHyIqwgTbFCn9yv/ff8CmfWZLE9YKBzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/engine-oniguruma": "^3.12.2", + "@shikijs/langs": "^3.12.2", + "@shikijs/themes": "^3.12.2", + "@shikijs/types": "^3.12.2", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@isaacs/balanced-match": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", + "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", + "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@isaacs/balanced-match": "^4.0.1" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.0.tgz", + "integrity": "sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.30", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz", + "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@kurkle/color": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz", + "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@mui/core-downloads-tracker": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.3.2.tgz", + "integrity": "sha512-AOyfHjyDKVPGJJFtxOlept3EYEdLoar/RvssBTWVAvDJGIE676dLi2oT/Kx+FoVXFoA/JdV7DEMq/BVWV3KHRw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + } + }, + "node_modules/@mui/material": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.2.tgz", + "integrity": "sha512-qXvbnawQhqUVfH1LMgMaiytP+ZpGoYhnGl7yYq2x57GYzcFL/iPzSZ3L30tlbwEjSVKNYcbiKO8tANR1tadjUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.3", + "@mui/core-downloads-tracker": "^7.3.2", + "@mui/system": "^7.3.2", + "@mui/types": "^7.4.6", + "@mui/utils": "^7.3.2", + "@popperjs/core": "^2.11.8", + "@types/react-transition-group": "^4.4.12", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1", + "react-is": "^19.1.1", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@mui/material-pigment-css": "^7.3.2", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@mui/material-pigment-css": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/private-theming": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.3.2.tgz", + "integrity": "sha512-ha7mFoOyZGJr75xeiO9lugS3joRROjc8tG1u4P50dH0KR7bwhHznVMcYg7MouochUy0OxooJm/OOSpJ7gKcMvg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.3", + "@mui/utils": "^7.3.2", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/styled-engine": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.3.2.tgz", + "integrity": "sha512-PkJzW+mTaek4e0nPYZ6qLnW5RGa0KN+eRTf5FA2nc7cFZTeM+qebmGibaTLrgQBy3UpcpemaqfzToBNkzuxqew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.3", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/sheet": "^1.4.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/system": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-7.3.2.tgz", + "integrity": "sha512-9d8JEvZW+H6cVkaZ+FK56R53vkJe3HsTpcjMUtH8v1xK6Y1TjzHdZ7Jck02mGXJsE6MQGWVs3ogRHTQmS9Q/rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.3", + "@mui/private-theming": "^7.3.2", + "@mui/styled-engine": "^7.3.2", + "@mui/types": "^7.4.6", + "@mui/utils": "^7.3.2", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/types": { + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.6.tgz", + "integrity": "sha512-NVBbIw+4CDMMppNamVxyTccNv0WxtDb7motWDlMeSC8Oy95saj1TIZMGynPpFLePt3yOD8TskzumeqORCgRGWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.3" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.3.2.tgz", + "integrity": "sha512-4DMWQGenOdLnM3y/SdFQFwKsCLM+mqxzvoWp9+x2XdEzXapkznauHLiXtSohHs/mc0+5/9UACt1GdugCX2te5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.3", + "@mui/types": "^7.4.6", + "@types/prop-types": "^15.7.15", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^19.1.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/x-data-grid": { + "version": "7.29.9", + "resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-7.29.9.tgz", + "integrity": "sha512-RfK7Fnuu4eyv/4eD3MEB1xxZsx0xRBsofb1kifghIjyQV1EKAeRcwvczyrzQggj7ZRT5AqkwCzhLsZDvE5O0nQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.25.7", + "@mui/utils": "^5.16.6 || ^6.0.0 || ^7.0.0", + "@mui/x-internals": "7.29.0", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "reselect": "^5.1.1", + "use-sync-external-store": "^1.0.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.9.0", + "@emotion/styled": "^11.8.1", + "@mui/material": "^5.15.14 || ^6.0.0 || ^7.0.0", + "@mui/system": "^5.15.14 || ^6.0.0 || ^7.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/x-internals": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-7.29.0.tgz", + "integrity": "sha512-+Gk6VTZIFD70XreWvdXBwKd8GZ2FlSCuecQFzm6znwqXg1ZsndavrhG9tkxpxo2fM1Zf7Tk8+HcOO0hCbhTQFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.25.7", + "@mui/utils": "^5.16.6 || ^6.0.0 || ^7.0.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@parcel/watcher": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", + "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^1.0.3", + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "node-addon-api": "^7.0.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.1", + "@parcel/watcher-darwin-arm64": "2.5.1", + "@parcel/watcher-darwin-x64": "2.5.1", + "@parcel/watcher-freebsd-x64": "2.5.1", + "@parcel/watcher-linux-arm-glibc": "2.5.1", + "@parcel/watcher-linux-arm-musl": "2.5.1", + "@parcel/watcher-linux-arm64-glibc": "2.5.1", + "@parcel/watcher-linux-arm64-musl": "2.5.1", + "@parcel/watcher-linux-x64-glibc": "2.5.1", + "@parcel/watcher-linux-x64-musl": "2.5.1", + "@parcel/watcher-win32-arm64": "2.5.1", + "@parcel/watcher-win32-ia32": "2.5.1", + "@parcel/watcher-win32-x64": "2.5.1" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", + "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", + "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", + "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", + "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", + "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", + "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", + "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", + "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", + "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", + "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", + "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", + "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", + "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@petamoriken/float16": { + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@petamoriken/float16/-/float16-3.9.2.tgz", + "integrity": "sha512-VgffxawQde93xKxT3qap3OH+meZf7VaSB5Sqd4Rqc+FP5alWbpOyan/7tRbOAvynjpG3GpdtAuGU/NdhQpmrog==", + "dev": true, + "license": "MIT" + }, + "node_modules/@playwright/test": { + "version": "1.55.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.55.0.tgz", + "integrity": "sha512-04IXzPwHrW69XusN/SIdDdKZBzMfOT9UNT/YiJit/xpy2VuAoB8NHc8Aplb96zsWDddLnbkPL3TsmrS04ZU2xQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.55.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.50.0.tgz", + "integrity": "sha512-lVgpeQyy4fWN5QYebtW4buT/4kn4p4IJ+kDNB4uYNT5b8c8DLJDg6titg20NIg7E8RWwdWZORW6vUFfrLyG3KQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.50.0.tgz", + "integrity": "sha512-2O73dR4Dc9bp+wSYhviP6sDziurB5/HCym7xILKifWdE9UsOe2FtNcM+I4xZjKrfLJnq5UR8k9riB87gauiQtw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.50.0.tgz", + "integrity": "sha512-vwSXQN8T4sKf1RHr1F0s98Pf8UPz7pS6P3LG9NSmuw0TVh7EmaE+5Ny7hJOZ0M2yuTctEsHHRTMi2wuHkdS6Hg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.50.0.tgz", + "integrity": "sha512-cQp/WG8HE7BCGyFVuzUg0FNmupxC+EPZEwWu2FCGGw5WDT1o2/YlENbm5e9SMvfDFR6FRhVCBePLqj0o8MN7Vw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.50.0.tgz", + "integrity": "sha512-UR1uTJFU/p801DvvBbtDD7z9mQL8J80xB0bR7DqW7UGQHRm/OaKzp4is7sQSdbt2pjjSS72eAtRh43hNduTnnQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.50.0.tgz", + "integrity": "sha512-G/DKyS6PK0dD0+VEzH/6n/hWDNPDZSMBmqsElWnCRGrYOb2jC0VSupp7UAHHQ4+QILwkxSMaYIbQ72dktp8pKA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.50.0.tgz", + "integrity": "sha512-u72Mzc6jyJwKjJbZZcIYmd9bumJu7KNmHYdue43vT1rXPm2rITwmPWF0mmPzLm9/vJWxIRbao/jrQmxTO0Sm9w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.50.0.tgz", + "integrity": "sha512-S4UefYdV0tnynDJV1mdkNawp0E5Qm2MtSs330IyHgaccOFrwqsvgigUD29uT+B/70PDY1eQ3t40+xf6wIvXJyg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.50.0.tgz", + "integrity": "sha512-1EhkSvUQXJsIhk4msxP5nNAUWoB4MFDHhtc4gAYvnqoHlaL9V3F37pNHabndawsfy/Tp7BPiy/aSa6XBYbaD1g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.50.0.tgz", + "integrity": "sha512-EtBDIZuDtVg75xIPIK1l5vCXNNCIRM0OBPUG+tbApDuJAy9mKago6QxX+tfMzbCI6tXEhMuZuN1+CU8iDW+0UQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.50.0.tgz", + "integrity": "sha512-BGYSwJdMP0hT5CCmljuSNx7+k+0upweM2M4YGfFBjnFSZMHOLYR0gEEj/dxyYJ6Zc6AiSeaBY8dWOa11GF/ppQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.50.0.tgz", + "integrity": "sha512-I1gSMzkVe1KzAxKAroCJL30hA4DqSi+wGc5gviD0y3IL/VkvcnAqwBf4RHXHyvH66YVHxpKO8ojrgc4SrWAnLg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.50.0.tgz", + "integrity": "sha512-bSbWlY3jZo7molh4tc5dKfeSxkqnf48UsLqYbUhnkdnfgZjgufLS/NTA8PcP/dnvct5CCdNkABJ56CbclMRYCA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.50.0.tgz", + "integrity": "sha512-LSXSGumSURzEQLT2e4sFqFOv3LWZsEF8FK7AAv9zHZNDdMnUPYH3t8ZlaeYYZyTXnsob3htwTKeWtBIkPV27iQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.50.0.tgz", + "integrity": "sha512-CxRKyakfDrsLXiCyucVfVWVoaPA4oFSpPpDwlMcDFQvrv3XY6KEzMtMZrA+e/goC8xxp2WSOxHQubP8fPmmjOQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.50.0.tgz", + "integrity": "sha512-8PrJJA7/VU8ToHVEPu14FzuSAqVKyo5gg/J8xUerMbyNkWkO9j2ExBho/68RnJsMGNJq4zH114iAttgm7BZVkA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.50.0.tgz", + "integrity": "sha512-SkE6YQp+CzpyOrbw7Oc4MgXFvTw2UIBElvAvLCo230pyxOLmYwRPwZ/L5lBe/VW/qT1ZgND9wJfOsdy0XptRvw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.50.0.tgz", + "integrity": "sha512-PZkNLPfvXeIOgJWA804zjSFH7fARBBCpCXxgkGDRjjAhRLOR8o0IGS01ykh5GYfod4c2yiiREuDM8iZ+pVsT+Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.50.0.tgz", + "integrity": "sha512-q7cIIdFvWQoaCbLDUyUc8YfR3Jh2xx3unO8Dn6/TTogKjfwrax9SyfmGGK6cQhKtjePI7jRfd7iRYcxYs93esg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.50.0.tgz", + "integrity": "sha512-XzNOVg/YnDOmFdDKcxxK410PrcbcqZkBmz+0FicpW5jtjKQxcW1BZJEQOF0NJa6JO7CZhett8GEtRN/wYLYJuw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.50.0.tgz", + "integrity": "sha512-xMmiWRR8sp72Zqwjgtf3QbZfF1wdh8X2ABu3EaozvZcyHJeU0r+XAnXdKgs4cCAp6ORoYoCygipYP1mjmbjrsg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "3.12.2", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.12.2.tgz", + "integrity": "sha512-hozwnFHsLvujK4/CPVHNo3Bcg2EsnG8krI/ZQ2FlBlCRpPZW4XAEQmEwqegJsypsTAN9ehu2tEYe30lYKSZW/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.12.2", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, + "node_modules/@shikijs/langs": { + "version": "3.12.2", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.12.2.tgz", + "integrity": "sha512-bVx5PfuZHDSHoBal+KzJZGheFuyH4qwwcwG/n+MsWno5cTlKmaNtTsGzJpHYQ8YPbB5BdEdKU1rga5/6JGY8ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.12.2" + } + }, + "node_modules/@shikijs/themes": { + "version": "3.12.2", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.12.2.tgz", + "integrity": "sha512-fTR3QAgnwYpfGczpIbzPjlRnxyONJOerguQv1iwpyQZ9QXX4qy/XFQqXlf17XTsorxnHoJGbH/LXBvwtqDsF5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.12.2" + } + }, + "node_modules/@shikijs/types": { + "version": "3.12.2", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.12.2.tgz", + "integrity": "sha512-K5UIBzxCyv0YoxN3LMrKB9zuhp1bV+LgewxuVwHdl4Gz5oePoUFrr9EfgJlGlDeXCU1b/yhdnXeuRvAnz8HN8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/chai": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.2.tgz", + "integrity": "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.18.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.1.tgz", + "integrity": "sha512-rzSDyhn4cYznVG+PCzGe1lwuMYJrcBS1fc3JqSa2PvtABwWo+dZ1ij5OVok3tqfpEBCBoaR4d7upFJk73HRJDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/prismjs": { + "version": "1.26.5", + "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.5.tgz", + "integrity": "sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/rbush": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/rbush/-/rbush-4.0.0.tgz", + "integrity": "sha512-+N+2H39P8X+Hy1I5mC6awlTX54k3FhiUmvt7HWzGJZvF+syUAAxP/stwppS8JE84YHqFgRMv6fCy31202CMFxQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.1.12", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.12.tgz", + "integrity": "sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.1.9", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.9.tgz", + "integrity": "sha512-qXRuZaOsAdXKFyOhRBg6Lqqc0yay13vN7KrIg4L7N4aaHN68ma9OK3NE1BoDFgFOTfM7zg+3/8+2n8rLUH3OKQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.0.0" + } + }, + "node_modules/@types/react-transition-group": { + "version": "4.4.12", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", + "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/stylis": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.5.tgz", + "integrity": "sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.42.0.tgz", + "integrity": "sha512-Aq2dPqsQkxHOLfb2OPv43RnIvfj05nw8v/6n3B2NABIPpHnjQnaLo9QGMTvml+tv4korl/Cjfrb/BYhoL8UUTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.42.0", + "@typescript-eslint/type-utils": "8.42.0", + "@typescript-eslint/utils": "8.42.0", + "@typescript-eslint/visitor-keys": "8.42.0", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.42.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.42.0.tgz", + "integrity": "sha512-r1XG74QgShUgXph1BYseJ+KZd17bKQib/yF3SR+demvytiRXrwd12Blnz5eYGm8tXaeRdd4x88MlfwldHoudGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.42.0", + "@typescript-eslint/types": "8.42.0", + "@typescript-eslint/typescript-estree": "8.42.0", + "@typescript-eslint/visitor-keys": "8.42.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.42.0.tgz", + "integrity": "sha512-vfVpLHAhbPjilrabtOSNcUDmBboQNrJUiNAGoImkZKnMjs2TIcWG33s4Ds0wY3/50aZmTMqJa6PiwkwezaAklg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.42.0", + "@typescript-eslint/types": "^8.42.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.42.0.tgz", + "integrity": "sha512-51+x9o78NBAVgQzOPd17DkNTnIzJ8T/O2dmMBLoK9qbY0Gm52XJcdJcCl18ExBMiHo6jPMErUQWUv5RLE51zJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.42.0", + "@typescript-eslint/visitor-keys": "8.42.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.42.0.tgz", + "integrity": "sha512-kHeFUOdwAJfUmYKjR3CLgZSglGHjbNTi1H8sTYRYV2xX6eNz4RyJ2LIgsDLKf8Yi0/GL1WZAC/DgZBeBft8QAQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.42.0.tgz", + "integrity": "sha512-9KChw92sbPTYVFw3JLRH1ockhyR3zqqn9lQXol3/YbI6jVxzWoGcT3AsAW0mu1MY0gYtsXnUGV/AKpkAj5tVlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.42.0", + "@typescript-eslint/typescript-estree": "8.42.0", + "@typescript-eslint/utils": "8.42.0", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.42.0.tgz", + "integrity": "sha512-LdtAWMiFmbRLNP7JNeY0SqEtJvGMYSzfiWBSmx+VSZ1CH+1zyl8Mmw1TT39OrtsRvIYShjJWzTDMPWZJCpwBlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.42.0.tgz", + "integrity": "sha512-ku/uYtT4QXY8sl9EDJETD27o3Ewdi72hcXg1ah/kkUgBvAYHLwj2ofswFFNXS+FL5G+AGkxBtvGt8pFBHKlHsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.42.0", + "@typescript-eslint/tsconfig-utils": "8.42.0", + "@typescript-eslint/types": "8.42.0", + "@typescript-eslint/visitor-keys": "8.42.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.42.0.tgz", + "integrity": "sha512-JnIzu7H3RH5BrKC4NoZqRfmjqCIS1u3hGZltDYJgkVdqAezl4L9d1ZLw+36huCujtSBSAirGINF/S4UxOcR+/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.42.0", + "@typescript-eslint/types": "8.42.0", + "@typescript-eslint/typescript-estree": "8.42.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.42.0.tgz", + "integrity": "sha512-3WbiuzoEowaEn8RSnhJBrxSwX8ULYE9CXaPepS2C2W3NSA5NNIvBaslpBSBElPq0UGr0xVJlXFWOAKIkyylydQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.42.0", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/@vitest/expect": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", + "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", + "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.2.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", + "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", + "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.2.4", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", + "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", + "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", + "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "deprecated": "Use your platform's native atob() and btoa() methods instead", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "peer": true + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-globals": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", + "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "acorn": "^7.1.1", + "acorn-walk": "^7.1.1" + } + }, + "node_modules/acorn-globals/node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ag-charts-types": { + "version": "11.3.2", + "resolved": "https://registry.npmjs.org/ag-charts-types/-/ag-charts-types-11.3.2.tgz", + "integrity": "sha512-trPGqgGYiTeLgtf9nLuztDYOPOFOLbqHn1g2D99phf7QowcwdX0TPx0wfWG8Hm90LjB8IH+G2s3AZe2vrdAtMQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/ag-grid-community": { + "version": "33.3.2", + "resolved": "https://registry.npmjs.org/ag-grid-community/-/ag-grid-community-33.3.2.tgz", + "integrity": "sha512-9bx0e/+ykOyLvUxHqmdy0cRVANH6JAtv0yZdnBZEXYYqBAwN+G5a4NY+2I1KvoOCYzbk8SnStG7y4hCdVAAWOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ag-charts-types": "11.3.2" + } + }, + "node_modules/ag-grid-react": { + "version": "33.3.2", + "resolved": "https://registry.npmjs.org/ag-grid-react/-/ag-grid-react-33.3.2.tgz", + "integrity": "sha512-5bv4JIJvGov23sduIUIyQTqpa/qhoQrRkQm5pFOQb7RMwusfx6xBPrkLwIIlCJiQ8g0OOinxWzZ2kQ2Zml6tLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ag-grid-community": "33.3.2", + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-process-hrtime": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", + "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "peer": true + }, + "node_modules/browserslist": { + "version": "4.25.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.4.tgz", + "integrity": "sha512-4jYpcjabC606xJ3kw2QwGEZKX0Aw7sgQdZCvIK9dhVSPh76BKo+C+btT1RRofH7B+8iNpEbgGNVWiLki5q93yg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001737", + "electron-to-chromium": "^1.5.211", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001741", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001741.tgz", + "integrity": "sha512-QGUGitqsc8ARjLdgAfxETDhRbJ0REsP6O3I96TAth/mVjh2cYzN2u+3AzPP3aVSm2FehEItaJw1xd+IGBXWeSw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chart.js": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.0.tgz", + "integrity": "sha512-aYeC/jDgSEx8SHWZvANYMioYMZ2KX02W6f6uVfyteuCGcadDLcYVHdfdygsTQkQ4TKn5lghoojAsPj5pu0SnvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@kurkle/color": "^0.3.0" + }, + "engines": { + "pnpm": ">=8" + } + }, + "node_modules/check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cosmiconfig/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=4" + } + }, + "node_modules/css-to-react-native": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", + "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, + "node_modules/cssom": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", + "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, + "node_modules/cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "cssom": "~0.3.6" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cssstyle/node_modules/cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true, + "license": "MIT" + }, + "node_modules/data-urls": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", + "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "abab": "^2.0.3", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/domexception": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", + "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", + "deprecated": "Use your platform's native DOMException instead", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "webidl-conversions": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/domexception/node_modules/webidl-conversions": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", + "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/earcut": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/earcut/-/earcut-3.0.2.tgz", + "integrity": "sha512-X7hshQbLyMJ/3RPhyObLARM2sNxxmRALLKx1+NVFFnQ9gKzmCrxm9+uLIAdBcvc8FNLpctqlQ2V6AE92Ol9UDQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.214", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.214.tgz", + "integrity": "sha512-TpvUNdha+X3ybfU78NoQatKvQEm1oq3lf2QbnmCEdw+Bd9RuIAY+hJTvq1avzHM0f7EJfnH3vbCnbzKzisc/9Q==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", + "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz", + "integrity": "sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.3", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.6", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.4", + "safe-array-concat": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/esbuild": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz", + "integrity": "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.9", + "@esbuild/android-arm": "0.25.9", + "@esbuild/android-arm64": "0.25.9", + "@esbuild/android-x64": "0.25.9", + "@esbuild/darwin-arm64": "0.25.9", + "@esbuild/darwin-x64": "0.25.9", + "@esbuild/freebsd-arm64": "0.25.9", + "@esbuild/freebsd-x64": "0.25.9", + "@esbuild/linux-arm": "0.25.9", + "@esbuild/linux-arm64": "0.25.9", + "@esbuild/linux-ia32": "0.25.9", + "@esbuild/linux-loong64": "0.25.9", + "@esbuild/linux-mips64el": "0.25.9", + "@esbuild/linux-ppc64": "0.25.9", + "@esbuild/linux-riscv64": "0.25.9", + "@esbuild/linux-s390x": "0.25.9", + "@esbuild/linux-x64": "0.25.9", + "@esbuild/netbsd-arm64": "0.25.9", + "@esbuild/netbsd-x64": "0.25.9", + "@esbuild/openbsd-arm64": "0.25.9", + "@esbuild/openbsd-x64": "0.25.9", + "@esbuild/openharmony-arm64": "0.25.9", + "@esbuild/sunos-x64": "0.25.9", + "@esbuild/win32-arm64": "0.25.9", + "@esbuild/win32-ia32": "0.25.9", + "@esbuild/win32-x64": "0.25.9" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "peer": true, + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/escodegen/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint": { + "version": "9.35.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.35.0.tgz", + "integrity": "sha512-QePbBFMJFjgmlE+cXAlbHZbHpdFVS2E/6vzCy7aKlebddvl1vadiC4JFV5u/wqTkNUwEV8WrQi257jf5f06hrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.3.1", + "@eslint/core": "^0.15.2", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.35.0", + "@eslint/plugin-kit": "^0.3.5", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.9", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "peer": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expect-type": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz", + "integrity": "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "dev": true, + "license": "MIT" + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.4.tgz", + "integrity": "sha512-f0cRzm6dkyVYV3nPoooP8XlccPQukegwhAnpoLcXy+X+A8KfpGOoXwDr9FLZd3wzgLaBGQBE3lY93Zm/i1JvIQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/geotiff": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/geotiff/-/geotiff-2.1.3.tgz", + "integrity": "sha512-PT6uoF5a1+kbC3tHmZSUsLHBp2QJlHasxxxxPW47QIY1VBKpFB+FcDvX+MxER6UzgLQZ0xDzJ9s48B9JbOCTqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@petamoriken/float16": "^3.4.7", + "lerc": "^3.0.0", + "pako": "^2.0.4", + "parse-headers": "^2.0.2", + "quick-lru": "^6.1.1", + "web-worker": "^1.2.0", + "xml-utils": "^1.0.2", + "zstddec": "^0.1.0" + }, + "engines": { + "node": ">=10.19" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "16.3.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.3.0.tgz", + "integrity": "sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/html-encoding-sniffer": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", + "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "whatwg-encoding": "^1.0.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/immutable": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.3.tgz", + "integrity": "sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg==", + "dev": true, + "license": "MIT" + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", + "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-proto": "^1.0.0", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/jackspeak": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", + "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsdom": { + "version": "16.7.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", + "integrity": "sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "abab": "^2.0.5", + "acorn": "^8.2.4", + "acorn-globals": "^6.0.0", + "cssom": "^0.4.4", + "cssstyle": "^2.3.0", + "data-urls": "^2.0.0", + "decimal.js": "^10.2.1", + "domexception": "^2.0.1", + "escodegen": "^2.0.0", + "form-data": "^3.0.0", + "html-encoding-sniffer": "^2.0.1", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.0", + "parse5": "6.0.1", + "saxes": "^5.0.1", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.0.0", + "w3c-hr-time": "^1.0.2", + "w3c-xmlserializer": "^2.0.0", + "webidl-conversions": "^6.1.0", + "whatwg-encoding": "^1.0.5", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.5.0", + "ws": "^7.4.6", + "xml-name-validator": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/lerc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lerc/-/lerc-3.0.0.tgz", + "integrity": "sha512-Rm4J/WaHhRa93nCN2mwWDZFoRVF18G1f47C+kvQWyHGEZxFpTUi73p7lMVSAndyxGt6lJ2/CFbOcf9ra5p8aww==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lunr": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "0.30.18", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.18.tgz", + "integrity": "sha512-yi8swmWbO17qHhwIBNeeZxTceJMeBvWJaId6dyvTSOwTipqeHhMhOrz6513r1sOKnpvQ7zkhlG8tPrpilwTxHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/markdown-it": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/markdown-it/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/node-releases": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.20.tgz", + "integrity": "sha512-7gK6zSXEH6neM212JgfYFXe+GmZQM+fia5SsusuBIUgnPheLFBmIPhtFoAQRj8/7wASYQnbDlHPVwY0BefoFgA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nwsapi": { + "version": "2.2.22", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.22.tgz", + "integrity": "sha512-ujSMe1OWVn55euT1ihwCI1ZcAaAU3nxUiDwfDQldc51ZXaB9m2AyOn6/jh1BLe2t/G8xd6uKG1UBF2aZJeg2SQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ol": { + "version": "10.6.1", + "resolved": "https://registry.npmjs.org/ol/-/ol-10.6.1.tgz", + "integrity": "sha512-xp174YOwPeLj7c7/8TCIEHQ4d41tgTDDhdv6SqNdySsql5/MaFJEJkjlsYcvOPt7xA6vrum/QG4UdJ0iCGT1cg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@types/rbush": "4.0.0", + "earcut": "^3.0.0", + "geotiff": "^2.1.3", + "pbf": "4.0.1", + "rbush": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/openlayers" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/pako": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", + "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==", + "dev": true, + "license": "(MIT AND Zlib)" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-headers": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.6.tgz", + "integrity": "sha512-Tz11t3uKztEW5FEVZnj1ox8GKblWn+PvHY9TmJV5Mll2uHEwRdR/5Li1OlXoECjLYkApdhWy44ocONwXLiKO5A==", + "dev": true, + "license": "MIT" + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.2.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.1.tgz", + "integrity": "sha512-r8LA6i4LP4EeWOhqBaZZjDWwehd1xUJPCJd9Sv300H0ZmcUER4+JPh7bqqZeqs1o5pgtgvXm+d9UGrB5zZGDiQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/pbf": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pbf/-/pbf-4.0.1.tgz", + "integrity": "sha512-SuLdBvS42z33m8ejRbInMapQe8n0D3vN/Xd5fmWM3tufNgRQFBpaW2YVJxQZV4iPNqb0vEFvssMEo5w9c6BTIA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "resolve-protobuf-schema": "^2.1.0" + }, + "bin": { + "pbf": "bin/pbf" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/playwright": { + "version": "1.55.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.55.0.tgz", + "integrity": "sha512-sdCWStblvV1YU909Xqx0DhOjPZE4/5lJsIS84IfN9dAZfcl/CIZ5O8l3o0j7hPMjDvqoTF8ZUcc+i/GL5erstA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.55.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.55.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.55.0.tgz", + "integrity": "sha512-GvZs4vU3U5ro2nZpeiwyb0zuFaqb9sUiAJuyrWpcGouD8y9/HLgGbNRjIph7zU9D3hnPaisMl9zG9CgFi/biIg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prismjs": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/protocol-buffers-schema": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz", + "integrity": "sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw==", + "dev": true, + "license": "MIT" + }, + "node_modules/psl": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", + "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "punycode": "^2.3.1" + }, + "funding": { + "url": "https://github.com/sponsors/lupomontero" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/quick-lru": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-6.1.2.tgz", + "integrity": "sha512-AAFUA5O1d83pIHEhJwWCq/RQcRukCkn/NSm2QsTEMle5f2hP0ChI2+3Xb051PZCkLryI/Ir1MVKviT2FIloaTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/quickselect": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-3.0.0.tgz", + "integrity": "sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==", + "dev": true, + "license": "ISC" + }, + "node_modules/rbush": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/rbush/-/rbush-4.0.1.tgz", + "integrity": "sha512-IP0UpfeWQujYC8Jg162rMNc01Rf0gWMMAb2Uxus/Q0qOFw4lCcq6ZnQEZwUoJqWyUGJ9th7JjwI4yIWo+uvoAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "quickselect": "^3.0.0" + } + }, + "node_modules/react": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz", + "integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-chartjs-2": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.3.0.tgz", + "integrity": "sha512-UfZZFnDsERI3c3CZGxzvNJd02SHjaSJ8kgW1djn65H1KK8rehwTjyrRKOG3VTMG8wtHZ5rgAO5oTHtHi9GCCmw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "chart.js": "^4.1.1", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-dom": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz", + "integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "scheduler": "^0.26.0" + }, + "peerDependencies": { + "react": "^19.1.1" + } + }, + "node_modules/react-is": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.1.tgz", + "integrity": "sha512-tr41fA15Vn8p4X9ntI+yCyeGSf1TlYaY5vlTZfQmeLBrFo3psOPX6HhTDnFNL9uj3EhP0KAQ80cugCl4b4BERA==", + "dev": true, + "license": "MIT" + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-scripts": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-0.0.0.tgz", + "integrity": "sha512-W7cVfdhbIvYrTjVaryO7WCpvzODu8V7JH/1O36RcfuzP3Cxjp5WpX5ycaoGt0LSQpntrem5jFSUoJrtvru1reA==", + "dev": true + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, + "node_modules/reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-protobuf-schema": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz", + "integrity": "sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "protocol-buffers-schema": "^3.3.1" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz", + "integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^11.0.0", + "package-json-from-dist": "^1.0.0" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.3.tgz", + "integrity": "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.0.3", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", + "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@isaacs/brace-expansion": "^5.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.50.0.tgz", + "integrity": "sha512-/Zl4D8zPifNmyGzJS+3kVoyXeDeT/GrsJM94sACNg9RtUE0hrHa1bNPtRSrfHTMH5HjRzce6K7rlTh3Khiw+pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.50.0", + "@rollup/rollup-android-arm64": "4.50.0", + "@rollup/rollup-darwin-arm64": "4.50.0", + "@rollup/rollup-darwin-x64": "4.50.0", + "@rollup/rollup-freebsd-arm64": "4.50.0", + "@rollup/rollup-freebsd-x64": "4.50.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.50.0", + "@rollup/rollup-linux-arm-musleabihf": "4.50.0", + "@rollup/rollup-linux-arm64-gnu": "4.50.0", + "@rollup/rollup-linux-arm64-musl": "4.50.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.50.0", + "@rollup/rollup-linux-ppc64-gnu": "4.50.0", + "@rollup/rollup-linux-riscv64-gnu": "4.50.0", + "@rollup/rollup-linux-riscv64-musl": "4.50.0", + "@rollup/rollup-linux-s390x-gnu": "4.50.0", + "@rollup/rollup-linux-x64-gnu": "4.50.0", + "@rollup/rollup-linux-x64-musl": "4.50.0", + "@rollup/rollup-openharmony-arm64": "4.50.0", + "@rollup/rollup-win32-arm64-msvc": "4.50.0", + "@rollup/rollup-win32-ia32-msvc": "4.50.0", + "@rollup/rollup-win32-x64-msvc": "4.50.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, + "node_modules/sass": { + "version": "1.92.1", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.92.1.tgz", + "integrity": "sha512-ffmsdbwqb3XeyR8jJR6KelIXARM9bFQe8A6Q3W4Klmwy5Ckd5gz7jgUNHo4UOqutU5Sk1DtKLbpDP0nLCg1xqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.0", + "immutable": "^5.0.2", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, + "node_modules/sass/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/sass/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/saxes": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", + "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true, + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/scheduler": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", + "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", + "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", + "dev": true, + "license": "MIT" + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-literal": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.0.0.tgz", + "integrity": "sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/strip-literal/node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/styled-components": { + "version": "6.1.19", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.19.tgz", + "integrity": "sha512-1v/e3Dl1BknC37cXMhwGomhO8AkYmN41CqyX9xhUDxry1ns3BFQy2lLDRQXJRdVVWB9OHemv/53xaStimvWyuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@emotion/is-prop-valid": "1.2.2", + "@emotion/unitless": "0.8.1", + "@types/stylis": "4.2.5", + "css-to-react-native": "3.2.0", + "csstype": "3.1.3", + "postcss": "8.4.49", + "shallowequal": "1.1.0", + "stylis": "4.3.2", + "tslib": "2.6.2" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0" + } + }, + "node_modules/styled-components/node_modules/@emotion/is-prop-valid": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz", + "integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.8.1" + } + }, + "node_modules/styled-components/node_modules/@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==", + "dev": true, + "license": "MIT" + }, + "node_modules/styled-components/node_modules/@emotion/unitless": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", + "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/styled-components/node_modules/postcss": { + "version": "8.4.49", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/styled-components/node_modules/stylis": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz", + "integrity": "sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==", + "dev": true, + "license": "MIT" + }, + "node_modules/styled-components/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true, + "license": "0BSD" + }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "dev": true, + "license": "MIT" + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, + "node_modules/terser": { + "version": "5.44.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.0.tgz", + "integrity": "sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "peer": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.3.tgz", + "integrity": "sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tough-cookie": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "peer": true, + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tough-cookie/node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/tr46": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", + "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typedoc": { + "version": "0.28.12", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.28.12.tgz", + "integrity": "sha512-H5ODu4f7N+myG4MfuSp2Vh6wV+WLoZaEYxKPt2y8hmmqNEMVrH69DAjjdmYivF4tP/C2jrIZCZhPalZlTU/ipA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@gerrit0/mini-shiki": "^3.12.0", + "lunr": "^2.3.9", + "markdown-it": "^14.1.0", + "minimatch": "^9.0.5", + "yaml": "^2.8.1" + }, + "bin": { + "typedoc": "bin/typedoc" + }, + "engines": { + "node": ">= 18", + "pnpm": ">= 10" + }, + "peerDependencies": { + "typescript": "5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x || 5.9.x" + } + }, + "node_modules/typedoc/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/typedoc/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/typescript": { + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", + "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.42.0.tgz", + "integrity": "sha512-ozR/rQn+aQXQxh1YgbCzQWDFrsi9mcg+1PM3l/z5o1+20P7suOIaNg515bpr/OYt6FObz/NHcBstydDLHWeEKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.42.0", + "@typescript-eslint/parser": "8.42.0", + "@typescript-eslint/typescript-estree": "8.42.0", + "@typescript-eslint/utils": "8.42.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "dev": true, + "license": "MIT" + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", + "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/vite": { + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", + "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vitest": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", + "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.4", + "@vitest/mocker": "3.2.4", + "@vitest/pretty-format": "^3.2.4", + "@vitest/runner": "3.2.4", + "@vitest/snapshot": "3.2.4", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.4", + "@vitest/ui": "3.2.4", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/w3c-hr-time": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", + "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", + "deprecated": "Use your platform's native performance.now() and performance.timeOrigin.", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "browser-process-hrtime": "^1.0.0" + } + }, + "node_modules/w3c-xmlserializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", + "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "xml-name-validator": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/web-worker": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.5.0.tgz", + "integrity": "sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/webidl-conversions": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", + "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "peer": true, + "engines": { + "node": ">=10.4" + } + }, + "node_modules/whatwg-encoding": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", + "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "iconv-lite": "0.4.24" + } + }, + "node_modules/whatwg-encoding/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/whatwg-mimetype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", + "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, + "node_modules/whatwg-url": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", + "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "lodash": "^4.7.0", + "tr46": "^2.1.0", + "webidl-conversions": "^6.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", + "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "peer": true + }, + "node_modules/xml-utils": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/xml-utils/-/xml-utils-1.10.2.tgz", + "integrity": "sha512-RqM+2o1RYs6T8+3DzDSoTRAUfrvaejbVHcp3+thnAtDKo8LskR+HomLajEy5UjTz24rpka7AxVBRR3g2wTUkJA==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", + "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zstddec": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/zstddec/-/zstddec-0.1.0.tgz", + "integrity": "sha512-w2NTI8+3l3eeltKAdK8QpiLo/flRAr2p8AGeakfMZOXBxOg9HIu4LVDxBi81sYgVhFhdJjv1OrB5ssI8uFPoLg==", + "dev": true, + "license": "MIT AND BSD-3-Clause" + } + } +} diff --git a/package.json b/package.json index f0def5c2..5e018d32 100755 --- a/package.json +++ b/package.json @@ -88,7 +88,7 @@ "react": "^19.1.0", "react-chartjs-2": "^5.3.0", "react-dom": "^19.1.0", - "react-scripts": "5.0.1", + "react-scripts": "^0.0.0", "rimraf": "^6.0.1", "sass": "^1.86.3", "styled-components": "^6.1.17", diff --git a/src/I18nLabel.ts b/src/I18nLabel.ts index c79e22b0..94fc1d22 100644 --- a/src/I18nLabel.ts +++ b/src/I18nLabel.ts @@ -7,6 +7,8 @@ export enum I18nLabel { Maximize = "Maximize tab set", Restore = "Restore tab set", Popout_Tab = "Popout selected tab", + Pin_Tab = "Pin tab", + Unpin_Tab = "Unpin tab", Overflow_Menu_Tooltip = "Hidden tabs", Error_rendering_component = "Error rendering component", Error_rendering_component_retry = "Retry", diff --git a/src/model/IJsonModel.ts b/src/model/IJsonModel.ts index 9545c4b6..c5508d34 100755 --- a/src/model/IJsonModel.ts +++ b/src/model/IJsonModel.ts @@ -6,19 +6,19 @@ export interface IJsonModel { global?: IGlobalAttributes; borders?: IJsonBorderNode[]; layout: IJsonRowNode; // top level 'row' is horizontal, rows inside rows take opposite orientation to parent row (ie can act as columns) - popouts?: Record; + popouts?: Record; } export interface IJsonRect { - x: number; - y: number; - width: number; - height: number; + x: number; + y: number; + width: number; + height: number; } export interface IJsonPopout { layout: IJsonRowNode; - rect: IJsonRect ; + rect: IJsonRect; } export interface IJsonBorderNode extends IBorderAttributes { @@ -31,234 +31,234 @@ export interface IJsonRowNode extends IRowAttributes { } export interface IJsonTabSetNode extends ITabSetAttributes { - /** Marks this as the active tab set, read from initial json but - * must subseqently be set on the model (only one tab set can be active)*/ - active?: boolean; - /** Marks this tab set as being maximized, read from initial json but - * must subseqently be set on the model (only one tab set can be maximized) */ - maximized?: boolean; + /** Marks this as the active tab set, read from initial json but + * must subseqently be set on the model (only one tab set can be active)*/ + active?: boolean; + /** Marks this tab set as being maximized, read from initial json but + * must subseqently be set on the model (only one tab set can be maximized) */ + maximized?: boolean; children: IJsonTabNode[]; } -export interface IJsonTabNode extends ITabAttributes { -} +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +export interface IJsonTabNode extends ITabAttributes {} //---------------------------------------------------------------------------------------------------------- // below this line is autogenerated from attributes in code via Model static method toTypescriptInterfaces() //---------------------------------------------------------------------------------------------------------- export interface IGlobalAttributes { - /** + /** Value for BorderNode attribute autoSelectTabWhenClosed if not overridden whether to select new/moved tabs in border when the border is currently closed Default: false */ - borderAutoSelectTabWhenClosed?: boolean; + borderAutoSelectTabWhenClosed?: boolean; - /** + /** Value for BorderNode attribute autoSelectTabWhenOpen if not overridden whether to select new/moved tabs in border when the border is already open Default: true */ - borderAutoSelectTabWhenOpen?: boolean; + borderAutoSelectTabWhenOpen?: boolean; - /** + /** Value for BorderNode attribute className if not overridden class applied to tab button Default: undefined */ - borderClassName?: string; + borderClassName?: string; - /** + /** Value for BorderNode attribute enableAutoHide if not overridden hide border if it has zero tabs Default: false */ - borderEnableAutoHide?: boolean; + borderEnableAutoHide?: boolean; - /** + /** Value for BorderNode attribute enableDrop if not overridden whether tabs can be dropped into this border Default: true */ - borderEnableDrop?: boolean; + borderEnableDrop?: boolean; - /** + /** Value for BorderNode attribute enableTabScrollbar if not overridden whether to show a mini scrollbar for the tabs Default: false */ - borderEnableTabScrollbar?: boolean; + borderEnableTabScrollbar?: boolean; - /** + /** Value for BorderNode attribute maxSize if not overridden the maximum size of the tab area Default: 99999 */ - borderMaxSize?: number; + borderMaxSize?: number; - /** + /** Value for BorderNode attribute minSize if not overridden the minimum size of the tab area Default: 0 */ - borderMinSize?: number; + borderMinSize?: number; - /** + /** Value for BorderNode attribute size if not overridden size of the tab area when selected Default: 200 */ - borderSize?: number; + borderSize?: number; - /** + /** enable docking to the edges of the layout, this will show the edge indicators Default: true */ - enableEdgeDock?: boolean; + enableEdgeDock?: boolean; - /** + /** boolean indicating if tab icons should rotate with the text in the left and right borders Default: true */ - enableRotateBorderIcons?: boolean; + enableRotateBorderIcons?: boolean; - /** + /** the top level 'row' will layout horizontally by default, set this option true to make it layout vertically Default: false */ - rootOrientationVertical?: boolean; + rootOrientationVertical?: boolean; - /** + /** enable a small centralized handle on all splitters Default: false */ - splitterEnableHandle?: boolean; + splitterEnableHandle?: boolean; - /** + /** additional width in pixels of the splitter hit test area Default: 0 */ - splitterExtra?: number; + splitterExtra?: number; - /** + /** width in pixels of all splitters between tabsets/borders Default: 8 */ - splitterSize?: number; + splitterSize?: number; - /** + /** Value for TabNode attribute borderHeight if not overridden height when added to border, -1 will use border size Default: -1 */ - tabBorderHeight?: number; + tabBorderHeight?: number; - /** + /** Value for TabNode attribute borderWidth if not overridden width when added to border, -1 will use border size Default: -1 */ - tabBorderWidth?: number; + tabBorderWidth?: number; - /** + /** Value for TabNode attribute className if not overridden class applied to tab button Default: undefined */ - tabClassName?: string; + tabClassName?: string; - /** + /** Value for TabNode attribute closeType if not overridden see values in ICloseType Default: 1 */ - tabCloseType?: ICloseType; + tabCloseType?: ICloseType; - /** + /** Value for TabNode attribute contentClassName if not overridden class applied to tab content Default: undefined */ - tabContentClassName?: string; + tabContentClassName?: string; - /** + /** Default: 0.3 */ - tabDragSpeed?: number; + tabDragSpeed?: number; - /** + /** Value for TabNode attribute enableClose if not overridden allow user to close tab via close button Default: true */ - tabEnableClose?: boolean; + tabEnableClose?: boolean; - /** + /** Value for TabNode attribute enableDrag if not overridden allow user to drag tab to new location Default: true */ - tabEnableDrag?: boolean; + tabEnableDrag?: boolean; - /** + /** Value for TabNode attribute enablePopout if not overridden enable popout (in popout capable browser) Default: false */ - tabEnablePopout?: boolean; + tabEnablePopout?: boolean; - /** + /** Value for TabNode attribute enablePopoutIcon if not overridden whether to show the popout icon in the tabset header if this tab enables popouts Default: true */ - tabEnablePopoutIcon?: boolean; + tabEnablePopoutIcon?: boolean; - /** + /** Value for TabNode attribute enablePopoutOverlay if not overridden if this tab will not work correctly in a popout window when the main window is backgrounded (inactive) @@ -266,702 +266,704 @@ export interface IGlobalAttributes { Default: false */ - tabEnablePopoutOverlay?: boolean; + tabEnablePopoutOverlay?: boolean; - /** + /** Value for TabNode attribute enableRename if not overridden allow user to rename tabs by double clicking Default: true */ - tabEnableRename?: boolean; + tabEnableRename?: boolean; - /** + /** Value for TabNode attribute enableRenderOnDemand if not overridden whether to avoid rendering component until tab is visible Default: true */ - tabEnableRenderOnDemand?: boolean; + tabEnableRenderOnDemand?: boolean; - /** + /** Value for TabNode attribute icon if not overridden the tab icon Default: undefined */ - tabIcon?: string; + tabIcon?: string; - /** + /** Value for TabNode attribute maxHeight if not overridden the max height of this tab Default: 99999 */ - tabMaxHeight?: number; + tabMaxHeight?: number; - /** + /** Value for TabNode attribute maxWidth if not overridden the max width of this tab Default: 99999 */ - tabMaxWidth?: number; + tabMaxWidth?: number; - /** + /** Value for TabNode attribute minHeight if not overridden the min height of this tab Default: 0 */ - tabMinHeight?: number; + tabMinHeight?: number; - /** + /** Value for TabNode attribute minWidth if not overridden the min width of this tab Default: 0 */ - tabMinWidth?: number; + tabMinWidth?: number; - /** + /** Value for TabSetNode attribute autoSelectTab if not overridden whether to select new/moved tabs in tabset Default: true */ - tabSetAutoSelectTab?: boolean; + tabSetAutoSelectTab?: boolean; - /** + /** Value for TabSetNode attribute classNameTabStrip if not overridden a class name to apply to the tab strip Default: undefined */ - tabSetClassNameTabStrip?: string; + tabSetClassNameTabStrip?: string; - /** + /** Value for TabSetNode attribute enableActiveIcon if not overridden whether the active icon (*) should be displayed when the tabset is active Default: false */ - tabSetEnableActiveIcon?: boolean; + tabSetEnableActiveIcon?: boolean; - /** + /** Value for TabSetNode attribute enableClose if not overridden allow user to close tabset via a close button Default: false */ - tabSetEnableClose?: boolean; + tabSetEnableClose?: boolean; - /** + /** Value for TabSetNode attribute enableDeleteWhenEmpty if not overridden whether to delete this tabset when is has no tabs Default: true */ - tabSetEnableDeleteWhenEmpty?: boolean; + tabSetEnableDeleteWhenEmpty?: boolean; - /** + /** Value for TabSetNode attribute enableDivide if not overridden allow user to drag tabs to region of this tabset, splitting into new tabset Default: true */ - tabSetEnableDivide?: boolean; + tabSetEnableDivide?: boolean; - /** + /** Value for TabSetNode attribute enableDrag if not overridden allow user to drag tabs out this tabset Default: true */ - tabSetEnableDrag?: boolean; + tabSetEnableDrag?: boolean; - /** + /** Value for TabSetNode attribute enableDrop if not overridden allow user to drag tabs into this tabset Default: true */ - tabSetEnableDrop?: boolean; + tabSetEnableDrop?: boolean; - /** + /** Value for TabSetNode attribute enableMaximize if not overridden allow user to maximize tabset to fill view via maximize button Default: true */ - tabSetEnableMaximize?: boolean; + tabSetEnableMaximize?: boolean; - /** + /** Value for TabSetNode attribute enableSingleTabStretch if not overridden if the tabset has only a single tab then stretch the single tab to fill area and display in a header style Default: false */ - tabSetEnableSingleTabStretch?: boolean; + tabSetEnableSingleTabStretch?: boolean; - /** + /** Value for TabSetNode attribute enableTabScrollbar if not overridden whether to show a mini scrollbar for the tabs Default: false */ - tabSetEnableTabScrollbar?: boolean; + tabSetEnableTabScrollbar?: boolean; - /** + /** Value for TabSetNode attribute enableTabStrip if not overridden enable tab strip and allow multiple tabs in this tabset Default: true */ - tabSetEnableTabStrip?: boolean; + tabSetEnableTabStrip?: boolean; - /** + /** Value for TabSetNode attribute enableTabWrap if not overridden wrap tabs onto multiple lines Default: false */ - tabSetEnableTabWrap?: boolean; + tabSetEnableTabWrap?: boolean; - /** + /** Value for TabSetNode attribute maxHeight if not overridden maximum height (in px) for this tabset Default: 99999 */ - tabSetMaxHeight?: number; + tabSetMaxHeight?: number; - /** + /** Value for TabSetNode attribute maxWidth if not overridden maximum width (in px) for this tabset Default: 99999 */ - tabSetMaxWidth?: number; + tabSetMaxWidth?: number; - /** + /** Value for TabSetNode attribute minHeight if not overridden minimum height (in px) for this tabset Default: 0 */ - tabSetMinHeight?: number; + tabSetMinHeight?: number; - /** + /** Value for TabSetNode attribute minWidth if not overridden minimum width (in px) for this tabset Default: 0 */ - tabSetMinWidth?: number; + tabSetMinWidth?: number; - /** + /** Value for TabSetNode attribute tabLocation if not overridden the location of the tabs either top or bottom Default: "top" */ - tabSetTabLocation?: ITabLocation; - + tabSetTabLocation?: ITabLocation; } export interface IRowAttributes { - /** + /** the unique id of the row, if left undefined a uuid will be assigned Default: undefined */ - id?: string; + id?: string; - /** + /** Fixed value: "row" */ - type?: string; + type?: string; - /** + /** relative weight for sizing of this row in parent row Default: 100 */ - weight?: number; - + weight?: number; } export interface ITabSetAttributes { - /** + /** whether to select new/moved tabs in tabset Default: inherited from Global attribute tabSetAutoSelectTab (default true) */ - autoSelectTab?: boolean; + autoSelectTab?: boolean; - /** + /** a class name to apply to the tab strip Default: inherited from Global attribute tabSetClassNameTabStrip (default undefined) */ - classNameTabStrip?: string; + classNameTabStrip?: string; - /** + /** a place to hold json config used in your own code Default: undefined */ - config?: any; + config?: any; - /** + /** whether the active icon (*) should be displayed when the tabset is active Default: inherited from Global attribute tabSetEnableActiveIcon (default false) */ - enableActiveIcon?: boolean; + enableActiveIcon?: boolean; - /** + /** allow user to close tabset via a close button Default: inherited from Global attribute tabSetEnableClose (default false) */ - enableClose?: boolean; + enableClose?: boolean; - /** + /** whether to delete this tabset when is has no tabs Default: inherited from Global attribute tabSetEnableDeleteWhenEmpty (default true) */ - enableDeleteWhenEmpty?: boolean; + enableDeleteWhenEmpty?: boolean; - /** + /** allow user to drag tabs to region of this tabset, splitting into new tabset Default: inherited from Global attribute tabSetEnableDivide (default true) */ - enableDivide?: boolean; + enableDivide?: boolean; - /** + /** allow user to drag tabs out this tabset Default: inherited from Global attribute tabSetEnableDrag (default true) */ - enableDrag?: boolean; + enableDrag?: boolean; - /** + /** allow user to drag tabs into this tabset Default: inherited from Global attribute tabSetEnableDrop (default true) */ - enableDrop?: boolean; + enableDrop?: boolean; - /** + /** allow user to maximize tabset to fill view via maximize button Default: inherited from Global attribute tabSetEnableMaximize (default true) */ - enableMaximize?: boolean; + enableMaximize?: boolean; - /** + /** if the tabset has only a single tab then stretch the single tab to fill area and display in a header style Default: inherited from Global attribute tabSetEnableSingleTabStretch (default false) */ - enableSingleTabStretch?: boolean; + enableSingleTabStretch?: boolean; - /** + /** whether to show a mini scrollbar for the tabs Default: inherited from Global attribute tabSetEnableTabScrollbar (default false) */ - enableTabScrollbar?: boolean; + enableTabScrollbar?: boolean; - /** + /** enable tab strip and allow multiple tabs in this tabset Default: inherited from Global attribute tabSetEnableTabStrip (default true) */ - enableTabStrip?: boolean; + enableTabStrip?: boolean; - /** + /** wrap tabs onto multiple lines Default: inherited from Global attribute tabSetEnableTabWrap (default false) */ - enableTabWrap?: boolean; + enableTabWrap?: boolean; - /** + /** the unique id of the tab set, if left undefined a uuid will be assigned Default: undefined */ - id?: string; + id?: string; - /** + /** maximum height (in px) for this tabset Default: inherited from Global attribute tabSetMaxHeight (default 99999) */ - maxHeight?: number; + maxHeight?: number; - /** + /** maximum width (in px) for this tabset Default: inherited from Global attribute tabSetMaxWidth (default 99999) */ - maxWidth?: number; + maxWidth?: number; - /** + /** minimum height (in px) for this tabset Default: inherited from Global attribute tabSetMinHeight (default 0) */ - minHeight?: number; + minHeight?: number; - /** + /** minimum width (in px) for this tabset Default: inherited from Global attribute tabSetMinWidth (default 0) */ - minWidth?: number; + minWidth?: number; - /** + /** Default: undefined */ - name?: string; + name?: string; - /** + /** index of selected/visible tab in tabset Default: 0 */ - selected?: number; + selected?: number; - /** + /** the location of the tabs either top or bottom Default: inherited from Global attribute tabSetTabLocation (default "top") */ - tabLocation?: ITabLocation; + tabLocation?: ITabLocation; - /** + /** Fixed value: "tabset" */ - type?: string; + type?: string; - /** + /** relative weight for sizing of this tabset in parent row Default: 100 */ - weight?: number; - + weight?: number; } export interface ITabAttributes { - /** + /** if there is no name specifed then this value will be used in the overflow menu Default: undefined */ - altName?: string; + altName?: string; - /** + /** height when added to border, -1 will use border size Default: inherited from Global attribute tabBorderHeight (default -1) */ - borderHeight?: number; + borderHeight?: number; - /** + /** width when added to border, -1 will use border size Default: inherited from Global attribute tabBorderWidth (default -1) */ - borderWidth?: number; + borderWidth?: number; - /** + /** class applied to tab button Default: inherited from Global attribute tabClassName (default undefined) */ - className?: string; + className?: string; - /** + /** see values in ICloseType Default: inherited from Global attribute tabCloseType (default 1) */ - closeType?: ICloseType; + closeType?: ICloseType; - /** + /** string identifying which component to run (for factory) Default: undefined */ - component?: string; + component?: string; - /** + /** a place to hold json config for the hosted component Default: undefined */ - config?: any; + config?: any; - /** + /** class applied to tab content Default: inherited from Global attribute tabContentClassName (default undefined) */ - contentClassName?: string; + contentClassName?: string; - /** + /** allow user to close tab via close button Default: inherited from Global attribute tabEnableClose (default true) */ - enableClose?: boolean; + enableClose?: boolean; - /** + /** allow user to drag tab to new location Default: inherited from Global attribute tabEnableDrag (default true) */ - enableDrag?: boolean; + enableDrag?: boolean; - /** + /** enable popout (in popout capable browser) Default: inherited from Global attribute tabEnablePopout (default false) */ - enablePopout?: boolean; + enablePopout?: boolean; - /** + /** whether to show the popout icon in the tabset header if this tab enables popouts Default: inherited from Global attribute tabEnablePopoutIcon (default true) */ - enablePopoutIcon?: boolean; + enablePopoutIcon?: boolean; - /** + /** if this tab will not work correctly in a popout window when the main window is backgrounded (inactive) then enabling this option will gray out this tab Default: inherited from Global attribute tabEnablePopoutOverlay (default false) */ - enablePopoutOverlay?: boolean; + enablePopoutOverlay?: boolean; - /** + /** allow user to rename tabs by double clicking Default: inherited from Global attribute tabEnableRename (default true) */ - enableRename?: boolean; + enableRename?: boolean; - /** + /** whether to avoid rendering component until tab is visible Default: inherited from Global attribute tabEnableRenderOnDemand (default true) */ - enableRenderOnDemand?: boolean; + enableRenderOnDemand?: boolean; - /** + /** if enabled the tab will re-mount when popped out/in Default: false */ - enableWindowReMount?: boolean; + enableWindowReMount?: boolean; + + /** + whether the tab remains open when clicking elsewhere - /** + Default: true + */ + pinned?: boolean; + + /** An optional help text for the tab to be displayed upon tab hover. Default: undefined */ - helpText?: string; + helpText?: string; - /** + /** the tab icon Default: inherited from Global attribute tabIcon (default undefined) */ - icon?: string; + icon?: string; - /** + /** the unique id of the tab, if left undefined a uuid will be assigned Default: undefined */ - id?: string; + id?: string; - /** + /** the max height of this tab Default: inherited from Global attribute tabMaxHeight (default 99999) */ - maxHeight?: number; + maxHeight?: number; - /** + /** the max width of this tab Default: inherited from Global attribute tabMaxWidth (default 99999) */ - maxWidth?: number; + maxWidth?: number; - /** + /** the min height of this tab Default: inherited from Global attribute tabMinHeight (default 0) */ - minHeight?: number; + minHeight?: number; - /** + /** the min width of this tab Default: inherited from Global attribute tabMinWidth (default 0) */ - minWidth?: number; + minWidth?: number; - /** + /** name of tab to be displayed in the tab button Default: "[Unnamed Tab]" */ - name?: string; + name?: string; - /** + /** class applied to parent tabset when this is the only tab and it is stretched to fill the tabset Default: undefined */ - tabsetClassName?: string; + tabsetClassName?: string; - /** + /** Fixed value: "tab" */ - type?: string; - + type?: string; } export interface IBorderAttributes { - /** + /** whether to select new/moved tabs in border when the border is currently closed Default: inherited from Global attribute borderAutoSelectTabWhenClosed (default false) */ - autoSelectTabWhenClosed?: boolean; + autoSelectTabWhenClosed?: boolean; - /** + /** whether to select new/moved tabs in border when the border is already open Default: inherited from Global attribute borderAutoSelectTabWhenOpen (default true) */ - autoSelectTabWhenOpen?: boolean; + autoSelectTabWhenOpen?: boolean; - /** + /** class applied to tab button Default: inherited from Global attribute borderClassName (default undefined) */ - className?: string; + className?: string; - /** + /** a place to hold json config used in your own code Default: undefined */ - config?: any; + config?: any; - /** + /** hide border if it has zero tabs Default: inherited from Global attribute borderEnableAutoHide (default false) */ - enableAutoHide?: boolean; + enableAutoHide?: boolean; - /** + /** whether tabs can be dropped into this border Default: inherited from Global attribute borderEnableDrop (default true) */ - enableDrop?: boolean; + enableDrop?: boolean; - /** + /** whether to show a mini scrollbar for the tabs Default: inherited from Global attribute borderEnableTabScrollbar (default false) */ - enableTabScrollbar?: boolean; + enableTabScrollbar?: boolean; - /** + /** the maximum size of the tab area Default: inherited from Global attribute borderMaxSize (default 99999) */ - maxSize?: number; + maxSize?: number; - /** + /** the minimum size of the tab area Default: inherited from Global attribute borderMinSize (default 0) */ - minSize?: number; + minSize?: number; - /** + /** index of selected/visible tab in border; -1 means no tab selected Default: -1 */ - selected?: number; + selected?: number; - /** + /** show/hide this border Default: true */ - show?: boolean; + show?: boolean; - /** + /** size of the tab area when selected Default: inherited from Global attribute borderSize (default 200) */ - size?: number; + size?: number; - /** + /** Fixed value: "border" */ - type?: string; - -} \ No newline at end of file + type?: string; +} diff --git a/src/model/TabNode.ts b/src/model/TabNode.ts index c9d38450..fe9fe48a 100755 --- a/src/model/TabNode.ts +++ b/src/model/TabNode.ts @@ -158,6 +158,10 @@ export class TabNode extends Node implements IDraggable { return this.getAttr("enableRenderOnDemand") as boolean; } + isPinned() { + return this.getAttr("pinned") as boolean; + } + getMinWidth() { return this.getAttr("minWidth") as number; } @@ -364,7 +368,9 @@ export class TabNode extends Node implements IDraggable { attributeDefinitions.add("enableWindowReMount", false).setType(Attribute.BOOLEAN).setDescription( `if enabled the tab will re-mount when popped out/in` ); - + attributeDefinitions.add("pinned", true).setType(Attribute.BOOLEAN).setDescription( + `whether the tab remains open when clicking elsewhere` + ); attributeDefinitions.addInherited("enableClose", "tabEnableClose").setType(Attribute.BOOLEAN).setDescription( `allow user to close tab via close button` ); diff --git a/src/view/BorderTab.tsx b/src/view/BorderTab.tsx index 02dceaa4..8bc31c33 100644 --- a/src/view/BorderTab.tsx +++ b/src/view/BorderTab.tsx @@ -17,6 +17,8 @@ export function BorderTab(props: IBorderTabProps) { const { layout, border, show } = props; const selfRef = React.useRef(null); const timer = React.useRef(undefined); + const selectedNode = border.getSelectedNode(); + const pinned = selectedNode?.isPinned(); React.useLayoutEffect(() => { const contentRect = layout.getBoundingClientRect(selfRef.current!); @@ -55,20 +57,47 @@ export function BorderTab(props: IBorderTabProps) { style.display = show ? "flex" : "none"; + if (show && pinned === false) { + style.position = "absolute"; + style.zIndex = 999; + style.pointerEvents = "none"; + style.backgroundColor = "transparent"; + const headerRect = border.getTabHeaderRect(); + if (border.getLocation() === DockLocation.LEFT) { + style.left = headerRect.width; + style.top = 0; + style.bottom = 0; + } else if (border.getLocation() === DockLocation.RIGHT) { + style.right = headerRect.width; + style.top = 0; + style.bottom = 0; + } else if (border.getLocation() === DockLocation.TOP) { + style.top = headerRect.height; + style.left = 0; + style.right = 0; + } else { // DockLocation.BOTTOM + style.bottom = headerRect.height; + style.left = 0; + style.right = 0; + } + } + const className = layout.getClassName(CLASSES.FLEXLAYOUT__BORDER_TAB_CONTENTS); + const splitter = show && pinned !== false ? : null; + if (border.getLocation() === DockLocation.LEFT || border.getLocation() === DockLocation.TOP) { return ( <>
- {show && } + {splitter} ); } else { return ( <> - {show && } + {splitter}
diff --git a/src/view/BorderTabSet.tsx b/src/view/BorderTabSet.tsx index ed6056b1..98ba3567 100755 --- a/src/view/BorderTabSet.tsx +++ b/src/view/BorderTabSet.tsx @@ -178,9 +178,46 @@ export const BorderTabSet = (props: IBorderTabSetProps) => { } const selectedIndex = border.getSelected(); - if (selectedIndex !== -1) { - const selectedTabNode = border.getChildren()[selectedIndex] as TabNode; - if (selectedTabNode !== undefined && layout.isSupportsPopout() && selectedTabNode.isEnablePopout()) { + const selectedTabNode = selectedIndex !== -1 ? (border.getChildren()[selectedIndex] as TabNode) : undefined; + const isPinned = selectedTabNode?.isPinned(); + + React.useEffect(() => { + if (selectedTabNode && !isPinned) { + const onBodyPointerDown = (e: PointerEvent) => { + const layoutRect = layout.getDomRect(); + const x = e.clientX - layoutRect.x; + const y = e.clientY - layoutRect.y; + if (!border.getTabHeaderRect().contains(x, y) && !border.getContentRect().contains(x, y)) { + layout.doAction(Actions.selectTab(selectedTabNode.getId())); + } + }; + document.addEventListener("pointerdown", onBodyPointerDown); + return () => document.removeEventListener("pointerdown", onBodyPointerDown); + } + }, [selectedTabNode, isPinned, border, layout]); + + if (selectedTabNode !== undefined) { + const pinTitle = selectedTabNode.isPinned() ? layout.i18nName(I18nLabel.Unpin_Tab) : layout.i18nName(I18nLabel.Pin_Tab); + const pinIcon = selectedTabNode.isPinned() + ? ((typeof icons.unpin === "function") ? icons.unpin(selectedTabNode) : icons.unpin) + : ((typeof icons.pin === "function") ? icons.pin(selectedTabNode) : icons.pin); + const onPinClick = (event: React.MouseEvent) => { + layout.doAction(Actions.updateNodeAttributes(selectedTabNode.getId(), { pinned: !selectedTabNode.isPinned() })); + event.stopPropagation(); + }; + buttons.push( + + ); + + if (layout.isSupportsPopout() && selectedTabNode.isEnablePopout()) { const popoutTitle = layout.i18nName(I18nLabel.Popout_Tab); buttons.push( - ); + overflowContent = icons.more(border, items); + } else { + overflowContent = ( + <> + {icons.more} +
+ {hiddenTabs.length > 0 ? hiddenTabs.length : ''} +
+ + ); } - - const selectedIndex = border.getSelected(); - const selectedTabNode = selectedIndex !== -1 ? (border.getChildren()[selectedIndex] as TabNode) : undefined; - const isPinned = selectedTabNode?.isPinned(); - - React.useEffect(() => { - if (selectedTabNode && !isPinned) { - const onBodyPointerDown = (e: PointerEvent) => { - const layoutRect = layout.getDomRect(); - const x = e.clientX - layoutRect.x; - const y = e.clientY - layoutRect.y; - if (!border.getTabHeaderRect().contains(x, y) && !border.getContentRect().contains(x, y)) { - layout.doAction(Actions.selectTab(selectedTabNode.getId())); - } - }; - document.addEventListener("pointerdown", onBodyPointerDown); - return () => document.removeEventListener("pointerdown", onBodyPointerDown); + buttons.splice( + Math.min(renderState.overflowPosition, buttons.length), + 0, + - ); - - if (layout.isSupportsPopout() && selectedTabNode.isEnablePopout()) { - const popoutTitle = layout.i18nName(I18nLabel.Popout_Tab); - buttons.push( - - ); + title={overflowTitle} + onClick={onOverflowClick} + onPointerDown={onInterceptPointerDown} + > + {overflowContent} + , + ); + } + + const selectedIndex = border.getSelected(); + const selectedTabNode = + selectedIndex !== -1 + ? (border.getChildren()[selectedIndex] as TabNode) + : undefined; + const isPinned = selectedTabNode?.isPinned(); + + React.useEffect(() => { + if (selectedTabNode && !isPinned) { + const onBodyPointerDown = (e: PointerEvent) => { + const layoutRect = layout.getDomRect(); + const x = e.clientX - layoutRect.x; + const y = e.clientY - layoutRect.y; + if ( + !border.getTabHeaderRect().contains(x, y) && + !border.getContentRect().contains(x, y) + ) { + layout.doAction(Actions.selectTab(selectedTabNode.getId())); } + }; + document.addEventListener('pointerdown', onBodyPointerDown); + return () => + document.removeEventListener('pointerdown', onBodyPointerDown); } - const toolbar = ( -
- {buttons} -
+ }, [selectedTabNode, isPinned, border, layout]); + + if (selectedTabNode !== undefined) { + const pinTitle = selectedTabNode.isPinned() + ? layout.i18nName(I18nLabel.Unpin_Tab) + : layout.i18nName(I18nLabel.Pin_Tab); + const pinIcon = selectedTabNode.isPinned() + ? typeof icons.unpin === 'function' + ? icons.unpin(selectedTabNode) + : icons.unpin + : typeof icons.pin === 'function' + ? icons.pin(selectedTabNode) + : icons.pin; + const onPinClick = (event: React.MouseEvent) => { + layout.doAction( + Actions.updateNodeAttributes(selectedTabNode.getId(), { + pinned: !selectedTabNode.isPinned(), + }), + ); + event.stopPropagation(); + }; + buttons.push( + , ); - let innerStyle = {}; - let outerStyle = {}; - const borderHeight = size - 1; - if (border.getLocation() === DockLocation.LEFT) { - innerStyle = { right: "100%", top: 0 }; - outerStyle = { width: borderHeight, overflowY: "auto" }; - } else if (border.getLocation() === DockLocation.RIGHT) { - innerStyle = { left: "100%", top: 0 }; - outerStyle = { width: borderHeight, overflowY: "auto" }; - } else { - innerStyle = { left: 0 }; - outerStyle = { height: borderHeight, overflowX: "auto" }; - } - - let miniScrollbar = undefined; - if (border.isEnableTabScrollbar()) { - miniScrollbar = ( -
- ); - } - - let leadingContainer: React.ReactNode = undefined; - if (leading) { - leadingContainer = ( -
- {leading} -
- ); + if (layout.isSupportsPopout() && selectedTabNode.isEnablePopout()) { + const popoutTitle = layout.i18nName(I18nLabel.Popout_Tab); + buttons.push( + , + ); } + } + const toolbar = ( +
+ {buttons} +
+ ); + + let innerStyle = {}; + let outerStyle = {}; + const borderHeight = size - 1; + if (border.getLocation() === DockLocation.LEFT) { + innerStyle = { right: '100%', top: 0 }; + outerStyle = { width: borderHeight, overflowY: 'auto' }; + } else if (border.getLocation() === DockLocation.RIGHT) { + innerStyle = { left: '100%', top: 0 }; + outerStyle = { width: borderHeight, overflowY: 'auto' }; + } else { + innerStyle = { left: 0 }; + outerStyle = { height: borderHeight, overflowX: 'auto' }; + } + + let miniScrollbar = undefined; + if (border.isEnableTabScrollbar()) { + miniScrollbar = ( +
+ ); + } - return ( + let leadingContainer: React.ReactNode = undefined; + if (leading) { + leadingContainer = ( +
{leading}
+ ); + } + + return ( +
+ {leadingContainer} +
- {leadingContainer} -
-
-
- {tabButtons} -
-
- {miniScrollbar} -
- {toolbar} -
- ); - +
+ {tabButtons} +
+
+ {miniScrollbar} +
+ {toolbar} +
+ ); }; diff --git a/src/view/DragContainer.tsx b/src/view/DragContainer.tsx index 8f25bf1c..7f0bc52a 100644 --- a/src/view/DragContainer.tsx +++ b/src/view/DragContainer.tsx @@ -1,32 +1,31 @@ -import * as React from "react"; -import { TabNode } from "../model/TabNode"; -import { LayoutInternal } from "./Layout"; -import { CLASSES } from "../Types"; -import { TabButtonStamp } from "./TabButtonStamp"; +import * as React from 'react'; +import { TabNode } from '../model/TabNode'; +import { LayoutInternal } from './Layout'; +import { CLASSES } from '../Types'; +import { TabButtonStamp } from './TabButtonStamp'; /** @internal */ export interface IDragContainerProps { - node: TabNode; - layout: LayoutInternal; + node: TabNode; + layout: LayoutInternal; } /** @internal */ export const DragContainer = (props: IDragContainerProps) => { - const { layout, node} = props; - const selfRef = React.useRef(null); + const { layout, node } = props; + const selfRef = React.useRef(null); - React.useEffect(()=> { - node.setTabStamp(selfRef.current); - }, [node, selfRef.current]); + React.useEffect(() => { + node.setTabStamp(selfRef.current); + }, [node, selfRef.current]); - const cm = layout.getClassName; + const cm = layout.getClassName; - const classNames = cm(CLASSES.FLEXLAYOUT__DRAG_RECT); + const classNames = cm(CLASSES.FLEXLAYOUT__DRAG_RECT); - return (
- -
- ); + return ( +
+ +
+ ); }; diff --git a/src/view/ErrorBoundary.tsx b/src/view/ErrorBoundary.tsx index ef3bc164..e69bebe8 100644 --- a/src/view/ErrorBoundary.tsx +++ b/src/view/ErrorBoundary.tsx @@ -1,53 +1,63 @@ -import * as React from "react"; -import { ErrorInfo } from "react"; -import { CLASSES } from "../Types"; +import * as React from 'react'; +import { ErrorInfo } from 'react'; +import { CLASSES } from '../Types'; /** @internal */ export interface IErrorBoundaryProps { - message: string; - retryText: string; - children: React.ReactNode; + message: string; + retryText: string; + children: React.ReactNode; } /** @internal */ export interface IErrorBoundaryState { - hasError: boolean; + hasError: boolean; } /** @internal */ -export class ErrorBoundary extends React.Component { - constructor(props: IErrorBoundaryProps) { - super(props); - this.state = { hasError: false }; - } +export class ErrorBoundary extends React.Component< + IErrorBoundaryProps, + IErrorBoundaryState +> { + constructor(props: IErrorBoundaryProps) { + super(props); + this.state = { hasError: false }; + } - static getDerivedStateFromError(error: Error) { - return { hasError: true }; - } + static getDerivedStateFromError(error: Error) { + return { hasError: true }; + } - componentDidCatch(error: Error, errorInfo: ErrorInfo) { - console.debug(error); - console.debug(errorInfo); - } + componentDidCatch(error: Error, errorInfo: ErrorInfo) { + console.debug(error); + console.debug(errorInfo); + } - retry = () => { - this.setState({ hasError: false }); - }; + retry = () => { + this.setState({ hasError: false }); + }; - render() { - if (this.state.hasError) { - return ( -
-
-
- {this.props.message} -

-
-
- -
- ); - } - - return this.props.children; + render() { + if (this.state.hasError) { + return ( +
+
+
+ {this.props.message} +

+ +

+
+
+
+ ); } + + return this.props.children; + } } diff --git a/src/view/Icons.tsx b/src/view/Icons.tsx index 0ae45e62..5036b566 100644 --- a/src/view/Icons.tsx +++ b/src/view/Icons.tsx @@ -1,105 +1,200 @@ -const style = { width: "1em", height: "1em", display: "flex", alignItems: "center" }; +const style = { + width: '1em', + height: '1em', + display: 'flex', + alignItems: 'center', +}; export const CloseIcon = () => { - return ( - - - - - ); -} + return ( + + + + + ); +}; export const MaximizeIcon = () => { - return ( - - ); -} + return ( + + + + + ); +}; export const OverflowIcon = () => { - return ( - - ); -} + return ( + + + + + ); +}; export const EdgeIcon = () => { - return ( - - ); -} + return ( + + + + ); +}; export const PopoutIcon = () => { - return ( - // - - // - // - // - - - - - - - ); -} + return ( + // + + // + // + // + + + + + + ); +}; export const RestoreIcon = () => { - return ( - - ); -} + return ( + + + + + ); +}; export const PinIcon = () => { - return ( - - - - ); -} + return ( + + + + ); +}; export const UnpinIcon = () => { - return ( - - - - - - ); -} + return ( + + + + + + ); +}; export const AsterickIcon = () => { - return ( - - ); -} + return ( + + + + ); +}; export const AddIcon = () => { - return ( - - - - - - ); -} + return ( + + + + + ); +}; export const MenuIcon = () => { - return ( - - - - ); -} - + return ( + + + + ); +}; export const SettingsIcon = (props: React.SVGProps) => { - return ( - - - - - - - ); -} \ No newline at end of file + return ( + + + + + + + ); +}; diff --git a/src/view/Layout.tsx b/src/view/Layout.tsx index 9aace520..28caf048 100755 --- a/src/view/Layout.tsx +++ b/src/view/Layout.tsx @@ -1,1314 +1,1636 @@ -import * as React from "react"; -import { createPortal } from "react-dom"; -import { createRoot } from "react-dom/client"; -import { DockLocation } from "../DockLocation"; -import { DropInfo } from "../DropInfo"; -import { I18nLabel } from "../I18nLabel"; -import { Orientation } from "../Orientation"; -import { Rect } from "../Rect"; -import { CLASSES } from "../Types"; -import { Action } from "../model/Action"; -import { Actions } from "../model/Actions"; -import { BorderNode } from "../model/BorderNode"; -import { IDraggable } from "../model/IDraggable"; -import { IJsonTabNode } from "../model/IJsonModel"; -import { Model } from "../model/Model"; -import { Node } from "../model/Node"; -import { TabNode } from "../model/TabNode"; -import { TabSetNode } from "../model/TabSetNode"; -import { BorderTab } from "./BorderTab"; -import { BorderTabSet } from "./BorderTabSet"; -import { DragContainer } from "./DragContainer"; -import { PopoutWindow } from "./PopoutWindow"; -import { AsterickIcon, CloseIcon, EdgeIcon, MaximizeIcon, OverflowIcon, PopoutIcon, RestoreIcon, PinIcon, UnpinIcon } from "./Icons"; -import { Overlay } from "./Overlay"; -import { Row } from "./Row"; -import { Tab } from "./Tab"; -import { copyInlineStyles, enablePointerOnIFrames, isDesktop, isSafari } from "./Utils"; -import { LayoutWindow } from "../model/LayoutWindow"; -import { TabButtonStamp } from "./TabButtonStamp"; -import { SizeTracker } from "./SizeTracker"; +import * as React from 'react'; +import { createPortal } from 'react-dom'; +import { createRoot } from 'react-dom/client'; +import { DockLocation } from '../DockLocation'; +import { DropInfo } from '../DropInfo'; +import { I18nLabel } from '../I18nLabel'; +import { Orientation } from '../Orientation'; +import { Rect } from '../Rect'; +import { CLASSES } from '../Types'; +import { Action } from '../model/Action'; +import { Actions } from '../model/Actions'; +import { BorderNode } from '../model/BorderNode'; +import { IDraggable } from '../model/IDraggable'; +import { IJsonTabNode } from '../model/IJsonModel'; +import { Model } from '../model/Model'; +import { Node } from '../model/Node'; +import { TabNode } from '../model/TabNode'; +import { TabSetNode } from '../model/TabSetNode'; +import { BorderTab } from './BorderTab'; +import { BorderTabSet } from './BorderTabSet'; +import { DragContainer } from './DragContainer'; +import { PopoutWindow } from './PopoutWindow'; +import { + AsterickIcon, + CloseIcon, + EdgeIcon, + MaximizeIcon, + OverflowIcon, + PopoutIcon, + RestoreIcon, + PinIcon, + UnpinIcon, +} from './Icons'; +import { Overlay } from './Overlay'; +import { Row } from './Row'; +import { Tab } from './Tab'; +import { + copyInlineStyles, + enablePointerOnIFrames, + isDesktop, + isSafari, +} from './Utils'; +import { LayoutWindow } from '../model/LayoutWindow'; +import { TabButtonStamp } from './TabButtonStamp'; +import { SizeTracker } from './SizeTracker'; export interface ILayoutProps { - /** the model for this layout */ - model: Model; - /** factory function for creating the tab components */ - factory: (node: TabNode) => React.ReactNode; - /** sets a top level class name on popout windows */ - popoutClassName?: string; - /** object mapping keys among close, maximize, restore, more, popout to React nodes to use in place of the default icons, can alternatively return functions for creating the React nodes */ - icons?: IIcons; - /** function called whenever the layout generates an action to update the model (allows for intercepting actions before they are dispatched to the model, for example, asking the user to confirm a tab close.) Returning undefined from the function will halt the action, otherwise return the action to continue */ - onAction?: (action: Action) => Action | undefined; - /** function called when rendering a tab, allows leading (icon), content section, buttons and name used in overflow menu to be customized */ - onRenderTab?: ( - node: TabNode, - renderValues: ITabRenderValues, // change the values in this object as required - ) => void; - /** function called when rendering a tabset, allows header and buttons to be customized */ - onRenderTabSet?: ( - tabSetNode: TabSetNode | BorderNode, - renderValues: ITabSetRenderValues, // change the values in this object as required - ) => void; - /** function called when model has changed */ - onModelChange?: (model: Model, action: Action) => void; - /** function called when an external object (not a tab) gets dragged onto the layout, with a single dragenter argument. Should return either undefined to reject the drag/drop or an object with keys dragText, jsonDrop, to create a tab via drag (similar to a call to addTabToTabSet). Function onDropis passed the added tabNodeand thedrop DragEvent`, unless the drag was canceled. */ - onExternalDrag?: (event: React.DragEvent) => undefined | { - json: any, - onDrop?: (node?: Node, event?: React.DragEvent) => void - }; - /** function called with default css class name, return value is class name that will be used. Mainly for use with css modules. */ - classNameMapper?: (defaultClassName: string) => string; - /** function called for each I18nLabel to allow user translation, currently used for tab and tabset move messages, return undefined to use default values */ - i18nMapper?: (id: I18nLabel, param?: string) => string | undefined; - /** if left undefined will do simple check based on userAgent */ - supportsPopout?: boolean | undefined; - /** URL of popout window relative to origin, defaults to popout.html */ - popoutURL?: string | undefined; - /** boolean value, defaults to false, resize tabs as splitters are dragged. Warning: this can cause resizing to become choppy when tabs are slow to draw */ - realtimeResize?: boolean | undefined; - /** callback for rendering the drag rectangles */ - onRenderDragRect?: DragRectRenderCallback; - /** callback for handling context actions on tabs and tabsets */ - onContextMenu?: NodeMouseEvent; - /** callback for handling mouse clicks on tabs and tabsets with alt, meta, shift keys, also handles center mouse clicks */ - onAuxMouseClick?: NodeMouseEvent; - /** callback for handling the display of the tab overflow menu */ - onShowOverflowMenu?: ShowOverflowMenuCallback; - /** callback for rendering a placeholder when a tabset is empty */ - onTabSetPlaceHolder?: TabSetPlaceHolderCallback; - /** Name given to popout windows, defaults to 'Popout Window' */ - popoutWindowName?: string; + /** the model for this layout */ + model: Model; + /** factory function for creating the tab components */ + factory: (node: TabNode) => React.ReactNode; + /** sets a top level class name on popout windows */ + popoutClassName?: string; + /** object mapping keys among close, maximize, restore, more, popout to React nodes to use in place of the default icons, can alternatively return functions for creating the React nodes */ + icons?: IIcons; + /** function called whenever the layout generates an action to update the model (allows for intercepting actions before they are dispatched to the model, for example, asking the user to confirm a tab close.) Returning undefined from the function will halt the action, otherwise return the action to continue */ + onAction?: (action: Action) => Action | undefined; + /** function called when rendering a tab, allows leading (icon), content section, buttons and name used in overflow menu to be customized */ + onRenderTab?: ( + node: TabNode, + renderValues: ITabRenderValues, // change the values in this object as required + ) => void; + /** function called when rendering a tabset, allows header and buttons to be customized */ + onRenderTabSet?: ( + tabSetNode: TabSetNode | BorderNode, + renderValues: ITabSetRenderValues, // change the values in this object as required + ) => void; + /** function called when model has changed */ + onModelChange?: (model: Model, action: Action) => void; + /** function called when an external object (not a tab) gets dragged onto the layout, with a single dragenter argument. Should return either undefined to reject the drag/drop or an object with keys dragText, jsonDrop, to create a tab via drag (similar to a call to addTabToTabSet). Function onDropis passed the added tabNodeand thedrop DragEvent`, unless the drag was canceled. */ + onExternalDrag?: (event: React.DragEvent) => + | undefined + | { + json: any; + onDrop?: (node?: Node, event?: React.DragEvent) => void; + }; + /** function called with default css class name, return value is class name that will be used. Mainly for use with css modules. */ + classNameMapper?: (defaultClassName: string) => string; + /** function called for each I18nLabel to allow user translation, currently used for tab and tabset move messages, return undefined to use default values */ + i18nMapper?: (id: I18nLabel, param?: string) => string | undefined; + /** if left undefined will do simple check based on userAgent */ + supportsPopout?: boolean | undefined; + /** URL of popout window relative to origin, defaults to popout.html */ + popoutURL?: string | undefined; + /** boolean value, defaults to false, resize tabs as splitters are dragged. Warning: this can cause resizing to become choppy when tabs are slow to draw */ + realtimeResize?: boolean | undefined; + /** callback for rendering the drag rectangles */ + onRenderDragRect?: DragRectRenderCallback; + /** callback for handling context actions on tabs and tabsets */ + onContextMenu?: NodeMouseEvent; + /** callback for handling mouse clicks on tabs and tabsets with alt, meta, shift keys, also handles center mouse clicks */ + onAuxMouseClick?: NodeMouseEvent; + /** callback for handling the display of the tab overflow menu */ + onShowOverflowMenu?: ShowOverflowMenuCallback; + /** callback for rendering a placeholder when a tabset is empty */ + onTabSetPlaceHolder?: TabSetPlaceHolderCallback; + /** Name given to popout windows, defaults to 'Popout Window' */ + popoutWindowName?: string; } /** * A React component that hosts a multi-tabbed layout */ export class Layout extends React.Component { - /** @internal */ - private selfRef: React.RefObject; - /** @internal */ - private revision: number; // so LayoutInternal knows this is a parent render (used for optimization) - - /** @internal */ - constructor(props: ILayoutProps) { - super(props); - this.selfRef = React.createRef(); - this.revision = 0; - } - - /** re-render the layout */ - redraw() { - this.selfRef.current!.redraw("parent " + this.revision); - } - - /** - * Adds a new tab to the given tabset - * @param tabsetId the id of the tabset where the new tab will be added - * @param json the json for the new tab node - * @returns the added tab node or undefined - */ - addTabToTabSet(tabsetId: string, json: IJsonTabNode): TabNode | undefined { - return this.selfRef.current!.addTabToTabSet(tabsetId, json); - } - - /** - * Adds a new tab by dragging an item to the drop location, must be called from within an HTML - * drag start handler. You can use the setDragComponent() method to set the drag image before calling this - * method. - * @param event the drag start event - * @param json the json for the new tab node - * @param onDrop a callback to call when the drag is complete - */ - addTabWithDragAndDrop(event: DragEvent, json: IJsonTabNode, onDrop?: (node?: Node, event?: React.DragEvent) => void) { - this.selfRef.current!.addTabWithDragAndDrop(event, json, onDrop); - } - - /** - * Move a tab/tabset using drag and drop, must be called from within an HTML - * drag start handler - * @param event the drag start event - * @param node the tab or tabset to drag - */ - moveTabWithDragAndDrop(event: DragEvent, node: (TabNode | TabSetNode)) { - this.selfRef.current!.moveTabWithDragAndDrop(event, node); - } - - /** - * Adds a new tab to the active tabset (if there is one) - * @param json the json for the new tab node - * @returns the added tab node or undefined - */ - addTabToActiveTabSet(json: IJsonTabNode): TabNode | undefined { - return this.selfRef.current!.addTabToActiveTabSet(json); - } - - /** - * Sets the drag image from a react component for a drag event - * @param event the drag event - * @param component the react component to be used for the drag image - * @param x the x position of the drag cursor on the image - * @param y the x position of the drag cursor on the image - */ - setDragComponent(event: DragEvent, component: React.ReactNode, x: number, y: number) { - this.selfRef.current!.setDragComponent(event, component, x, y); - } - - /** Get the root div element of the layout */ - getRootDiv() { - return this.selfRef.current!.getRootDiv(); - } - - /** @internal */ - render() { - return () - } + /** @internal */ + private selfRef: React.RefObject; + /** @internal */ + private revision: number; // so LayoutInternal knows this is a parent render (used for optimization) + + /** @internal */ + constructor(props: ILayoutProps) { + super(props); + this.selfRef = React.createRef(); + this.revision = 0; + } + + /** re-render the layout */ + redraw() { + this.selfRef.current!.redraw('parent ' + this.revision); + } + + /** + * Adds a new tab to the given tabset + * @param tabsetId the id of the tabset where the new tab will be added + * @param json the json for the new tab node + * @returns the added tab node or undefined + */ + addTabToTabSet(tabsetId: string, json: IJsonTabNode): TabNode | undefined { + return this.selfRef.current!.addTabToTabSet(tabsetId, json); + } + + /** + * Adds a new tab by dragging an item to the drop location, must be called from within an HTML + * drag start handler. You can use the setDragComponent() method to set the drag image before calling this + * method. + * @param event the drag start event + * @param json the json for the new tab node + * @param onDrop a callback to call when the drag is complete + */ + addTabWithDragAndDrop( + event: DragEvent, + json: IJsonTabNode, + onDrop?: (node?: Node, event?: React.DragEvent) => void, + ) { + this.selfRef.current!.addTabWithDragAndDrop(event, json, onDrop); + } + + /** + * Move a tab/tabset using drag and drop, must be called from within an HTML + * drag start handler + * @param event the drag start event + * @param node the tab or tabset to drag + */ + moveTabWithDragAndDrop(event: DragEvent, node: TabNode | TabSetNode) { + this.selfRef.current!.moveTabWithDragAndDrop(event, node); + } + + /** + * Adds a new tab to the active tabset (if there is one) + * @param json the json for the new tab node + * @returns the added tab node or undefined + */ + addTabToActiveTabSet(json: IJsonTabNode): TabNode | undefined { + return this.selfRef.current!.addTabToActiveTabSet(json); + } + + /** + * Sets the drag image from a react component for a drag event + * @param event the drag event + * @param component the react component to be used for the drag image + * @param x the x position of the drag cursor on the image + * @param y the x position of the drag cursor on the image + */ + setDragComponent( + event: DragEvent, + component: React.ReactNode, + x: number, + y: number, + ) { + this.selfRef.current!.setDragComponent(event, component, x, y); + } + + /** Get the root div element of the layout */ + getRootDiv() { + return this.selfRef.current!.getRootDiv(); + } + + /** @internal */ + render() { + return ( + + ); + } } /** @internal */ interface ILayoutInternalProps extends ILayoutProps { - renderRevision: number; + renderRevision: number; - // used only for popout windows: - windowId?: string; - mainLayout?: LayoutInternal; + // used only for popout windows: + windowId?: string; + mainLayout?: LayoutInternal; } /** @internal */ interface ILayoutInternalState { - rect: Rect; - editingTab?: TabNode; - portal?: React.ReactPortal; - showEdges: boolean; - showOverlay: boolean; - calculatedBorderBarSize: number; - layoutRevision: number; - forceRevision: number; - showHiddenBorder: DockLocation; + rect: Rect; + editingTab?: TabNode; + portal?: React.ReactPortal; + showEdges: boolean; + showOverlay: boolean; + calculatedBorderBarSize: number; + layoutRevision: number; + forceRevision: number; + showHiddenBorder: DockLocation; } /** @internal */ -export class LayoutInternal extends React.Component { - public static dragState: DragState | undefined = undefined; - - private selfRef: React.RefObject; - private moveablesRef: React.RefObject; - private findBorderBarSizeRef: React.RefObject; - private mainRef: React.RefObject; - private previousModel?: Model; - private orderedTabIds: string[]; - private orderedTabMoveableIds: string[]; - private moveableElementMap = new Map(); - private dropInfo: DropInfo | undefined; - private outlineDiv?: HTMLElement; - private currentDocument?: Document; - private currentWindow?: Window; - private supportsPopout: boolean; - private popoutURL: string; - private icons: IIcons; - private resizeObserver?: ResizeObserver; - - private dragEnterCount: number = 0; - private dragging: boolean = false; - private windowId: string; - private layoutWindow: LayoutWindow; - private mainLayout: LayoutInternal; - private isMainWindow: boolean; - private isDraggingOverWindow: boolean; - private styleObserver: MutationObserver | undefined; - private popoutWindowName: string; - // private renderCount: any; - - constructor(props: ILayoutInternalProps) { - super(props); - - this.orderedTabIds = []; - this.orderedTabMoveableIds = []; - this.selfRef = React.createRef(); - this.moveablesRef = React.createRef(); - this.mainRef = React.createRef(); - this.findBorderBarSizeRef = React.createRef(); - - this.supportsPopout = props.supportsPopout !== undefined ? props.supportsPopout : defaultSupportsPopout; - this.popoutURL = props.popoutURL ? props.popoutURL : "popout.html"; - this.icons = { ...defaultIcons, ...props.icons }; - this.windowId = props.windowId ? props.windowId : Model.MAIN_WINDOW_ID; - this.mainLayout = this.props.mainLayout ? this.props.mainLayout : this; - this.isDraggingOverWindow = false; - this.layoutWindow = this.props.model.getwindowsMap().get(this.windowId)!; - this.layoutWindow.layout = this; - this.popoutWindowName = this.props.popoutWindowName || "Popout Window"; - // this.renderCount = 0; - - this.state = { - rect: Rect.empty(), - editingTab: undefined, - showEdges: false, - showOverlay: false, - calculatedBorderBarSize: 29, - layoutRevision: 0, - forceRevision: 0, - showHiddenBorder: DockLocation.CENTER - }; - - this.isMainWindow = this.windowId === Model.MAIN_WINDOW_ID; - } - - componentDidMount() { - this.updateRect(); +export class LayoutInternal extends React.Component< + ILayoutInternalProps, + ILayoutInternalState +> { + public static dragState: DragState | undefined = undefined; + + private selfRef: React.RefObject; + private moveablesRef: React.RefObject; + private findBorderBarSizeRef: React.RefObject; + private mainRef: React.RefObject; + private previousModel?: Model; + private orderedTabIds: string[]; + private orderedTabMoveableIds: string[]; + private moveableElementMap = new Map(); + private dropInfo: DropInfo | undefined; + private outlineDiv?: HTMLElement; + private currentDocument?: Document; + private currentWindow?: Window; + private supportsPopout: boolean; + private popoutURL: string; + private icons: IIcons; + private resizeObserver?: ResizeObserver; + + private dragEnterCount: number = 0; + private dragging: boolean = false; + private windowId: string; + private layoutWindow: LayoutWindow; + private mainLayout: LayoutInternal; + private isMainWindow: boolean; + private isDraggingOverWindow: boolean; + private styleObserver: MutationObserver | undefined; + private popoutWindowName: string; + // private renderCount: any; + + constructor(props: ILayoutInternalProps) { + super(props); + + this.orderedTabIds = []; + this.orderedTabMoveableIds = []; + this.selfRef = React.createRef(); + this.moveablesRef = React.createRef(); + this.mainRef = React.createRef(); + this.findBorderBarSizeRef = React.createRef(); + + this.supportsPopout = + props.supportsPopout !== undefined + ? props.supportsPopout + : defaultSupportsPopout; + this.popoutURL = props.popoutURL ? props.popoutURL : 'popout.html'; + this.icons = { ...defaultIcons, ...props.icons }; + this.windowId = props.windowId ? props.windowId : Model.MAIN_WINDOW_ID; + this.mainLayout = this.props.mainLayout ? this.props.mainLayout : this; + this.isDraggingOverWindow = false; + this.layoutWindow = this.props.model.getwindowsMap().get(this.windowId)!; + this.layoutWindow.layout = this; + this.popoutWindowName = this.props.popoutWindowName || 'Popout Window'; + // this.renderCount = 0; + + this.state = { + rect: Rect.empty(), + editingTab: undefined, + showEdges: false, + showOverlay: false, + calculatedBorderBarSize: 29, + layoutRevision: 0, + forceRevision: 0, + showHiddenBorder: DockLocation.CENTER, + }; - this.currentDocument = (this.selfRef.current as HTMLElement).ownerDocument; - this.currentWindow = this.currentDocument.defaultView!; + this.isMainWindow = this.windowId === Model.MAIN_WINDOW_ID; + } - this.layoutWindow.window = this.currentWindow; - this.layoutWindow.toScreenRectFunction = (r) => this.getScreenRect(r); + componentDidMount() { + this.updateRect(); - this.resizeObserver = new ResizeObserver(entries => { - requestAnimationFrame(() => { - this.updateRect(); - }); - }); - if (this.selfRef.current) { - this.resizeObserver.observe(this.selfRef.current); - } + this.currentDocument = (this.selfRef.current as HTMLElement).ownerDocument; + this.currentWindow = this.currentDocument.defaultView!; - if (this.isMainWindow) { - this.props.model.addChangeListener(this.onModelChange); - this.updateLayoutMetrics(); - } else { - // since resizeObserver doesn't always work as expected when observing element in another document - this.currentWindow.addEventListener("resize", () => { - this.updateRect(); - }); + this.layoutWindow.window = this.currentWindow; + this.layoutWindow.toScreenRectFunction = (r) => this.getScreenRect(r); - const sourceElement = this.props.mainLayout!.getRootDiv()!; - const targetElement = this.selfRef.current!; + this.resizeObserver = new ResizeObserver((entries) => { + requestAnimationFrame(() => { + this.updateRect(); + }); + }); + if (this.selfRef.current) { + this.resizeObserver.observe(this.selfRef.current); + } + + if (this.isMainWindow) { + this.props.model.addChangeListener(this.onModelChange); + this.updateLayoutMetrics(); + } else { + // since resizeObserver doesn't always work as expected when observing element in another document + this.currentWindow.addEventListener('resize', () => { + this.updateRect(); + }); - copyInlineStyles(sourceElement, targetElement); + const sourceElement = this.props.mainLayout!.getRootDiv()!; + const targetElement = this.selfRef.current!; - this.styleObserver = new MutationObserver(() => { - const changed = copyInlineStyles(sourceElement, targetElement); - if (changed) { - this.redraw("mutation observer"); - } - }); + copyInlineStyles(sourceElement, targetElement); - // Observe changes to the source element's style attribute - this.styleObserver.observe(sourceElement, { attributeFilter: ['style'] }); + this.styleObserver = new MutationObserver(() => { + const changed = copyInlineStyles(sourceElement, targetElement); + if (changed) { + this.redraw('mutation observer'); } + }); - // allow tabs to overlay when hidden - document.addEventListener('visibilitychange', () => { - for (const [_, layoutWindow] of this.props.model.getwindowsMap()) { - const layout = layoutWindow.layout; - if (layout) { - this.redraw("visibility change"); - } - } - }); + // Observe changes to the source element's style attribute + this.styleObserver.observe(sourceElement, { attributeFilter: ['style'] }); } - componentDidUpdate() { - this.currentDocument = (this.selfRef.current as HTMLElement).ownerDocument; - this.currentWindow = this.currentDocument.defaultView!; - if (this.isMainWindow) { - if (this.props.model !== this.previousModel) { - if (this.previousModel !== undefined) { - this.previousModel.removeChangeListener(this.onModelChange); // stop listening to old model - } - this.props.model.getwindowsMap().get(this.windowId)!.layout = this; - this.props.model.addChangeListener(this.onModelChange); - this.layoutWindow = this.props.model.getwindowsMap().get(this.windowId)!; - this.layoutWindow.layout = this; - this.layoutWindow.toScreenRectFunction = (r) => this.getScreenRect(r); - this.previousModel = this.props.model; - this.tidyMoveablesMap(); - } - - this.updateLayoutMetrics(); + // allow tabs to overlay when hidden + document.addEventListener('visibilitychange', () => { + for (const [_, layoutWindow] of this.props.model.getwindowsMap()) { + const layout = layoutWindow.layout; + if (layout) { + this.redraw('visibility change'); } - } - - componentWillUnmount() { - if (this.selfRef.current) { - this.resizeObserver?.unobserve(this.selfRef.current); - } - - if (this.isMainWindow) { - this.props.model.removeChangeListener(this.onModelChange); - } - this.styleObserver?.disconnect(); - } - - render() { - // console.log("render", this.windowId, this.state.revision, this.renderCount++); - // first render will be used to find the size (via selfRef) - if (!this.selfRef.current) { - return ( -
-
- {this.renderMetricsElements()} -
- ); + } + }); + } + + componentDidUpdate() { + this.currentDocument = (this.selfRef.current as HTMLElement).ownerDocument; + this.currentWindow = this.currentDocument.defaultView!; + if (this.isMainWindow) { + if (this.props.model !== this.previousModel) { + if (this.previousModel !== undefined) { + this.previousModel.removeChangeListener(this.onModelChange); // stop listening to old model } - - const model = this.props.model; - model.getRoot(this.windowId).calcMinMaxSize(); - model.getRoot(this.windowId).setPaths(""); - model.getBorderSet().setPaths(); - - const inner = this.renderLayout(); - const outer = this.renderBorders(inner); - - const tabs = this.renderTabs(); - const reorderedTabs = this.reorderComponents(tabs, this.orderedTabIds); - - let floatingWindows = null; - let reorderedTabMoveables = null; - let tabStamps = null; - let metricElements = null; - - if (this.isMainWindow) { - floatingWindows = this.renderWindows(); - metricElements = this.renderMetricsElements(); - const tabMoveables = this.renderTabMoveables(); - reorderedTabMoveables = this.reorderComponents(tabMoveables, this.orderedTabMoveableIds); - tabStamps =
- {this.renderTabStamps()} -
; + this.props.model.getwindowsMap().get(this.windowId)!.layout = this; + this.props.model.addChangeListener(this.onModelChange); + this.layoutWindow = this.props.model + .getwindowsMap() + .get(this.windowId)!; + this.layoutWindow.layout = this; + this.layoutWindow.toScreenRectFunction = (r) => this.getScreenRect(r); + this.previousModel = this.props.model; + this.tidyMoveablesMap(); + } + + this.updateLayoutMetrics(); + } + } + + componentWillUnmount() { + if (this.selfRef.current) { + this.resizeObserver?.unobserve(this.selfRef.current); + } + + if (this.isMainWindow) { + this.props.model.removeChangeListener(this.onModelChange); + } + this.styleObserver?.disconnect(); + } + + render() { + // console.log("render", this.windowId, this.state.revision, this.renderCount++); + // first render will be used to find the size (via selfRef) + if (!this.selfRef.current) { + return ( +
+
+ {this.renderMetricsElements()} +
+ ); + } + + const model = this.props.model; + model.getRoot(this.windowId).calcMinMaxSize(); + model.getRoot(this.windowId).setPaths(''); + model.getBorderSet().setPaths(); + + const inner = this.renderLayout(); + const outer = this.renderBorders(inner); + + const tabs = this.renderTabs(); + const reorderedTabs = this.reorderComponents(tabs, this.orderedTabIds); + + let floatingWindows = null; + let reorderedTabMoveables = null; + let tabStamps = null; + let metricElements = null; + + if (this.isMainWindow) { + floatingWindows = this.renderWindows(); + metricElements = this.renderMetricsElements(); + const tabMoveables = this.renderTabMoveables(); + reorderedTabMoveables = this.reorderComponents( + tabMoveables, + this.orderedTabMoveableIds, + ); + tabStamps = ( +
+ {this.renderTabStamps()} +
+ ); + } + + return ( +
+
+ {metricElements} + + {outer} + {reorderedTabs} + {reorderedTabMoveables} + {tabStamps} + {this.state.portal} + {floatingWindows} +
+ ); + } + + renderBorders(inner: React.ReactNode) { + const classMain = this.getClassName(CLASSES.FLEXLAYOUT__LAYOUT_MAIN); + const borders = this.props.model.getBorderSet().getBorderMap(); + if (this.isMainWindow && borders.size > 0) { + inner = ( +
+ {inner} +
+ ); + const borderSetComponents = new Map(); + const borderSetContentComponents = new Map< + DockLocation, + React.ReactNode + >(); + for (const [_, location] of DockLocation.values) { + const border = borders.get(location); + const showBorder = + border && + border.isShowing() && + (!border.isAutoHide() || + (border.isAutoHide() && + (border.getChildren().length > 0 || + this.state.showHiddenBorder === location))); + if (showBorder) { + borderSetComponents.set( + location, + , + ); + borderSetContentComponents.set( + location, + , + ); } - + } + + const classBorderOuter = this.getClassName( + CLASSES.FLEXLAYOUT__LAYOUT_BORDER_CONTAINER, + ); + const classBorderInner = this.getClassName( + CLASSES.FLEXLAYOUT__LAYOUT_BORDER_CONTAINER_INNER, + ); + + if (this.props.model.getBorderSet().getLayoutHorizontal()) { + const innerWithBorderTabs = ( +
+ {borderSetContentComponents.get(DockLocation.TOP)} +
+ {borderSetContentComponents.get(DockLocation.LEFT)} + {inner} + {borderSetContentComponents.get(DockLocation.RIGHT)} +
+ {borderSetContentComponents.get(DockLocation.BOTTOM)} +
+ ); return ( +
+ {borderSetComponents.get(DockLocation.TOP)} +
+ {borderSetComponents.get(DockLocation.LEFT)} + {innerWithBorderTabs} + {borderSetComponents.get(DockLocation.RIGHT)} +
+ {borderSetComponents.get(DockLocation.BOTTOM)} +
+ ); + } else { + const innerWithBorderTabs = ( +
+ {borderSetContentComponents.get(DockLocation.LEFT)}
-
- {metricElements} - - {outer} - {reorderedTabs} - {reorderedTabMoveables} - {tabStamps} - {this.state.portal} - {floatingWindows} + {borderSetContentComponents.get(DockLocation.TOP)} + {inner} + {borderSetContentComponents.get(DockLocation.BOTTOM)}
+ {borderSetContentComponents.get(DockLocation.RIGHT)} +
); - } - renderBorders( - inner: React.ReactNode - ) { - const classMain = this.getClassName(CLASSES.FLEXLAYOUT__LAYOUT_MAIN); - const borders = this.props.model.getBorderSet().getBorderMap() - if (this.isMainWindow && borders.size > 0) { - inner = ( -
- {inner} -
); - const borderSetComponents = new Map(); - const borderSetContentComponents = new Map(); - for (const [_, location] of DockLocation.values) { - const border = borders.get(location); - const showBorder = border && border.isShowing() && ( - !border.isAutoHide() || - (border.isAutoHide() && (border.getChildren().length > 0 || this.state.showHiddenBorder === location))); - if (showBorder) { - borderSetComponents.set(location, ); - borderSetContentComponents.set(location, ); - } - } - - const classBorderOuter = this.getClassName(CLASSES.FLEXLAYOUT__LAYOUT_BORDER_CONTAINER); - const classBorderInner = this.getClassName(CLASSES.FLEXLAYOUT__LAYOUT_BORDER_CONTAINER_INNER); - - if (this.props.model.getBorderSet().getLayoutHorizontal()) { - const innerWithBorderTabs = ( -
- {borderSetContentComponents.get(DockLocation.TOP)} -
- {borderSetContentComponents.get(DockLocation.LEFT)} - {inner} - {borderSetContentComponents.get(DockLocation.RIGHT)} -
- {borderSetContentComponents.get(DockLocation.BOTTOM)} -
- ); - return ( -
- {borderSetComponents.get(DockLocation.TOP)} -
- {borderSetComponents.get(DockLocation.LEFT)} - {innerWithBorderTabs} - {borderSetComponents.get(DockLocation.RIGHT)} -
- {borderSetComponents.get(DockLocation.BOTTOM)} -
- ); - } else { - const innerWithBorderTabs = ( -
- {borderSetContentComponents.get(DockLocation.LEFT)} -
- {borderSetContentComponents.get(DockLocation.TOP)} - {inner} - {borderSetContentComponents.get(DockLocation.BOTTOM)} -
- {borderSetContentComponents.get(DockLocation.RIGHT)} -
- ); - - return ( -
- {borderSetComponents.get(DockLocation.LEFT)} -
- {borderSetComponents.get(DockLocation.TOP)} - {innerWithBorderTabs} - {borderSetComponents.get(DockLocation.BOTTOM)} -
- {borderSetComponents.get(DockLocation.RIGHT)} -
- ); - } - - } else { // no borders - return ( -
- {inner} -
- ); - } - } - - renderLayout() { return ( - <> - - {this.renderEdgeIndicators()} - - ); - } - - renderEdgeIndicators() { - const edges: React.ReactNode[] = []; - const arrowIcon = this.icons.edgeArrow; - if (this.state.showEdges) { - const r = this.props.model.getRoot(this.windowId).getRect(); - const length = edgeRectLength; - const width = edgeRectWidth; - const offset = edgeRectLength / 2; - const className = this.getClassName(CLASSES.FLEXLAYOUT__EDGE_RECT); - const radius = 50; - edges.push(
-
- {arrowIcon} -
-
); - edges.push(
-
- {arrowIcon} -
-
); - edges.push(
-
- {arrowIcon} -
-
); - edges.push(
-
- {arrowIcon} -
-
); - } - - return edges; - } - - renderWindows() { - const floatingWindows: React.ReactNode[] = []; - if (this.supportsPopout) { - const windows = this.props.model.getwindowsMap(); - let i = 1; - for (const [windowId, layoutWindow] of windows) { - - if (windowId !== Model.MAIN_WINDOW_ID) { - floatingWindows.push( - - - , element, key)); - - child.setRendered(renderTab); - } - } - }); - - return tabMoveables; - } - - renderTabStamps() { - const tabStamps: React.ReactNode[] = []; - - this.props.model.visitNodes((node) => { - if (node instanceof TabNode) { - const child = node as TabNode; - - // what the tab should look like when dragged (since images need to have been loaded before drag image can be taken) - tabStamps.push() - } - }); - - return tabStamps; - } - - renderTabs() { - const tabs = new Map(); - this.props.model.visitWindowNodes(this.windowId, (node) => { - if (node instanceof TabNode) { - const child = node as TabNode; - const selected = child.isSelected(); - const path = child.getPath(); - - const renderTab = child.isRendered() || selected || !child.isEnableRenderOnDemand(); - - if (renderTab) { - tabs.set(child.getId(), ( - - )); - } - } - }); - return tabs; - } - - renderMetricsElements() { - return ( -
- FindBorderBarSize +
+ {borderSetComponents.get(DockLocation.LEFT)} +
+ {borderSetComponents.get(DockLocation.TOP)} + {innerWithBorderTabs} + {borderSetComponents.get(DockLocation.BOTTOM)}
+ {borderSetComponents.get(DockLocation.RIGHT)} +
); - } - - checkForBorderToShow(x: number, y: number) { - const r = this.getBoundingClientRect(this.mainRef.current!); - const c = r.getCenter(); - const margin = edgeRectWidth; - const offset = edgeRectLength / 2; - - let overEdge = false; - if (this.props.model.isEnableEdgeDock() && this.state.showHiddenBorder === DockLocation.CENTER) { - if ((y > c.y - offset && y < c.y + offset) || - (x > c.x - offset && x < c.x + offset)) { - overEdge = true; - } - } - - let location = DockLocation.CENTER; - if (!overEdge) { - if (x <= r.x + margin) { - location = DockLocation.LEFT; - } else if (x >= r.getRight() - margin) { - location = DockLocation.RIGHT; - } else if (y <= r.y + margin) { - location = DockLocation.TOP; - } else if (y >= r.getBottom() - margin) { - location = DockLocation.BOTTOM; - } - } - - if (location !== this.state.showHiddenBorder) { - this.setState({ showHiddenBorder: location }); - } - } - - updateLayoutMetrics = () => { - if (this.findBorderBarSizeRef.current) { - const borderBarSize = this.findBorderBarSizeRef.current.getBoundingClientRect().height; - if (borderBarSize !== this.state.calculatedBorderBarSize) { - this.setState({ calculatedBorderBarSize: borderBarSize }); - } - } - }; - - tidyMoveablesMap() { - // console.log("tidyMoveablesMap"); - const tabs = new Map(); - this.props.model.visitNodes((node, _) => { - if (node instanceof TabNode) { - tabs.set(node.getId(), node); - } - }); - - for (const [nodeId, element] of this.moveableElementMap) { - if (!tabs.has(nodeId)) { - // console.log("delete", nodeId); - element.remove(); // remove from dom - this.moveableElementMap.delete(nodeId); // remove map entry - } - } - } - - reorderComponents(components: Map, ids: string[]) { - const nextIds: string[] = []; - const nextIdsSet = new Set(); - - let reordered: React.ReactNode[] = []; - // Keep any previous tabs in the same DOM order as before, removing any that have been deleted - for (const id of ids) { - if (components.get(id)) { - nextIds.push(id); - nextIdsSet.add(id); - } - } - ids.splice(0, ids.length, ...nextIds); - - // Add tabs that have been added to the DOM - for (const [id, _] of components) { - if (!nextIdsSet.has(id)) { - ids.push(id); - } + } + } else { + // no borders + return ( +
+ {inner} +
+ ); + } + } + + renderLayout() { + return ( + <> + + {this.renderEdgeIndicators()} + + ); + } + + renderEdgeIndicators() { + const edges: React.ReactNode[] = []; + const arrowIcon = this.icons.edgeArrow; + if (this.state.showEdges) { + const r = this.props.model.getRoot(this.windowId).getRect(); + const length = edgeRectLength; + const width = edgeRectWidth; + const offset = edgeRectLength / 2; + const className = this.getClassName(CLASSES.FLEXLAYOUT__EDGE_RECT); + const radius = 50; + edges.push( +
+
{arrowIcon}
+
, + ); + edges.push( +
+
{arrowIcon}
+
, + ); + edges.push( +
+
{arrowIcon}
+
, + ); + edges.push( +
+
{arrowIcon}
+
, + ); + } + + return edges; + } + + renderWindows() { + const floatingWindows: React.ReactNode[] = []; + if (this.supportsPopout) { + const windows = this.props.model.getwindowsMap(); + let i = 1; + for (const [windowId, layoutWindow] of windows) { + if (windowId !== Model.MAIN_WINDOW_ID) { + floatingWindows.push( + , + element, + key, + ), + ); + + child.setRendered(renderTab); } - }; + } + }); - redraw(type?: string) { - // console.log("redraw", this.windowId, type); - this.mainLayout.setState((state, props) => { return { forceRevision: state.forceRevision + 1 } }); - } + return tabMoveables; + } - redrawInternal(type: string) { - // console.log("redrawInternal", this.windowId, type); - this.mainLayout.setState((state, props) => { return { layoutRevision: state.layoutRevision + 1 } }); - } + renderTabStamps() { + const tabStamps: React.ReactNode[] = []; - doAction(action: Action): Node | undefined { - if (this.props.onAction !== undefined) { - const outcome = this.props.onAction(action); - if (outcome !== undefined) { - return this.props.model.doAction(outcome); - } - return undefined; - } else { - return this.props.model.doAction(action); - } - } + this.props.model.visitNodes((node) => { + if (node instanceof TabNode) { + const child = node as TabNode; - updateRect = () => { - if (this.selfRef.current) { - const rect = Rect.fromDomRect(this.selfRef.current.getBoundingClientRect()); - if (!rect.equals(this.state.rect) && rect.width !== 0 && rect.height !== 0) { - // console.log("updateRect", rect.floor()); - this.setState({ rect }); - if (this.windowId !== Model.MAIN_WINDOW_ID) { - this.redrawInternal("rect updated"); - } - } + // what the tab should look like when dragged (since images need to have been loaded before drag image can be taken) + tabStamps.push( + , + ); + } + }); + + return tabStamps; + } + + renderTabs() { + const tabs = new Map(); + this.props.model.visitWindowNodes(this.windowId, (node) => { + if (node instanceof TabNode) { + const child = node as TabNode; + const selected = child.isSelected(); + const path = child.getPath(); + + const renderTab = + child.isRendered() || selected || !child.isEnableRenderOnDemand(); + + if (renderTab) { + tabs.set( + child.getId(), + , + ); } - }; - - getBoundingClientRect(div: HTMLElement): Rect { - const layoutRect = this.getDomRect(); - if (layoutRect) { - return Rect.getBoundingClientRect(div).relativeTo(layoutRect); + } + }); + return tabs; + } + + renderMetricsElements() { + return ( +
+ FindBorderBarSize +
+ ); + } + + checkForBorderToShow(x: number, y: number) { + const r = this.getBoundingClientRect(this.mainRef.current!); + const c = r.getCenter(); + const margin = edgeRectWidth; + const offset = edgeRectLength / 2; + + let overEdge = false; + if ( + this.props.model.isEnableEdgeDock() && + this.state.showHiddenBorder === DockLocation.CENTER + ) { + if ( + (y > c.y - offset && y < c.y + offset) || + (x > c.x - offset && x < c.x + offset) + ) { + overEdge = true; + } + } + + let location = DockLocation.CENTER; + if (!overEdge) { + if (x <= r.x + margin) { + location = DockLocation.LEFT; + } else if (x >= r.getRight() - margin) { + location = DockLocation.RIGHT; + } else if (y <= r.y + margin) { + location = DockLocation.TOP; + } else if (y >= r.getBottom() - margin) { + location = DockLocation.BOTTOM; + } + } + + if (location !== this.state.showHiddenBorder) { + this.setState({ showHiddenBorder: location }); + } + } + + updateLayoutMetrics = () => { + if (this.findBorderBarSizeRef.current) { + const borderBarSize = + this.findBorderBarSizeRef.current.getBoundingClientRect().height; + if (borderBarSize !== this.state.calculatedBorderBarSize) { + this.setState({ calculatedBorderBarSize: borderBarSize }); + } + } + }; + + tidyMoveablesMap() { + // console.log("tidyMoveablesMap"); + const tabs = new Map(); + this.props.model.visitNodes((node, _) => { + if (node instanceof TabNode) { + tabs.set(node.getId(), node); + } + }); + + for (const [nodeId, element] of this.moveableElementMap) { + if (!tabs.has(nodeId)) { + // console.log("delete", nodeId); + element.remove(); // remove from dom + this.moveableElementMap.delete(nodeId); // remove map entry + } + } + } + + reorderComponents(components: Map, ids: string[]) { + const nextIds: string[] = []; + const nextIdsSet = new Set(); + + let reordered: React.ReactNode[] = []; + // Keep any previous tabs in the same DOM order as before, removing any that have been deleted + for (const id of ids) { + if (components.get(id)) { + nextIds.push(id); + nextIdsSet.add(id); + } + } + ids.splice(0, ids.length, ...nextIds); + + // Add tabs that have been added to the DOM + for (const [id, _] of components) { + if (!nextIdsSet.has(id)) { + ids.push(id); + } + } + + reordered = ids.map((id) => { + return components.get(id); + }); + + return reordered; + } + + onModelChange = (action: Action) => { + this.redrawInternal('model change'); + if (this.props.onModelChange) { + this.props.onModelChange(this.props.model, action); + } + }; + + redraw(type?: string) { + // console.log("redraw", this.windowId, type); + this.mainLayout.setState((state, props) => { + return { forceRevision: state.forceRevision + 1 }; + }); + } + + redrawInternal(type: string) { + // console.log("redrawInternal", this.windowId, type); + this.mainLayout.setState((state, props) => { + return { layoutRevision: state.layoutRevision + 1 }; + }); + } + + doAction(action: Action): Node | undefined { + if (this.props.onAction !== undefined) { + const outcome = this.props.onAction(action); + if (outcome !== undefined) { + return this.props.model.doAction(outcome); + } + return undefined; + } else { + return this.props.model.doAction(action); + } + } + + updateRect = () => { + if (this.selfRef.current) { + const rect = Rect.fromDomRect( + this.selfRef.current.getBoundingClientRect(), + ); + if ( + !rect.equals(this.state.rect) && + rect.width !== 0 && + rect.height !== 0 + ) { + // console.log("updateRect", rect.floor()); + this.setState({ rect }); + if (this.windowId !== Model.MAIN_WINDOW_ID) { + this.redrawInternal('rect updated'); } - return Rect.empty(); + } } - - getMoveableContainer() { - return this.moveablesRef.current; + }; + + getBoundingClientRect(div: HTMLElement): Rect { + const layoutRect = this.getDomRect(); + if (layoutRect) { + return Rect.getBoundingClientRect(div).relativeTo(layoutRect); + } + return Rect.empty(); + } + + getMoveableContainer() { + return this.moveablesRef.current; + } + + getMoveableElement(id: string) { + let moveableElement = this.moveableElementMap.get(id); + if (moveableElement === undefined) { + moveableElement = document.createElement('div'); + this.moveablesRef.current!.appendChild(moveableElement); + moveableElement.className = CLASSES.FLEXLAYOUT__TAB_MOVEABLE; + this.moveableElementMap.set(id, moveableElement); + } + return moveableElement; + } + + getMainLayout() { + return this.mainLayout; + } + + getClassName = (defaultClassName: string) => { + if (this.props.classNameMapper === undefined) { + return defaultClassName; + } else { + return this.props.classNameMapper(defaultClassName); + } + }; + + getCurrentDocument() { + return this.currentDocument; + } + + getDomRect() { + // must get on demand, since page may have scrolled + if (this.selfRef.current) { + return Rect.fromDomRect(this.selfRef.current.getBoundingClientRect()); + } else { + return Rect.empty(); + } + } + + getWindowId() { + return this.windowId; + } + + getRootDiv() { + return this.selfRef.current; + } + + getMainElement() { + return this.mainRef.current; + } + + getFactory() { + return this.props.factory; + } + + isSupportsPopout() { + return this.supportsPopout; + } + + isRealtimeResize() { + return this.props.realtimeResize ?? false; + } + + getPopoutURL() { + return this.popoutURL; + } + + setEditingTab(tabNode?: TabNode) { + this.setState({ editingTab: tabNode }); + } + + getEditingTab() { + return this.state.editingTab; + } + + getModel() { + return this.props.model; + } + + onCloseWindow = (windowLayout: LayoutWindow) => { + this.doAction(Actions.closeWindow(windowLayout.windowId)); + }; + + onSetWindow = (windowLayout: LayoutWindow, window: Window) => {}; + + getScreenRect(inRect: Rect) { + const rect = inRect.clone(); + const layoutRect = this.getDomRect(); + // Note: outerHeight can be less than innerHeight when window is zoomed, so cannot use + // const navHeight = Math.min(65, this.currentWindow!.outerHeight - this.currentWindow!.innerHeight); + // const navWidth = Math.min(65, this.currentWindow!.outerWidth - this.currentWindow!.innerWidth); + const navHeight = 60; + const navWidth = 2; + // console.log(rect.y, this.currentWindow!.screenX,layoutRect.y); + rect.x = + this.currentWindow!.screenX + + this.currentWindow!.scrollX + + navWidth / 2 + + layoutRect.x + + rect.x; + rect.y = + this.currentWindow!.screenY + + this.currentWindow!.scrollY + + (navHeight - navWidth / 2) + + layoutRect.y + + rect.y; + rect.height += navHeight; + rect.width += navWidth; + return rect; + } + + addTabToTabSet(tabsetId: string, json: IJsonTabNode): TabNode | undefined { + const tabsetNode = this.props.model.getNodeById(tabsetId); + if (tabsetNode !== undefined) { + const node = this.doAction( + Actions.addNode(json, tabsetId, DockLocation.CENTER, -1), + ); + return node as TabNode; + } + return undefined; + } + + addTabToActiveTabSet(json: IJsonTabNode): TabNode | undefined { + const tabsetNode = this.props.model.getActiveTabset(this.windowId); + if (tabsetNode !== undefined) { + const node = this.doAction( + Actions.addNode(json, tabsetNode.getId(), DockLocation.CENTER, -1), + ); + return node as TabNode; + } + return undefined; + } + + showControlInPortal = (control: React.ReactNode, element: HTMLElement) => { + const portal = createPortal(control, element) as React.ReactPortal; + this.setState({ portal }); + }; + + hideControlInPortal = () => { + this.setState({ portal: undefined }); + }; + + getIcons = () => { + return this.icons; + }; + + maximize(tabsetNode: TabSetNode) { + this.doAction( + Actions.maximizeToggle(tabsetNode.getId(), this.getWindowId()), + ); + } + + customizeTab(tabNode: TabNode, renderValues: ITabRenderValues) { + if (this.props.onRenderTab) { + this.props.onRenderTab(tabNode, renderValues); + } + } + + customizeTabSet( + tabSetNode: TabSetNode | BorderNode, + renderValues: ITabSetRenderValues, + ) { + if (this.props.onRenderTabSet) { + this.props.onRenderTabSet(tabSetNode, renderValues); + } + } + + i18nName(id: I18nLabel, param?: string) { + let message; + if (this.props.i18nMapper) { + message = this.props.i18nMapper(id, param); + } + if (message === undefined) { + message = id + (param === undefined ? '' : param); + } + return message; + } + + getShowOverflowMenu() { + return this.props.onShowOverflowMenu; + } + + getTabSetPlaceHolderCallback() { + return this.props.onTabSetPlaceHolder; + } + + showContextMenu( + node: TabNode | TabSetNode | BorderNode, + event: React.MouseEvent, + ) { + if (this.props.onContextMenu) { + this.props.onContextMenu(node, event); } + } - getMoveableElement(id: string) { - let moveableElement = this.moveableElementMap.get(id); - if (moveableElement === undefined) { - moveableElement = document.createElement("div"); - this.moveablesRef.current!.appendChild(moveableElement); - moveableElement.className = CLASSES.FLEXLAYOUT__TAB_MOVEABLE; - this.moveableElementMap.set(id, moveableElement); + auxMouseClick( + node: TabNode | TabSetNode | BorderNode, + event: React.MouseEvent, + ) { + if (this.props.onAuxMouseClick) { + this.props.onAuxMouseClick(node, event); + } + } + + public showOverlay(show: boolean) { + this.setState({ showOverlay: show }); + enablePointerOnIFrames(!show, this.currentDocument!); + } + + // *************************** Start Drag Drop ************************************* + + addTabWithDragAndDrop( + event: DragEvent, + json: IJsonTabNode, + onDrop?: (node?: Node, event?: React.DragEvent) => void, + ) { + const tempNode = TabNode.fromJson(json, this.props.model, false); + LayoutInternal.dragState = new DragState( + this.mainLayout, + DragSource.Add, + tempNode, + json, + onDrop, + ); + } + + moveTabWithDragAndDrop(event: DragEvent, node: TabNode | TabSetNode) { + this.setDragNode(event, node); + } + + public setDragNode = (event: DragEvent, node: Node & IDraggable) => { + LayoutInternal.dragState = new DragState( + this.mainLayout, + DragSource.Internal, + node, + undefined, + undefined, + ); + // Note: can only set (very) limited types on android! so cannot set json + // Note: must set text/plain for android to allow drag, + // so just set a simple message indicating its a flexlayout drag (this is not used anywhere else) + event.dataTransfer!.setData('text/plain', '--flexlayout--'); + event.dataTransfer!.effectAllowed = 'copyMove'; + event.dataTransfer!.dropEffect = 'move'; + + this.dragEnterCount = 0; + + if (node instanceof TabSetNode) { + let rendered = false; + let content = this.i18nName(I18nLabel.Move_Tabset); + if (node.getChildren().length > 0) { + content = this.i18nName(I18nLabel.Move_Tabs).replace( + '?', + String(node.getChildren().length), + ); + } + if (this.props.onRenderDragRect) { + const dragComponent = this.props.onRenderDragRect( + content, + node, + undefined, + ); + if (dragComponent) { + this.setDragComponent(event, dragComponent, 10, 10); + rendered = true; } - return moveableElement; - } - - getMainLayout() { - return this.mainLayout; - } - - getClassName = (defaultClassName: string) => { - if (this.props.classNameMapper === undefined) { - return defaultClassName; - } else { - return this.props.classNameMapper(defaultClassName); + } + if (!rendered) { + this.setDragComponent(event, content, 10, 10); + } + } else { + const element = event.target as HTMLElement; + const rect = element.getBoundingClientRect(); + const offsetX = event.clientX - rect.left; + const offsetY = event.clientY - rect.top; + const parentNode = node?.getParent(); + const isInVerticalBorder = + parentNode instanceof BorderNode && + (parentNode as BorderNode).getOrientation() === Orientation.HORZ; + const x = isInVerticalBorder ? 10 : offsetX; + const y = isInVerticalBorder ? 10 : offsetY; + + let rendered = false; + if (this.props.onRenderDragRect) { + const content = ( + + ); + const dragComponent = this.props.onRenderDragRect( + content, + node, + undefined, + ); + if (dragComponent) { + this.setDragComponent(event, dragComponent, x, y); + rendered = true; } - }; - - getCurrentDocument() { - return this.currentDocument; - } - - getDomRect() { - // must get on demand, since page may have scrolled - if (this.selfRef.current) { - return Rect.fromDomRect(this.selfRef.current.getBoundingClientRect()); + } + if (!rendered) { + if (isSafari()) { + // safari doesnt render the offscreen tabstamps + this.setDragComponent( + event, + , + x, + y, + ); } else { - return Rect.empty(); + event.dataTransfer!.setDragImage( + (node as TabNode).getTabStamp()!, + x, + y, + ); } - } - - getWindowId() { - return this.windowId; - } - - getRootDiv() { - return this.selfRef.current; - } - - getMainElement() { - return this.mainRef.current; - } - - getFactory() { - return this.props.factory; - } - - isSupportsPopout() { - return this.supportsPopout; - } - - isRealtimeResize() { - return this.props.realtimeResize ?? false; - } - - getPopoutURL() { - return this.popoutURL; - } - - setEditingTab(tabNode?: TabNode) { - this.setState({ editingTab: tabNode }); - } - - getEditingTab() { - return this.state.editingTab; - } - - getModel() { - return this.props.model; - } - - onCloseWindow = (windowLayout: LayoutWindow) => { - this.doAction(Actions.closeWindow(windowLayout.windowId)); - }; - - onSetWindow = (windowLayout: LayoutWindow, window: Window) => { - }; - - getScreenRect(inRect: Rect) { - const rect = inRect.clone(); - const layoutRect = this.getDomRect(); - // Note: outerHeight can be less than innerHeight when window is zoomed, so cannot use - // const navHeight = Math.min(65, this.currentWindow!.outerHeight - this.currentWindow!.innerHeight); - // const navWidth = Math.min(65, this.currentWindow!.outerWidth - this.currentWindow!.innerWidth); - const navHeight = 60; - const navWidth = 2; - // console.log(rect.y, this.currentWindow!.screenX,layoutRect.y); - rect.x = this.currentWindow!.screenX + this.currentWindow!.scrollX + navWidth / 2 + layoutRect.x + rect.x; - rect.y = this.currentWindow!.screenY + this.currentWindow!.scrollY + (navHeight - navWidth / 2) + layoutRect.y + rect.y; - rect.height += navHeight; - rect.width += navWidth; - return rect; - } - - addTabToTabSet(tabsetId: string, json: IJsonTabNode): TabNode | undefined { - const tabsetNode = this.props.model.getNodeById(tabsetId); - if (tabsetNode !== undefined) { - const node = this.doAction(Actions.addNode(json, tabsetId, DockLocation.CENTER, -1)); - return node as TabNode; - } - return undefined; - } - - addTabToActiveTabSet(json: IJsonTabNode): TabNode | undefined { - const tabsetNode = this.props.model.getActiveTabset(this.windowId); - if (tabsetNode !== undefined) { - const node = this.doAction(Actions.addNode(json, tabsetNode.getId(), DockLocation.CENTER, -1)); - return node as TabNode; - } - return undefined; - } - - showControlInPortal = (control: React.ReactNode, element: HTMLElement) => { - const portal = createPortal(control, element) as React.ReactPortal; - this.setState({ portal }); - }; - - hideControlInPortal = () => { - this.setState({ portal: undefined }); - }; - - getIcons = () => { - return this.icons; - }; - - maximize(tabsetNode: TabSetNode) { - this.doAction(Actions.maximizeToggle(tabsetNode.getId(), this.getWindowId())); - } - - customizeTab( - tabNode: TabNode, - renderValues: ITabRenderValues, - ) { - if (this.props.onRenderTab) { - this.props.onRenderTab(tabNode, renderValues); - } - } - - customizeTabSet( - tabSetNode: TabSetNode | BorderNode, - renderValues: ITabSetRenderValues, - ) { - if (this.props.onRenderTabSet) { - this.props.onRenderTabSet(tabSetNode, renderValues); - } - } - - i18nName(id: I18nLabel, param?: string) { - let message; - if (this.props.i18nMapper) { - message = this.props.i18nMapper(id, param); + } + } + }; + + public setDragComponent( + event: DragEvent, + component: React.ReactNode, + x: number, + y: number, + ) { + const dragElement = ( +
+ {component} +
+ ); + + const tempDiv = this.currentDocument!.createElement('div'); + tempDiv.setAttribute('data-layout-path', '/drag-rectangle'); + tempDiv.style.position = 'absolute'; + tempDiv.style.left = '-10000px'; + tempDiv.style.top = '-10000px'; + this.currentDocument!.body.appendChild(tempDiv); + createRoot(tempDiv).render(dragElement); + + event.dataTransfer!.setDragImage(tempDiv, x, y); + setTimeout(() => { + this.currentDocument!.body.removeChild(tempDiv!); + }, 0); + } + + setDraggingOverWindow(overWindow: boolean) { + // console.log("setDraggingOverWindow", overWindow); + if (this.isDraggingOverWindow !== overWindow) { + if (this.outlineDiv) { + this.outlineDiv!.style.visibility = overWindow ? 'hidden' : 'visible'; + } + + if (overWindow) { + this.setState({ showEdges: false }); + } else { + // add edge indicators + if (this.props.model.getMaximizedTabset(this.windowId) === undefined) { + this.setState({ showEdges: this.props.model.isEnableEdgeDock() }); } - return message; - } + } - getShowOverflowMenu() { - return this.props.onShowOverflowMenu; + this.isDraggingOverWindow = overWindow; } + } - getTabSetPlaceHolderCallback() { - return this.props.onTabSetPlaceHolder; + onDragEnterRaw = (event: React.DragEvent) => { + this.dragEnterCount++; + if (this.dragEnterCount === 1) { + this.onDragEnter(event); } + }; - showContextMenu(node: TabNode | TabSetNode | BorderNode, event: React.MouseEvent) { - if (this.props.onContextMenu) { - this.props.onContextMenu(node, event); - } + onDragLeaveRaw = (event: React.DragEvent) => { + this.dragEnterCount--; + if (this.dragEnterCount === 0) { + this.onDragLeave(event); } + }; - auxMouseClick(node: TabNode | TabSetNode | BorderNode, event: React.MouseEvent) { - if (this.props.onAuxMouseClick) { - this.props.onAuxMouseClick(node, event); - } + clearDragMain() { + // console.log("clear drag main"); + LayoutInternal.dragState = undefined; + if (this.windowId === Model.MAIN_WINDOW_ID) { + this.isDraggingOverWindow = false; } - - public showOverlay(show: boolean) { - this.setState({ showOverlay: show }); - enablePointerOnIFrames(!show, this.currentDocument!); - } - - - - // *************************** Start Drag Drop ************************************* - - addTabWithDragAndDrop(event: DragEvent, json: IJsonTabNode, onDrop?: (node?: Node, event?: React.DragEvent) => void) { - const tempNode = TabNode.fromJson(json, this.props.model, false); - LayoutInternal.dragState = new DragState(this.mainLayout, DragSource.Add, tempNode, json, onDrop); + for (const [, layoutWindow] of this.props.model.getwindowsMap()) { + // console.log(layoutWindow); + layoutWindow.layout!.clearDragLocal(); } + } - moveTabWithDragAndDrop(event: DragEvent, node: (TabNode | TabSetNode)) { - this.setDragNode(event, node); + clearDragLocal() { + // console.log("clear drag local", this.windowId); + this.setState({ showEdges: false }); + this.showOverlay(false); + this.dragEnterCount = 0; + this.dragging = false; + if (this.outlineDiv) { + this.selfRef.current!.removeChild(this.outlineDiv); + this.outlineDiv = undefined; } + } - public setDragNode = (event: DragEvent, node: Node & IDraggable) => { - LayoutInternal.dragState = new DragState(this.mainLayout, DragSource.Internal, node, undefined, undefined); - // Note: can only set (very) limited types on android! so cannot set json - // Note: must set text/plain for android to allow drag, - // so just set a simple message indicating its a flexlayout drag (this is not used anywhere else) - event.dataTransfer!.setData('text/plain', "--flexlayout--"); - event.dataTransfer!.effectAllowed = "copyMove"; - event.dataTransfer!.dropEffect = "move"; - - this.dragEnterCount = 0; - - if (node instanceof TabSetNode) { - let rendered = false; - let content = this.i18nName(I18nLabel.Move_Tabset); - if (node.getChildren().length > 0) { - content = this.i18nName(I18nLabel.Move_Tabs).replace("?", String(node.getChildren().length)); - } - if (this.props.onRenderDragRect) { - const dragComponent = this.props.onRenderDragRect(content, node, undefined); - if (dragComponent) { - this.setDragComponent(event, dragComponent, 10, 10); - rendered = true; - } - } - if (!rendered) { - this.setDragComponent(event, content, 10, 10); - } - } else { - const element = event.target as HTMLElement; - const rect = element.getBoundingClientRect(); - const offsetX = event.clientX - rect.left; - const offsetY = event.clientY - rect.top; - const parentNode = node?.getParent(); - const isInVerticalBorder = parentNode instanceof BorderNode && (parentNode as BorderNode).getOrientation() === Orientation.HORZ; - const x = isInVerticalBorder ? 10 : offsetX; - const y = isInVerticalBorder ? 10 : offsetY; - - let rendered = false; - if (this.props.onRenderDragRect) { - const content = ; - const dragComponent = this.props.onRenderDragRect(content, node, undefined); - if (dragComponent) { - this.setDragComponent(event, dragComponent, x, y); - rendered = true; - } - } - if (!rendered) { - if (isSafari()) { // safari doesnt render the offscreen tabstamps - this.setDragComponent(event, , x, y); - } else { - event.dataTransfer!.setDragImage((node as TabNode).getTabStamp()!, x, y); - } - } - } - }; - + onDragEnter = (event: React.DragEvent) => { + // console.log("onDragEnter", this.windowId, this.dragEnterCount); - - public setDragComponent(event: DragEvent, component: React.ReactNode, x: number, y: number) { - const dragElement = ( -
- {component} -
+ if (!LayoutInternal.dragState && this.props.onExternalDrag) { + // not internal dragging + const externalDrag = this.props.onExternalDrag!(event); + if (externalDrag) { + const tempNode = TabNode.fromJson( + externalDrag.json, + this.props.model, + false, ); - - const tempDiv = this.currentDocument!.createElement('div'); - tempDiv.setAttribute("data-layout-path", "/drag-rectangle"); - tempDiv.style.position = "absolute"; - tempDiv.style.left = "-10000px"; - tempDiv.style.top = "-10000px"; - this.currentDocument!.body.appendChild(tempDiv); - createRoot(tempDiv).render(dragElement); - - event.dataTransfer!.setDragImage(tempDiv, x, y); - setTimeout(() => { - this.currentDocument!.body.removeChild(tempDiv!); - }, 0); - } - - setDraggingOverWindow(overWindow: boolean) { - // console.log("setDraggingOverWindow", overWindow); - if (this.isDraggingOverWindow !== overWindow) { - if (this.outlineDiv) { - this.outlineDiv!.style.visibility = overWindow ? "hidden" : "visible"; - } - - if (overWindow) { - this.setState({ showEdges: false }); - } else { - // add edge indicators - if (this.props.model.getMaximizedTabset(this.windowId) === undefined) { - this.setState({ showEdges: this.props.model.isEnableEdgeDock() }); - } - } - - this.isDraggingOverWindow = overWindow; - } - } - - onDragEnterRaw = (event: React.DragEvent) => { - this.dragEnterCount++; - if (this.dragEnterCount === 1) { - this.onDragEnter(event); - } - } - - onDragLeaveRaw = (event: React.DragEvent) => { - this.dragEnterCount--; - if (this.dragEnterCount === 0) { - this.onDragLeave(event); - } - } - - clearDragMain() { - // console.log("clear drag main"); - LayoutInternal.dragState = undefined; - if (this.windowId === Model.MAIN_WINDOW_ID) { - this.isDraggingOverWindow = false; - } - for (const [, layoutWindow] of this.props.model.getwindowsMap()) { - // console.log(layoutWindow); - layoutWindow.layout!.clearDragLocal(); - } - } - - clearDragLocal() { - // console.log("clear drag local", this.windowId); - this.setState({ showEdges: false }); - this.showOverlay(false); - this.dragEnterCount = 0; - this.dragging = false; + LayoutInternal.dragState = new DragState( + this.mainLayout, + DragSource.External, + tempNode, + externalDrag.json, + externalDrag.onDrop, + ); + } + } + + if (LayoutInternal.dragState) { + if ( + this.windowId !== Model.MAIN_WINDOW_ID && + LayoutInternal.dragState.mainLayout === this.mainLayout + ) { + LayoutInternal.dragState.mainLayout.setDraggingOverWindow(true); + } + + if (LayoutInternal.dragState.mainLayout !== this.mainLayout) { + return; // drag not by this layout or its popouts + } + + event.preventDefault(); + + this.dropInfo = undefined; + const rootdiv = this.selfRef.current; + this.outlineDiv = this.currentDocument!.createElement('div'); + this.outlineDiv.className = this.getClassName( + CLASSES.FLEXLAYOUT__OUTLINE_RECT, + ); + this.outlineDiv.style.visibility = 'hidden'; + const speed = this.props.model.getAttribute('tabDragSpeed') as number; + this.outlineDiv.style.transition = `top ${speed}s, left ${speed}s, width ${speed}s, height ${speed}s`; + + rootdiv!.appendChild(this.outlineDiv); + + this.dragging = true; + this.showOverlay(true); + // add edge indicators + if ( + !this.isDraggingOverWindow && + this.props.model.getMaximizedTabset(this.windowId) === undefined + ) { + this.setState({ showEdges: this.props.model.isEnableEdgeDock() }); + } + + const clientRect = this.selfRef.current!.getBoundingClientRect()!; + const r = new Rect( + event.clientX - clientRect.left, + event.clientY - clientRect.top, + 1, + 1, + ); + r.positionElement(this.outlineDiv); + } + }; + + onDragOver = (event: React.DragEvent) => { + if (this.dragging && !this.isDraggingOverWindow) { + // console.log("onDragOver"); + + event.preventDefault(); + const clientRect = this.selfRef.current?.getBoundingClientRect(); + const pos = { + x: event.clientX - (clientRect?.left ?? 0), + y: event.clientY - (clientRect?.top ?? 0), + }; + + this.checkForBorderToShow(pos.x, pos.y); + + const dropInfo = this.props.model.findDropTargetNode( + this.windowId, + LayoutInternal.dragState!.dragNode!, + pos.x, + pos.y, + ); + if (dropInfo) { + this.dropInfo = dropInfo; if (this.outlineDiv) { - this.selfRef.current!.removeChild(this.outlineDiv); - this.outlineDiv = undefined; - } - } - - onDragEnter = (event: React.DragEvent) => { - // console.log("onDragEnter", this.windowId, this.dragEnterCount); - - if (!LayoutInternal.dragState && this.props.onExternalDrag) { // not internal dragging - const externalDrag = this.props.onExternalDrag!(event); - if (externalDrag) { - const tempNode = TabNode.fromJson(externalDrag.json, this.props.model, false); - LayoutInternal.dragState = new DragState(this.mainLayout, DragSource.External, tempNode, externalDrag.json, externalDrag.onDrop); - } - } - - if (LayoutInternal.dragState) { - if (this.windowId !== Model.MAIN_WINDOW_ID && LayoutInternal.dragState.mainLayout === this.mainLayout) { - LayoutInternal.dragState.mainLayout.setDraggingOverWindow(true); - } - - if (LayoutInternal.dragState.mainLayout !== this.mainLayout) { - return; // drag not by this layout or its popouts - } - - event.preventDefault(); - - this.dropInfo = undefined; - const rootdiv = this.selfRef.current; - this.outlineDiv = this.currentDocument!.createElement("div"); - this.outlineDiv.className = this.getClassName(CLASSES.FLEXLAYOUT__OUTLINE_RECT); - this.outlineDiv.style.visibility = "hidden"; - const speed = this.props.model.getAttribute("tabDragSpeed") as number; - this.outlineDiv.style.transition = `top ${speed}s, left ${speed}s, width ${speed}s, height ${speed}s`; - - rootdiv!.appendChild(this.outlineDiv); - - this.dragging = true; - this.showOverlay(true); - // add edge indicators - if (!this.isDraggingOverWindow && this.props.model.getMaximizedTabset(this.windowId) === undefined) { - this.setState({ showEdges: this.props.model.isEnableEdgeDock() }); - } - - const clientRect = this.selfRef.current!.getBoundingClientRect()!; - const r = new Rect( - event.clientX - (clientRect.left), - event.clientY - (clientRect.top), - 1, 1 - ); - r.positionElement(this.outlineDiv); - } - } - - onDragOver = (event: React.DragEvent) => { - if (this.dragging && !this.isDraggingOverWindow) { - // console.log("onDragOver"); - - event.preventDefault(); - const clientRect = this.selfRef.current?.getBoundingClientRect(); - const pos = { - x: event.clientX - (clientRect?.left ?? 0), - y: event.clientY - (clientRect?.top ?? 0), - }; - - this.checkForBorderToShow(pos.x, pos.y); - - const dropInfo = this.props.model.findDropTargetNode(this.windowId, LayoutInternal.dragState!.dragNode!, pos.x, pos.y); - if (dropInfo) { - this.dropInfo = dropInfo; - if (this.outlineDiv) { - this.outlineDiv.className = this.getClassName(dropInfo.className); - dropInfo.rect.positionElement(this.outlineDiv); - this.outlineDiv.style.visibility = "visible"; - } - } + this.outlineDiv.className = this.getClassName(dropInfo.className); + dropInfo.rect.positionElement(this.outlineDiv); + this.outlineDiv.style.visibility = 'visible'; } - } - - onDragLeave = (event: React.DragEvent) => { - // console.log("onDragLeave", this.windowId, this.dragging); - if (this.dragging) { - if (this.windowId !== Model.MAIN_WINDOW_ID) { - LayoutInternal.dragState!.mainLayout.setDraggingOverWindow(false); - } - - this.clearDragLocal(); + } + } + }; + + onDragLeave = (event: React.DragEvent) => { + // console.log("onDragLeave", this.windowId, this.dragging); + if (this.dragging) { + if (this.windowId !== Model.MAIN_WINDOW_ID) { + LayoutInternal.dragState!.mainLayout.setDraggingOverWindow(false); + } + + this.clearDragLocal(); + } + }; + + onDrop = (event: React.DragEvent) => { + // console.log("ondrop", this.windowId, this.dragging, Layout.dragState); + + if (this.dragging) { + event.preventDefault(); + + const dragState = LayoutInternal.dragState!; + if (this.dropInfo) { + if (dragState.dragJson !== undefined) { + const newNode = this.doAction( + Actions.addNode( + dragState.dragJson, + this.dropInfo.node.getId(), + this.dropInfo.location, + this.dropInfo.index, + ), + ); + + if (dragState.fnNewNodeDropped !== undefined) { + dragState.fnNewNodeDropped(newNode, event); + } + } else if (dragState.dragNode !== undefined) { + this.doAction( + Actions.moveNode( + dragState.dragNode.getId(), + this.dropInfo.node.getId(), + this.dropInfo.location, + this.dropInfo.index, + ), + ); } - } - - onDrop = (event: React.DragEvent) => { - // console.log("ondrop", this.windowId, this.dragging, Layout.dragState); - - if (this.dragging) { - event.preventDefault(); + } - const dragState = LayoutInternal.dragState!; - if (this.dropInfo) { - if (dragState.dragJson !== undefined) { - const newNode = this.doAction(Actions.addNode(dragState.dragJson, this.dropInfo.node.getId(), this.dropInfo.location, this.dropInfo.index)); - - if (dragState.fnNewNodeDropped !== undefined) { - dragState.fnNewNodeDropped(newNode, event); - } - } else if (dragState.dragNode !== undefined) { - this.doAction(Actions.moveNode(dragState.dragNode.getId(), this.dropInfo.node.getId(), this.dropInfo.location, this.dropInfo.index)); - } - } - - this.mainLayout.clearDragMain(); - } - this.dragEnterCount = 0; // must set to zero here ref sublayouts + this.mainLayout.clearDragMain(); } + this.dragEnterCount = 0; // must set to zero here ref sublayouts + }; - // *************************** End Drag Drop ************************************* + // *************************** End Drag Drop ************************************* } declare const __VERSION__: string; export const FlexLayoutVersion = __VERSION__; export type DragRectRenderCallback = ( - content: React.ReactNode | undefined, - node?: Node, - json?: IJsonTabNode + content: React.ReactNode | undefined, + node?: Node, + json?: IJsonTabNode, ) => React.ReactNode | undefined; export type NodeMouseEvent = ( - node: TabNode | TabSetNode | BorderNode, - event: React.MouseEvent + node: TabNode | TabSetNode | BorderNode, + event: React.MouseEvent, ) => void; export type ShowOverflowMenuCallback = ( - node: TabSetNode | BorderNode, - mouseEvent: React.MouseEvent, - items: { index: number; node: TabNode }[], - onSelect: (item: { index: number; node: TabNode }) => void, + node: TabSetNode | BorderNode, + mouseEvent: React.MouseEvent, + items: { index: number; node: TabNode }[], + onSelect: (item: { index: number; node: TabNode }) => void, ) => void; export type TabSetPlaceHolderCallback = (node: TabSetNode) => React.ReactNode; export interface ITabSetRenderValues { - /** a component to be placed before the tabs */ - leading: React.ReactNode; - /** components that will be added after the tabs */ - stickyButtons: React.ReactNode[]; - /** components that will be added at the end of the tabset */ - buttons: React.ReactNode[]; - /** position to insert overflow button within [...stickyButtons, ...buttons] - * if left undefined position will be after the sticky buttons (if any) - */ - overflowPosition: number | undefined; + /** a component to be placed before the tabs */ + leading: React.ReactNode; + /** components that will be added after the tabs */ + stickyButtons: React.ReactNode[]; + /** components that will be added at the end of the tabset */ + buttons: React.ReactNode[]; + /** position to insert overflow button within [...stickyButtons, ...buttons] + * if left undefined position will be after the sticky buttons (if any) + */ + overflowPosition: number | undefined; } export interface ITabRenderValues { - /** the icon or other leading component */ - leading: React.ReactNode; - /** the main tab text/component */ - content: React.ReactNode; - /** a set of react components to add to the tab after the content */ - buttons: React.ReactNode[]; + /** the icon or other leading component */ + leading: React.ReactNode; + /** the main tab text/component */ + content: React.ReactNode; + /** a set of react components to add to the tab after the content */ + buttons: React.ReactNode[]; } export interface IIcons { - close?: (React.ReactNode | ((tabNode: TabNode) => React.ReactNode)); - closeTabset?: (React.ReactNode | ((tabSetNode: TabSetNode) => React.ReactNode)); - popout?: (React.ReactNode | ((tabNode: TabNode) => React.ReactNode)); - maximize?: (React.ReactNode | ((tabSetNode: TabSetNode) => React.ReactNode)); - restore?: (React.ReactNode | ((tabSetNode: TabSetNode) => React.ReactNode)); - more?: (React.ReactNode | ((tabSetNode: (TabSetNode | BorderNode), hiddenTabs: { node: TabNode; index: number }[]) => React.ReactNode)); - edgeArrow?: React.ReactNode; - activeTabset?: (React.ReactNode | ((tabSetNode: TabSetNode) => React.ReactNode)); - pin?: (React.ReactNode | ((tabNode: TabNode) => React.ReactNode)); - unpin?: (React.ReactNode | ((tabNode: TabNode) => React.ReactNode)); + close?: React.ReactNode | ((tabNode: TabNode) => React.ReactNode); + closeTabset?: React.ReactNode | ((tabSetNode: TabSetNode) => React.ReactNode); + popout?: React.ReactNode | ((tabNode: TabNode) => React.ReactNode); + maximize?: React.ReactNode | ((tabSetNode: TabSetNode) => React.ReactNode); + restore?: React.ReactNode | ((tabSetNode: TabSetNode) => React.ReactNode); + more?: + | React.ReactNode + | (( + tabSetNode: TabSetNode | BorderNode, + hiddenTabs: { node: TabNode; index: number }[], + ) => React.ReactNode); + edgeArrow?: React.ReactNode; + activeTabset?: + | React.ReactNode + | ((tabSetNode: TabSetNode) => React.ReactNode); + pin?: React.ReactNode | ((tabNode: TabNode) => React.ReactNode); + unpin?: React.ReactNode | ((tabNode: TabNode) => React.ReactNode); } const defaultIcons = { - close: , - closeTabset: , - popout: , - maximize: , - restore: , - more: , - edgeArrow: , - activeTabset: , - pin: , - unpin: , + close: , + closeTabset: , + popout: , + maximize: , + restore: , + more: , + edgeArrow: , + activeTabset: , + pin: , + unpin: , }; enum DragSource { - Internal = "internal", - External = "external", - Add = "add" + Internal = 'internal', + External = 'external', + Add = 'add', } /** @internal */ @@ -1321,23 +1643,27 @@ const edgeRectWidth = 10; // global layout drag state class DragState { - public readonly mainLayout: LayoutInternal; - public readonly dragSource: DragSource; - public readonly dragNode: Node & IDraggable | undefined; - public readonly dragJson: IJsonTabNode | undefined; - public readonly fnNewNodeDropped: ((node?: Node, event?: React.DragEvent) => void) | undefined; - - public constructor( - mainLayout: LayoutInternal, - dragSource: DragSource, - dragNode: Node & IDraggable | undefined, - dragJson: IJsonTabNode | undefined, - fnNewNodeDropped: ((node?: Node, event?: React.DragEvent) => void) | undefined - ) { - this.mainLayout = mainLayout; - this.dragSource = dragSource; - this.dragNode = dragNode; - this.dragJson = dragJson; - this.fnNewNodeDropped = fnNewNodeDropped; - } + public readonly mainLayout: LayoutInternal; + public readonly dragSource: DragSource; + public readonly dragNode: (Node & IDraggable) | undefined; + public readonly dragJson: IJsonTabNode | undefined; + public readonly fnNewNodeDropped: + | ((node?: Node, event?: React.DragEvent) => void) + | undefined; + + public constructor( + mainLayout: LayoutInternal, + dragSource: DragSource, + dragNode: (Node & IDraggable) | undefined, + dragJson: IJsonTabNode | undefined, + fnNewNodeDropped: + | ((node?: Node, event?: React.DragEvent) => void) + | undefined, + ) { + this.mainLayout = mainLayout; + this.dragSource = dragSource; + this.dragNode = dragNode; + this.dragJson = dragJson; + this.fnNewNodeDropped = fnNewNodeDropped; + } } diff --git a/src/view/Overlay.tsx b/src/view/Overlay.tsx index 65e7ea64..614e6b8a 100644 --- a/src/view/Overlay.tsx +++ b/src/view/Overlay.tsx @@ -1,21 +1,20 @@ -import { LayoutInternal } from "./Layout"; -import { CLASSES } from "../Types"; +import { LayoutInternal } from './Layout'; +import { CLASSES } from '../Types'; /** @internal */ export interface IOverlayProps { - layout: LayoutInternal; - show: boolean; + layout: LayoutInternal; + show: boolean; } /** @internal */ export const Overlay = (props: IOverlayProps) => { - const {layout, show} = props; + const { layout, show } = props; - return ( -
- ); -} \ No newline at end of file + return ( +
+ ); +}; diff --git a/src/view/PopoutWindow.tsx b/src/view/PopoutWindow.tsx index e7ef1c09..06d229a6 100644 --- a/src/view/PopoutWindow.tsx +++ b/src/view/PopoutWindow.tsx @@ -1,152 +1,191 @@ -import * as React from "react"; -import { createPortal } from "react-dom"; -import { CLASSES } from "../Types"; -import { LayoutInternal } from "./Layout"; -import { LayoutWindow } from "../model/LayoutWindow"; +import * as React from 'react'; +import { createPortal } from 'react-dom'; +import { CLASSES } from '../Types'; +import { LayoutInternal } from './Layout'; +import { LayoutWindow } from '../model/LayoutWindow'; /** @internal */ export interface IPopoutWindowProps { - title: string; - layout: LayoutInternal; - layoutWindow: LayoutWindow; - url: string; - onCloseWindow: (layoutWindow: LayoutWindow) => void; - onSetWindow: (layoutWindow: LayoutWindow, window: Window) => void; + title: string; + layout: LayoutInternal; + layoutWindow: LayoutWindow; + url: string; + onCloseWindow: (layoutWindow: LayoutWindow) => void; + onSetWindow: (layoutWindow: LayoutWindow, window: Window) => void; } /** @internal */ -export const PopoutWindow = (props: React.PropsWithChildren) => { - const { title, layout, layoutWindow, url, onCloseWindow, onSetWindow, children } = props; const popoutWindow = React.useRef(null); - const [content, setContent] = React.useState(undefined); - // map from main docs style -> this docs equivalent style - const styleMap = new Map(); - - React.useLayoutEffect(() => { - if (!popoutWindow.current) { // only create window once, even in strict mode - const windowId = layoutWindow.windowId; - const rect = layoutWindow.rect; - - popoutWindow.current = window.open(url, windowId, `left=${rect.x},top=${rect.y},width=${rect.width},height=${rect.height}`); - - if (popoutWindow.current) { - layoutWindow.window = popoutWindow.current; - onSetWindow(layoutWindow, popoutWindow.current); - - // listen for parent unloading to remove all popouts - window.addEventListener("beforeunload", () => { - if (popoutWindow.current) { - const closedWindow = popoutWindow.current; - popoutWindow.current = null; // need to set to null before close, since this will trigger popup window before unload... - closedWindow.close(); - } - }); - - popoutWindow.current.addEventListener("load", () => { - if (popoutWindow.current) { - popoutWindow.current.focus(); - - // note: resizeto must be before moveto in chrome otherwise the window will end up at 0,0 - popoutWindow.current.resizeTo(rect.width, rect.height); - popoutWindow.current.moveTo(rect.x, rect.y); - - const popoutDocument = popoutWindow.current.document; - popoutDocument.title = title; - const popoutContent = popoutDocument.createElement("div"); - popoutContent.className = CLASSES.FLEXLAYOUT__FLOATING_WINDOW_CONTENT; - popoutDocument.body.appendChild(popoutContent); - copyStyles(popoutDocument, styleMap).then(() => { - setContent(popoutContent); // re-render once link styles loaded - }); - - // listen for style mutations - const observer = new MutationObserver((mutationsList: any) => handleStyleMutations(mutationsList, popoutDocument, styleMap)); - observer.observe(document.head, { childList: true }); - - // listen for popout unloading (needs to be after load for safari) - popoutWindow.current.addEventListener("beforeunload", () => { - if (popoutWindow.current) { - onCloseWindow(layoutWindow); // remove the layoutWindow in the model - popoutWindow.current = null; - observer.disconnect(); - } - }); - } - }); - } else { - console.warn(`Unable to open window ${url}`); +export const PopoutWindow = ( + props: React.PropsWithChildren, +) => { + const { + title, + layout, + layoutWindow, + url, + onCloseWindow, + onSetWindow, + children, + } = props; + const popoutWindow = React.useRef(null); + const [content, setContent] = React.useState( + undefined, + ); + // map from main docs style -> this docs equivalent style + const styleMap = new Map(); + + React.useLayoutEffect(() => { + if (!popoutWindow.current) { + // only create window once, even in strict mode + const windowId = layoutWindow.windowId; + const rect = layoutWindow.rect; + + popoutWindow.current = window.open( + url, + windowId, + `left=${rect.x},top=${rect.y},width=${rect.width},height=${rect.height}`, + ); + + if (popoutWindow.current) { + layoutWindow.window = popoutWindow.current; + onSetWindow(layoutWindow, popoutWindow.current); + + // listen for parent unloading to remove all popouts + window.addEventListener('beforeunload', () => { + if (popoutWindow.current) { + const closedWindow = popoutWindow.current; + popoutWindow.current = null; // need to set to null before close, since this will trigger popup window before unload... + closedWindow.close(); + } + }); + + popoutWindow.current.addEventListener('load', () => { + if (popoutWindow.current) { + popoutWindow.current.focus(); + + // note: resizeto must be before moveto in chrome otherwise the window will end up at 0,0 + popoutWindow.current.resizeTo(rect.width, rect.height); + popoutWindow.current.moveTo(rect.x, rect.y); + + const popoutDocument = popoutWindow.current.document; + popoutDocument.title = title; + const popoutContent = popoutDocument.createElement('div'); + popoutContent.className = + CLASSES.FLEXLAYOUT__FLOATING_WINDOW_CONTENT; + popoutDocument.body.appendChild(popoutContent); + copyStyles(popoutDocument, styleMap).then(() => { + setContent(popoutContent); // re-render once link styles loaded + }); + + // listen for style mutations + const observer = new MutationObserver((mutationsList: any) => + handleStyleMutations(mutationsList, popoutDocument, styleMap), + ); + observer.observe(document.head, { childList: true }); + + // listen for popout unloading (needs to be after load for safari) + popoutWindow.current.addEventListener('beforeunload', () => { + if (popoutWindow.current) { onCloseWindow(layoutWindow); // remove the layoutWindow in the model - } - } - return () => { - // only close popoutWindow if windowId has been removed from the model (ie this was due to model change) - if (!layout.getModel().getwindowsMap().has(layoutWindow.windowId)) { - popoutWindow.current?.close(); popoutWindow.current = null; - } - } - }, []); - - if (content !== undefined) { - return createPortal(children, content!); - } else { - return null; + observer.disconnect(); + } + }); + } + }); + } else { + console.warn(`Unable to open window ${url}`); + onCloseWindow(layoutWindow); // remove the layoutWindow in the model + } } + return () => { + // only close popoutWindow if windowId has been removed from the model (ie this was due to model change) + if (!layout.getModel().getwindowsMap().has(layoutWindow.windowId)) { + popoutWindow.current?.close(); + popoutWindow.current = null; + } + }; + }, []); + + if (content !== undefined) { + return createPortal(children, content!); + } else { + return null; + } }; -function handleStyleMutations(mutationsList: any, popoutDocument: Document, styleMap: Map) { - for (const mutation of mutationsList) { - if (mutation.type === 'childList') { - for (const addition of mutation.addedNodes) { - if (addition instanceof HTMLLinkElement || addition instanceof HTMLStyleElement) { - copyStyle(popoutDocument, addition, styleMap); - } - } - for (const removal of mutation.removedNodes) { - if (removal instanceof HTMLLinkElement || removal instanceof HTMLStyleElement) { - const popoutStyle = styleMap.get(removal); - if (popoutStyle) { - popoutDocument.head.removeChild(popoutStyle); - } - } - } +function handleStyleMutations( + mutationsList: any, + popoutDocument: Document, + styleMap: Map, +) { + for (const mutation of mutationsList) { + if (mutation.type === 'childList') { + for (const addition of mutation.addedNodes) { + if ( + addition instanceof HTMLLinkElement || + addition instanceof HTMLStyleElement + ) { + copyStyle(popoutDocument, addition, styleMap); + } + } + for (const removal of mutation.removedNodes) { + if ( + removal instanceof HTMLLinkElement || + removal instanceof HTMLStyleElement + ) { + const popoutStyle = styleMap.get(removal); + if (popoutStyle) { + popoutDocument.head.removeChild(popoutStyle); + } } + } } -}; - - + } +} /** @internal */ -function copyStyles(popoutDoc: Document, styleMap: Map): Promise { - const promises: Promise[] = []; - const styleElements = document.querySelectorAll('style, link[rel="stylesheet"]') as NodeListOf - for (const element of styleElements) { - copyStyle(popoutDoc, element, styleMap, promises); - } - return Promise.all(promises); +function copyStyles( + popoutDoc: Document, + styleMap: Map, +): Promise { + const promises: Promise[] = []; + const styleElements = document.querySelectorAll( + 'style, link[rel="stylesheet"]', + ) as NodeListOf; + for (const element of styleElements) { + copyStyle(popoutDoc, element, styleMap, promises); + } + return Promise.all(promises); } /** @internal */ -function copyStyle(popoutDoc: Document, element: HTMLElement, styleMap: Map, promises?: Promise[]) { - if (element instanceof HTMLLinkElement) { - // prefer links since they will keep paths to images etc - const linkElement = element.cloneNode(true) as HTMLLinkElement; - popoutDoc.head.appendChild(linkElement); - styleMap.set(element, linkElement); - - if (promises) { - promises.push(new Promise((resolve) => { - linkElement.onload = () => resolve(true); - })); - } - } else if (element instanceof HTMLStyleElement) { - try { - const styleElement = element.cloneNode(true) as HTMLStyleElement; - popoutDoc.head.appendChild(styleElement); - styleMap.set(element, styleElement); - } catch (e) { - // can throw an exception - } +function copyStyle( + popoutDoc: Document, + element: HTMLElement, + styleMap: Map, + promises?: Promise[], +) { + if (element instanceof HTMLLinkElement) { + // prefer links since they will keep paths to images etc + const linkElement = element.cloneNode(true) as HTMLLinkElement; + popoutDoc.head.appendChild(linkElement); + styleMap.set(element, linkElement); + + if (promises) { + promises.push( + new Promise((resolve) => { + linkElement.onload = () => resolve(true); + }), + ); } + } else if (element instanceof HTMLStyleElement) { + try { + const styleElement = element.cloneNode(true) as HTMLStyleElement; + popoutDoc.head.appendChild(styleElement); + styleMap.set(element, styleElement); + } catch (e) { + // can throw an exception + } + } } - - diff --git a/src/view/PopupMenu.tsx b/src/view/PopupMenu.tsx index f6974f3f..6523285b 100644 --- a/src/view/PopupMenu.tsx +++ b/src/view/PopupMenu.tsx @@ -1,156 +1,164 @@ -import * as React from "react"; -import { TabNode } from "../model/TabNode"; -import { CLASSES } from "../Types"; -import { LayoutInternal } from "./Layout"; -import { TabButtonStamp } from "./TabButtonStamp"; -import { TabSetNode } from "../model/TabSetNode"; -import { BorderNode } from "../model/BorderNode"; -import { useEffect, useRef } from "react"; +import * as React from 'react'; +import { TabNode } from '../model/TabNode'; +import { CLASSES } from '../Types'; +import { LayoutInternal } from './Layout'; +import { TabButtonStamp } from './TabButtonStamp'; +import { TabSetNode } from '../model/TabSetNode'; +import { BorderNode } from '../model/BorderNode'; +import { useEffect, useRef } from 'react'; /** @internal */ export function showPopup( - triggerElement: Element, - parentNode: TabSetNode | BorderNode, - items: { index: number; node: TabNode }[], - onSelect: (item: { index: number; node: TabNode }) => void, - layout: LayoutInternal, + triggerElement: Element, + parentNode: TabSetNode | BorderNode, + items: { index: number; node: TabNode }[], + onSelect: (item: { index: number; node: TabNode }) => void, + layout: LayoutInternal, ) { - const layoutDiv = layout.getRootDiv(); - const classNameMapper = layout.getClassName; - const currentDocument = triggerElement.ownerDocument; - const triggerRect = triggerElement.getBoundingClientRect(); - const layoutRect = layoutDiv?.getBoundingClientRect() ?? new DOMRect(0, 0, 100, 100); - - const elm = currentDocument.createElement("div"); - elm.className = classNameMapper(CLASSES.FLEXLAYOUT__POPUP_MENU_CONTAINER); - if (triggerRect.left < layoutRect.left + layoutRect.width / 2) { - elm.style.left = triggerRect.left - layoutRect.left + "px"; - } else { - elm.style.right = layoutRect.right - triggerRect.right + "px"; - } - - if (triggerRect.top < layoutRect.top + layoutRect.height / 2) { - elm.style.top = triggerRect.top - layoutRect.top + "px"; - } else { - elm.style.bottom = layoutRect.bottom - triggerRect.bottom + "px"; - } - - layout.showOverlay(true); - + const layoutDiv = layout.getRootDiv(); + const classNameMapper = layout.getClassName; + const currentDocument = triggerElement.ownerDocument; + const triggerRect = triggerElement.getBoundingClientRect(); + const layoutRect = + layoutDiv?.getBoundingClientRect() ?? new DOMRect(0, 0, 100, 100); + + const elm = currentDocument.createElement('div'); + elm.className = classNameMapper(CLASSES.FLEXLAYOUT__POPUP_MENU_CONTAINER); + if (triggerRect.left < layoutRect.left + layoutRect.width / 2) { + elm.style.left = triggerRect.left - layoutRect.left + 'px'; + } else { + elm.style.right = layoutRect.right - triggerRect.right + 'px'; + } + + if (triggerRect.top < layoutRect.top + layoutRect.height / 2) { + elm.style.top = triggerRect.top - layoutRect.top + 'px'; + } else { + elm.style.bottom = layoutRect.bottom - triggerRect.bottom + 'px'; + } + + layout.showOverlay(true); + + if (layoutDiv) { + layoutDiv.appendChild(elm); + } + + const onHide = () => { + layout.hideControlInPortal(); + layout.showOverlay(false); if (layoutDiv) { - layoutDiv.appendChild(elm); + layoutDiv.removeChild(elm); } - - const onHide = () => { - layout.hideControlInPortal(); - layout.showOverlay(false); - if (layoutDiv) { - layoutDiv.removeChild(elm); - } - elm.removeEventListener("pointerdown", onElementPointerDown); - currentDocument.removeEventListener("pointerdown", onDocPointerDown); - }; - - const onElementPointerDown = (event: Event) => { - event.stopPropagation(); - }; - - const onDocPointerDown = (_event: Event) => { - onHide(); - }; - - elm.addEventListener("pointerdown", onElementPointerDown); - currentDocument.addEventListener("pointerdown", onDocPointerDown); - - layout.showControlInPortal(, elm); + elm.removeEventListener('pointerdown', onElementPointerDown); + currentDocument.removeEventListener('pointerdown', onDocPointerDown); + }; + + const onElementPointerDown = (event: Event) => { + event.stopPropagation(); + }; + + const onDocPointerDown = (_event: Event) => { + onHide(); + }; + + elm.addEventListener('pointerdown', onElementPointerDown); + currentDocument.addEventListener('pointerdown', onDocPointerDown); + + layout.showControlInPortal( + , + elm, + ); } /** @internal */ interface IPopupMenuProps { - parentNode: TabSetNode | BorderNode; - items: { index: number; node: TabNode }[]; - currentDocument: Document; - onHide: () => void; - onSelect: (item: { index: number; node: TabNode }) => void; - classNameMapper: (defaultClassName: string) => string; - layout: LayoutInternal; + parentNode: TabSetNode | BorderNode; + items: { index: number; node: TabNode }[]; + currentDocument: Document; + onHide: () => void; + onSelect: (item: { index: number; node: TabNode }) => void; + classNameMapper: (defaultClassName: string) => string; + layout: LayoutInternal; } /** @internal */ const PopupMenu = (props: IPopupMenuProps) => { - const { parentNode, items, onHide, onSelect, classNameMapper, layout } = props; - const divRef = useRef(null); - - useEffect(() => { - // Set focus when the component mounts - if (divRef.current) { - divRef.current.focus(); - } - }, []); - - const onItemClick = (item: { index: number; node: TabNode }, event: React.MouseEvent) => { - onSelect(item); - onHide(); - event.stopPropagation(); - }; - - const onDragStart = (event: React.DragEvent, node: TabNode) => { - event.stopPropagation(); // prevent starting a tabset drag as well - layout.setDragNode(event.nativeEvent, node as TabNode); - setTimeout(() => { - onHide(); - }, 0); - - }; - - const onDragEnd = (event: React.DragEvent) => { - layout.clearDragMain(); - }; - - const handleKeyDown = (event: React.KeyboardEvent) => { - if (event.key === "Escape") { - onHide(); - } - }; - - const itemElements = items.map((item, i) => { - let classes = classNameMapper(CLASSES.FLEXLAYOUT__POPUP_MENU_ITEM); - if (parentNode.getSelected() === item.index) { - classes += " " + classNameMapper(CLASSES.FLEXLAYOUT__POPUP_MENU_ITEM__SELECTED); - } - return ( -
onItemClick(item, event)} - draggable={true} - onDragStart={(e) => onDragStart(e, item.node)} - onDragEnd={onDragEnd} - title={item.node.getHelpText()} > - -
- ) + const { parentNode, items, onHide, onSelect, classNameMapper, layout } = + props; + const divRef = useRef(null); + + useEffect(() => { + // Set focus when the component mounts + if (divRef.current) { + divRef.current.focus(); } - ); + }, []); + + const onItemClick = ( + item: { index: number; node: TabNode }, + event: React.MouseEvent, + ) => { + onSelect(item); + onHide(); + event.stopPropagation(); + }; + + const onDragStart = (event: React.DragEvent, node: TabNode) => { + event.stopPropagation(); // prevent starting a tabset drag as well + layout.setDragNode(event.nativeEvent, node as TabNode); + setTimeout(() => { + onHide(); + }, 0); + }; + + const onDragEnd = (event: React.DragEvent) => { + layout.clearDragMain(); + }; + + const handleKeyDown = (event: React.KeyboardEvent) => { + if (event.key === 'Escape') { + onHide(); + } + }; + const itemElements = items.map((item, i) => { + let classes = classNameMapper(CLASSES.FLEXLAYOUT__POPUP_MENU_ITEM); + if (parentNode.getSelected() === item.index) { + classes += + ' ' + classNameMapper(CLASSES.FLEXLAYOUT__POPUP_MENU_ITEM__SELECTED); + } return ( -
- {itemElements} -
); +
onItemClick(item, event)} + draggable={true} + onDragStart={(e) => onDragStart(e, item.node)} + onDragEnd={onDragEnd} + title={item.node.getHelpText()} + > + +
+ ); + }); + + return ( +
+ {itemElements} +
+ ); }; diff --git a/src/view/Row.tsx b/src/view/Row.tsx index 7615ad5c..6f352bae 100644 --- a/src/view/Row.tsx +++ b/src/view/Row.tsx @@ -1,68 +1,74 @@ -import * as React from "react"; -import { RowNode } from "../model/RowNode"; -import { TabSetNode } from "../model/TabSetNode"; -import { CLASSES } from "../Types"; -import { LayoutInternal } from "./Layout"; -import { TabSet } from "./TabSet"; -import { Splitter } from "./Splitter"; -import { Orientation } from "../Orientation"; +import * as React from 'react'; +import { RowNode } from '../model/RowNode'; +import { TabSetNode } from '../model/TabSetNode'; +import { CLASSES } from '../Types'; +import { LayoutInternal } from './Layout'; +import { TabSet } from './TabSet'; +import { Splitter } from './Splitter'; +import { Orientation } from '../Orientation'; /** @internal */ export interface IRowProps { - layout: LayoutInternal; - node: RowNode; + layout: LayoutInternal; + node: RowNode; } /** @internal */ export const Row = (props: IRowProps) => { - const { layout, node } = props; - const selfRef = React.useRef(null); + const { layout, node } = props; + const selfRef = React.useRef(null); - const horizontal = node.getOrientation() === Orientation.HORZ; + const horizontal = node.getOrientation() === Orientation.HORZ; - React.useLayoutEffect(() => { - node.setRect(layout.getBoundingClientRect(selfRef.current!)); - }); + React.useLayoutEffect(() => { + node.setRect(layout.getBoundingClientRect(selfRef.current!)); + }); - const items: React.ReactNode[] = []; + const items: React.ReactNode[] = []; - let i = 0; + let i = 0; - for (const child of node.getChildren()) { - if (i > 0) { - items.push() - } - if (child instanceof RowNode) { - items.push(); - } else if (child instanceof TabSetNode) { - items.push(); - } - i++; + for (const child of node.getChildren()) { + if (i > 0) { + items.push( + , + ); } - - const style: Record = { - flexGrow: Math.max(1, node.getWeight()*1000), // NOTE: flex-grow cannot have values < 1 otherwise will not fill parent, need to normalize - minWidth: node.getMinWidth(), - minHeight: node.getMinHeight(), - maxWidth: node.getMaxWidth(), - maxHeight: node.getMaxHeight(), - }; - - if (horizontal) { - style.flexDirection = "row"; - } else { - style.flexDirection = "column"; + if (child instanceof RowNode) { + items.push(); + } else if (child instanceof TabSetNode) { + items.push(); } + i++; + } - return ( -
- {items} -
- ); -}; + const style: Record = { + flexGrow: Math.max(1, node.getWeight() * 1000), // NOTE: flex-grow cannot have values < 1 otherwise will not fill parent, need to normalize + minWidth: node.getMinWidth(), + minHeight: node.getMinHeight(), + maxWidth: node.getMaxWidth(), + maxHeight: node.getMaxHeight(), + }; + if (horizontal) { + style.flexDirection = 'row'; + } else { + style.flexDirection = 'column'; + } + return ( +
+ {items} +
+ ); +}; diff --git a/src/view/SizeTracker.tsx b/src/view/SizeTracker.tsx index 2a9dc925..5957f0e3 100644 --- a/src/view/SizeTracker.tsx +++ b/src/view/SizeTracker.tsx @@ -1,35 +1,39 @@ -import * as React from "react"; -import { Rect } from "../Rect"; -import { ErrorBoundary } from "./ErrorBoundary"; -import { I18nLabel } from "../I18nLabel"; -import { LayoutInternal } from "./Layout"; -import { TabNode } from "../model/TabNode"; +import * as React from 'react'; +import { Rect } from '../Rect'; +import { ErrorBoundary } from './ErrorBoundary'; +import { I18nLabel } from '../I18nLabel'; +import { LayoutInternal } from './Layout'; +import { TabNode } from '../model/TabNode'; export interface ISizeTrackerProps { - layout: LayoutInternal, - node: TabNode, - rect: Rect; - visible: boolean; - forceRevision: number; - tabsRevision: number; + layout: LayoutInternal; + node: TabNode; + rect: Rect; + visible: boolean; + forceRevision: number; + tabsRevision: number; } export const SizeTracker = React.memo(({ layout, node }: ISizeTrackerProps) => { - return ( - - {layout.props.factory(node)} - ); + return ( + + {layout.props.factory(node)} + + ); }, arePropsEqual); // only re-render if visible && (size changed or forceRevision changed or tabsRevision changed) -function arePropsEqual(prevProps: ISizeTrackerProps, nextProps: ISizeTrackerProps) { - const reRender = nextProps.visible && - (!prevProps.rect.equalSize(nextProps.rect) || - prevProps.forceRevision !== nextProps.forceRevision || - prevProps.tabsRevision !== nextProps.tabsRevision - ); - return !reRender; +function arePropsEqual( + prevProps: ISizeTrackerProps, + nextProps: ISizeTrackerProps, +) { + const reRender = + nextProps.visible && + (!prevProps.rect.equalSize(nextProps.rect) || + prevProps.forceRevision !== nextProps.forceRevision || + prevProps.tabsRevision !== nextProps.tabsRevision); + return !reRender; } - diff --git a/src/view/Splitter.tsx b/src/view/Splitter.tsx index 2e002e08..a5bb6d7c 100755 --- a/src/view/Splitter.tsx +++ b/src/view/Splitter.tsx @@ -1,262 +1,302 @@ -import * as React from "react"; -import { Actions } from "../model/Actions"; -import { BorderNode } from "../model/BorderNode"; -import { RowNode } from "../model/RowNode"; -import { Orientation } from "../Orientation"; -import { CLASSES } from "../Types"; -import { LayoutInternal } from "./Layout"; -import { enablePointerOnIFrames, isDesktop, startDrag } from "./Utils"; -import { Rect } from "../Rect"; +import * as React from 'react'; +import { Actions } from '../model/Actions'; +import { BorderNode } from '../model/BorderNode'; +import { RowNode } from '../model/RowNode'; +import { Orientation } from '../Orientation'; +import { CLASSES } from '../Types'; +import { LayoutInternal } from './Layout'; +import { enablePointerOnIFrames, isDesktop, startDrag } from './Utils'; +import { Rect } from '../Rect'; /** @internal */ export interface ISplitterProps { - layout: LayoutInternal; - node: RowNode | BorderNode; - index: number; - horizontal: boolean; + layout: LayoutInternal; + node: RowNode | BorderNode; + index: number; + horizontal: boolean; } /** @internal */ -export let splitterDragging:boolean = false; // used in tabset & borderTab +export let splitterDragging: boolean = false; // used in tabset & borderTab /** @internal */ export const Splitter = (props: ISplitterProps) => { - const { layout, node, index, horizontal } = props; - - const [dragging, setDragging] = React.useState(false); - const selfRef = React.useRef(null); - const extendedRef = React.useRef(null); - const pBounds = React.useRef([]); - const outlineDiv = React.useRef(undefined); - const handleDiv = React.useRef(undefined); - const dragStartX = React.useRef(0); - const dragStartY = React.useRef(0); - const initalSizes = React.useRef<{ initialSizes: number[], sum: number, startPosition: number }>({ initialSizes: [], sum: 0, startPosition: 0 }) - // const throttleTimer = React.useRef(undefined); - - const size = node.getModel().getSplitterSize(); - let extra = node.getModel().getSplitterExtra(); - - if (!isDesktop()) { - // make hit test area on mobile at least 20px - extra = Math.max(20, extra + size) - size; - } + const { layout, node, index, horizontal } = props; + + const [dragging, setDragging] = React.useState(false); + const selfRef = React.useRef(null); + const extendedRef = React.useRef(null); + const pBounds = React.useRef([]); + const outlineDiv = React.useRef(undefined); + const handleDiv = React.useRef(undefined); + const dragStartX = React.useRef(0); + const dragStartY = React.useRef(0); + const initalSizes = React.useRef<{ + initialSizes: number[]; + sum: number; + startPosition: number; + }>({ initialSizes: [], sum: 0, startPosition: 0 }); + // const throttleTimer = React.useRef(undefined); + + const size = node.getModel().getSplitterSize(); + let extra = node.getModel().getSplitterExtra(); + + if (!isDesktop()) { + // make hit test area on mobile at least 20px + extra = Math.max(20, extra + size) - size; + } + + React.useEffect(() => { + // Android fix: must have passive touchstart handler to prevent default handling + selfRef.current?.addEventListener('touchstart', onTouchStart, { + passive: false, + }); + extendedRef.current?.addEventListener('touchstart', onTouchStart, { + passive: false, + }); + return () => { + selfRef.current?.removeEventListener('touchstart', onTouchStart); + extendedRef.current?.removeEventListener('touchstart', onTouchStart); + }; + }, []); - React.useEffect(() => { - // Android fix: must have passive touchstart handler to prevent default handling - selfRef.current?.addEventListener("touchstart", onTouchStart, { passive: false }); - extendedRef.current?.addEventListener("touchstart", onTouchStart, { passive: false }); - return () => { - selfRef.current?.removeEventListener("touchstart", onTouchStart); - extendedRef.current?.removeEventListener("touchstart", onTouchStart); - } - }, []); + const onTouchStart = (event: TouchEvent) => { + event.preventDefault(); + event.stopImmediatePropagation(); + }; - const onTouchStart = (event: TouchEvent) => { - event.preventDefault(); - event.stopImmediatePropagation(); + const onPointerDown = (event: React.PointerEvent) => { + event.stopPropagation(); + if (node instanceof RowNode) { + initalSizes.current = node.getSplitterInitials(index); } - const onPointerDown = (event: React.PointerEvent) => { - event.stopPropagation(); - if (node instanceof RowNode) { - initalSizes.current = node.getSplitterInitials(index); - } - - enablePointerOnIFrames(false, layout.getCurrentDocument()!); - startDrag(event.currentTarget.ownerDocument, event, onDragMove, onDragEnd, onDragCancel); - - pBounds.current = node.getSplitterBounds(index, true); - const rootdiv = layout.getRootDiv(); - outlineDiv.current = layout.getCurrentDocument()!.createElement("div"); - outlineDiv.current.style.flexDirection = horizontal ? "row" : "column"; - outlineDiv.current.className = layout.getClassName(CLASSES.FLEXLAYOUT__SPLITTER_DRAG); - outlineDiv.current.style.cursor = node.getOrientation() === Orientation.VERT ? "ns-resize" : "ew-resize"; - - if (node.getModel().isSplitterEnableHandle()) { - handleDiv.current = layout.getCurrentDocument()!.createElement("div"); - handleDiv.current.className = cm(CLASSES.FLEXLAYOUT__SPLITTER_HANDLE) + " " + - (horizontal ? cm(CLASSES.FLEXLAYOUT__SPLITTER_HANDLE_HORZ) : cm(CLASSES.FLEXLAYOUT__SPLITTER_HANDLE_VERT)); - outlineDiv.current.appendChild(handleDiv.current); - } - - const r = selfRef.current?.getBoundingClientRect()!; - const rect = new Rect( - r.x - layout.getDomRect()!.x, - r.y - layout.getDomRect()!.y, - r.width, - r.height - ); + enablePointerOnIFrames(false, layout.getCurrentDocument()!); + startDrag( + event.currentTarget.ownerDocument, + event, + onDragMove, + onDragEnd, + onDragCancel, + ); + + pBounds.current = node.getSplitterBounds(index, true); + const rootdiv = layout.getRootDiv(); + outlineDiv.current = layout.getCurrentDocument()!.createElement('div'); + outlineDiv.current.style.flexDirection = horizontal ? 'row' : 'column'; + outlineDiv.current.className = layout.getClassName( + CLASSES.FLEXLAYOUT__SPLITTER_DRAG, + ); + outlineDiv.current.style.cursor = + node.getOrientation() === Orientation.VERT ? 'ns-resize' : 'ew-resize'; + + if (node.getModel().isSplitterEnableHandle()) { + handleDiv.current = layout.getCurrentDocument()!.createElement('div'); + handleDiv.current.className = + cm(CLASSES.FLEXLAYOUT__SPLITTER_HANDLE) + + ' ' + + (horizontal + ? cm(CLASSES.FLEXLAYOUT__SPLITTER_HANDLE_HORZ) + : cm(CLASSES.FLEXLAYOUT__SPLITTER_HANDLE_VERT)); + outlineDiv.current.appendChild(handleDiv.current); + } - dragStartX.current = event.clientX - r.x; - dragStartY.current = event.clientY - r.y; + const r = selfRef.current?.getBoundingClientRect(); + if (!r) { + return; + } + const domRect = layout.getDomRect(); + if (!domRect) { + return; + } + const rect = new Rect(r.x - domRect.x, r.y - domRect.y, r.width, r.height); - rect.positionElement(outlineDiv.current); - if (rootdiv) { - rootdiv.appendChild(outlineDiv.current); - } + dragStartX.current = event.clientX - r.x; + dragStartY.current = event.clientY - r.y; - setDragging(true); - splitterDragging = true; - }; + rect.positionElement(outlineDiv.current); + if (rootdiv) { + rootdiv.appendChild(outlineDiv.current); + } - const onDragCancel = () => { - const rootdiv = layout.getRootDiv(); - if (rootdiv && outlineDiv.current) { - rootdiv.removeChild(outlineDiv.current as Element); - } - outlineDiv.current = undefined; - setDragging(false); - splitterDragging = false; - }; + setDragging(true); + splitterDragging = true; + }; - const onDragMove = (x: number, y: number) => { - - if (outlineDiv.current) { - const clientRect = layout.getDomRect(); - if (!clientRect) { - return; - } - if (node.getOrientation() === Orientation.VERT) { - outlineDiv.current!.style.top = getBoundPosition(y - clientRect.y - dragStartY.current) + "px"; - } else { - outlineDiv.current!.style.left = getBoundPosition(x - clientRect.x - dragStartX.current) + "px"; - } - - if (layout.isRealtimeResize()) { - updateLayout(true); - } - } - }; + const onDragCancel = () => { + const rootdiv = layout.getRootDiv(); + if (rootdiv && outlineDiv.current) { + rootdiv.removeChild(outlineDiv.current as Element); + } + outlineDiv.current = undefined; + setDragging(false); + splitterDragging = false; + }; + + const onDragMove = (x: number, y: number) => { + if (outlineDiv.current) { + const clientRect = layout.getDomRect(); + if (!clientRect) { + return; + } + if (node.getOrientation() === Orientation.VERT) { + outlineDiv.current!.style.top = + getBoundPosition(y - clientRect.y - dragStartY.current) + 'px'; + } else { + outlineDiv.current!.style.left = + getBoundPosition(x - clientRect.x - dragStartX.current) + 'px'; + } + + if (layout.isRealtimeResize()) { + updateLayout(true); + } + } + }; - const onDragEnd = () => { - if (outlineDiv.current) { - updateLayout(false); + const onDragEnd = () => { + if (outlineDiv.current) { + updateLayout(false); - const rootdiv = layout.getRootDiv(); - if (rootdiv && outlineDiv.current) { - rootdiv.removeChild(outlineDiv.current as HTMLElement); - } - outlineDiv.current = undefined; + const rootdiv = layout.getRootDiv(); + if (rootdiv && outlineDiv.current) { + rootdiv.removeChild(outlineDiv.current as HTMLElement); + } + outlineDiv.current = undefined; + } + enablePointerOnIFrames(true, layout.getCurrentDocument()!); + setDragging(false); + splitterDragging = false; + }; + + const updateLayout = (realtime: boolean) => { + const redraw = () => { + if (outlineDiv.current) { + let value = 0; + if (node.getOrientation() === Orientation.VERT) { + value = outlineDiv.current!.offsetTop; + } else { + value = outlineDiv.current!.offsetLeft; } - enablePointerOnIFrames(true, layout.getCurrentDocument()!); - setDragging(false); - splitterDragging = false; - }; - const updateLayout = (realtime: boolean) => { - - const redraw = () => { - if (outlineDiv.current) { - let value = 0; - if (node.getOrientation() === Orientation.VERT) { - value = outlineDiv.current!.offsetTop; - } else { - value = outlineDiv.current!.offsetLeft; - } - - - if (node instanceof BorderNode) { - const pos = (node as BorderNode).calculateSplit(node, value); - layout.doAction(Actions.adjustBorderSplit(node.getId(), pos)); - } else { - const init = initalSizes.current; - const weights = node.calculateSplit(index, value, init.initialSizes, init.sum, init.startPosition); - layout.doAction(Actions.adjustWeights(node.getId(), weights)); - } - } - }; - - redraw(); - }; - - const getBoundPosition = (p: number) => { - const bounds = pBounds.current as number[]; - let rtn = p; - if (p < bounds[0]) { - rtn = bounds[0]; - } - if (p > bounds[1]) { - rtn = bounds[1]; + if (node instanceof BorderNode) { + const pos = (node as BorderNode).calculateSplit(node, value); + layout.doAction(Actions.adjustBorderSplit(node.getId(), pos)); + } else { + const init = initalSizes.current; + const weights = node.calculateSplit( + index, + value, + init.initialSizes, + init.sum, + init.startPosition, + ); + layout.doAction(Actions.adjustWeights(node.getId(), weights)); } - - return rtn; + } }; - const cm = layout.getClassName; - const style: Record = { - cursor: horizontal ? "ew-resize" : "ns-resize", - flexDirection: horizontal ? "column" : "row" - }; - let className = cm(CLASSES.FLEXLAYOUT__SPLITTER) + " " + cm(CLASSES.FLEXLAYOUT__SPLITTER_ + node.getOrientation().getName()); + redraw(); + }; - if (node instanceof BorderNode) { - className += " " + cm(CLASSES.FLEXLAYOUT__SPLITTER_BORDER); - } else { - if (node.getModel().getMaximizedTabset(layout.getWindowId()) !== undefined) { - style.display = "none"; - } + const getBoundPosition = (p: number) => { + const bounds = pBounds.current as number[]; + let rtn = p; + if (p < bounds[0]) { + rtn = bounds[0]; } - - if (horizontal) { - style.width = size + "px"; - style.minWidth = size + "px"; - } else { - style.height = size + "px"; - style.minHeight = size + "px"; + if (p > bounds[1]) { + rtn = bounds[1]; } - let handle; - if (!dragging && node.getModel().isSplitterEnableHandle()) { - handle = ( -
-
- ); + return rtn; + }; + + const cm = layout.getClassName; + const style: Record = { + cursor: horizontal ? 'ew-resize' : 'ns-resize', + flexDirection: horizontal ? 'column' : 'row', + }; + let className = + cm(CLASSES.FLEXLAYOUT__SPLITTER) + + ' ' + + cm(CLASSES.FLEXLAYOUT__SPLITTER_ + node.getOrientation().getName()); + + if (node instanceof BorderNode) { + className += ' ' + cm(CLASSES.FLEXLAYOUT__SPLITTER_BORDER); + } else { + if ( + node.getModel().getMaximizedTabset(layout.getWindowId()) !== undefined + ) { + style.display = 'none'; } - - if (extra === 0) { - return (
- {handle} -
); - } else { - // add extended transparent div for hit testing - - const style2: Record = {}; - if (node.getOrientation() === Orientation.HORZ) { - style2.height = "100%"; - style2.width = size + extra + "px"; - style2.cursor = "ew-resize"; - } else { - style2.height = size + extra + "px"; - style2.width = "100%"; - style2.cursor = "ns-resize"; + } + + if (horizontal) { + style.width = size + 'px'; + style.minWidth = size + 'px'; + } else { + style.height = size + 'px'; + style.minHeight = size + 'px'; + } + + let handle; + if (!dragging && node.getModel().isSplitterEnableHandle()) { + handle = ( +
-
-
-
); + >
+ ); + } + + if (extra === 0) { + return ( +
+ {handle} +
+ ); + } else { + // add extended transparent div for hit testing + + const style2: Record = {}; + if (node.getOrientation() === Orientation.HORZ) { + style2.height = '100%'; + style2.width = size + extra + 'px'; + style2.cursor = 'ew-resize'; + } else { + style2.height = size + extra + 'px'; + style2.width = '100%'; + style2.cursor = 'ns-resize'; } -}; + const className2 = cm(CLASSES.FLEXLAYOUT__SPLITTER_EXTRA); + + return ( +
+
+
+ ); + } +}; diff --git a/src/view/Tab.tsx b/src/view/Tab.tsx index 486be228..9d75188f 100755 --- a/src/view/Tab.tsx +++ b/src/view/Tab.tsx @@ -1,133 +1,142 @@ -import * as React from "react"; -import { TabNode } from "../model/TabNode"; -import { TabSetNode } from "../model/TabSetNode"; -import { CLASSES } from "../Types"; -import { LayoutInternal } from "./Layout"; -import { BorderNode } from "../model/BorderNode"; -import { Actions } from "../model/Actions"; +import * as React from 'react'; +import { TabNode } from '../model/TabNode'; +import { TabSetNode } from '../model/TabSetNode'; +import { CLASSES } from '../Types'; +import { LayoutInternal } from './Layout'; +import { BorderNode } from '../model/BorderNode'; +import { Actions } from '../model/Actions'; /** @internal */ export interface ITabProps { - layout: LayoutInternal; - node: TabNode; - selected: boolean; - path: string; + layout: LayoutInternal; + node: TabNode; + selected: boolean; + path: string; } /** @internal */ export const Tab = (props: ITabProps) => { - const { layout, selected, node, path } = props; - const selfRef = React.useRef(null); - const firstSelect = React.useRef(true); - - const parentNode = node.getParent() as TabSetNode | BorderNode; - const rect = parentNode.getContentRect()!; - - React.useLayoutEffect(() => { - const element = node.getMoveableElement()!; - selfRef.current!.appendChild(element); - node.setMoveableElement(element); - - const handleScroll = () => { - node.saveScrollPosition(); - }; - - // keep scroll position - element.addEventListener('scroll', handleScroll); - - // listen for clicks to change active tabset - selfRef.current!.addEventListener("pointerdown", onPointerDown); - - return () => { - element.removeEventListener('scroll', handleScroll); - if (selfRef.current) { - selfRef.current.removeEventListener("pointerdown", onPointerDown); - } - node.setVisible(false); - } - }, []); - - React.useEffect(() => { - if (node.isSelected()) { - if (firstSelect.current) { - node.restoreScrollPosition(); // if window docked back in - firstSelect.current = false; - } - } - }); - - const onPointerDown = () => { - const parent = node.getParent()!; // cannot use parentNode here since will be out of date - if (parent instanceof TabSetNode) { - if (!parent.isActive()) { - layout.doAction(Actions.setActiveTabset(parent.getId(), layout.getWindowId())); - } - } - }; + const { layout, selected, node, path } = props; + const selfRef = React.useRef(null); + const firstSelect = React.useRef(true); - node.setRect(rect); // needed for resize event - const cm = layout.getClassName; - const style: Record = {}; + const parentNode = node.getParent() as TabSetNode | BorderNode; + const rect = parentNode.getContentRect()!; - rect.styleWithPosition(style); + React.useLayoutEffect(() => { + const element = node.getMoveableElement()!; + selfRef.current!.appendChild(element); + node.setMoveableElement(element); - if (parentNode instanceof BorderNode && !node.isPinned()) { - style.zIndex = 1000; - style.boxShadow = "0 2px 8px rgba(0,0,0,0.2)"; - } + const handleScroll = () => { + node.saveScrollPosition(); + }; - let overlay = null; - - if (selected) { - node.setVisible(true); - if (document.hidden && node.isEnablePopoutOverlay()) { - const overlayStyle: Record = {}; - rect.styleWithPosition(overlayStyle); - overlay = (
) - } - } else { - style.display = "none"; - node.setVisible(false); - } + // keep scroll position + element.addEventListener('scroll', handleScroll); - if (parentNode instanceof TabSetNode) { - if (node.getModel().getMaximizedTabset(layout.getWindowId()) !== undefined) { - if (parentNode.isMaximized()) { - style.zIndex = 10; - } else { - style.display = "none"; - } - } - } + // listen for clicks to change active tabset + selfRef.current!.addEventListener('pointerdown', onPointerDown); - if (parentNode instanceof BorderNode) { - if (!parentNode.isShowing()) { - style.display = "none"; - } + return () => { + element.removeEventListener('scroll', handleScroll); + if (selfRef.current) { + selfRef.current.removeEventListener('pointerdown', onPointerDown); + } + node.setVisible(false); + }; + }, []); + + React.useEffect(() => { + if (node.isSelected()) { + if (firstSelect.current) { + node.restoreScrollPosition(); // if window docked back in + firstSelect.current = false; + } } - - let className = cm(CLASSES.FLEXLAYOUT__TAB); - if (parentNode instanceof BorderNode) { - className += " " + cm(CLASSES.FLEXLAYOUT__TAB_BORDER); - className += " " + cm(CLASSES.FLEXLAYOUT__TAB_BORDER_ + parentNode.getLocation().getName()); + }); + + const onPointerDown = () => { + const parent = node.getParent()!; // cannot use parentNode here since will be out of date + if (parent instanceof TabSetNode) { + if (!parent.isActive()) { + layout.doAction( + Actions.setActiveTabset(parent.getId(), layout.getWindowId()), + ); + } } - - if (node.getContentClassName() !== undefined) { - className += " " + node.getContentClassName(); + }; + + node.setRect(rect); // needed for resize event + const cm = layout.getClassName; + const style: Record = {}; + + rect.styleWithPosition(style); + + if (parentNode instanceof BorderNode && !node.isPinned()) { + style.zIndex = 1000; + style.boxShadow = '0 2px 8px rgba(0,0,0,0.2)'; + } + + let overlay = null; + + if (selected) { + node.setVisible(true); + if (document.hidden && node.isEnablePopoutOverlay()) { + const overlayStyle: Record = {}; + rect.styleWithPosition(overlayStyle); + overlay = ( +
+ ); + } + } else { + style.display = 'none'; + node.setVisible(false); + } + + if (parentNode instanceof TabSetNode) { + if ( + node.getModel().getMaximizedTabset(layout.getWindowId()) !== undefined + ) { + if (parentNode.isMaximized()) { + style.zIndex = 10; + } else { + style.display = 'none'; + } } + } - return ( - <> - {overlay} - -
- - ); + if (parentNode instanceof BorderNode) { + if (!parentNode.isShowing()) { + style.display = 'none'; + } + } + + let className = cm(CLASSES.FLEXLAYOUT__TAB); + if (parentNode instanceof BorderNode) { + className += ' ' + cm(CLASSES.FLEXLAYOUT__TAB_BORDER); + className += + ' ' + + cm(CLASSES.FLEXLAYOUT__TAB_BORDER_ + parentNode.getLocation().getName()); + } + + if (node.getContentClassName() !== undefined) { + className += ' ' + node.getContentClassName(); + } + + return ( + <> + {overlay} + +
+ + ); }; - - diff --git a/src/view/TabButton.tsx b/src/view/TabButton.tsx index cd2e48c6..b2b93b9a 100755 --- a/src/view/TabButton.tsx +++ b/src/view/TabButton.tsx @@ -1,200 +1,223 @@ -import * as React from "react"; -import { I18nLabel } from "../I18nLabel"; -import { Actions } from "../model/Actions"; -import { TabNode } from "../model/TabNode"; -import { TabSetNode } from "../model/TabSetNode"; -import { LayoutInternal } from "./Layout"; -import { ICloseType } from "../model/ICloseType"; -import { CLASSES } from "../Types"; -import { getRenderStateEx, isAuxMouseEvent } from "./Utils"; +import * as React from 'react'; +import { I18nLabel } from '../I18nLabel'; +import { Actions } from '../model/Actions'; +import { TabNode } from '../model/TabNode'; +import { TabSetNode } from '../model/TabSetNode'; +import { LayoutInternal } from './Layout'; +import { ICloseType } from '../model/ICloseType'; +import { CLASSES } from '../Types'; +import { getRenderStateEx, isAuxMouseEvent } from './Utils'; /** @internal */ export interface ITabButtonProps { - layout: LayoutInternal; - node: TabNode; - selected: boolean; - path: string; + layout: LayoutInternal; + node: TabNode; + selected: boolean; + path: string; } /** @internal */ export const TabButton = (props: ITabButtonProps) => { - const { layout, node, selected, path } = props; - const selfRef = React.useRef(null); - const contentRef = React.useRef(null); - const icons = layout.getIcons(); - - React.useLayoutEffect(() => { - node.setTabRect(layout.getBoundingClientRect(selfRef.current!)); - if (layout.getEditingTab() === node) { - (contentRef.current! as HTMLInputElement).select(); - } - }); - - const onDragStart = (event: React.DragEvent) => { - if (node.isEnableDrag()) { - event.stopPropagation(); // prevent starting a tabset drag as well - layout.setDragNode(event.nativeEvent, node as TabNode); - } else { - event.preventDefault(); - } - }; - - const onDragEnd = (event: React.DragEvent) => { - layout.clearDragMain(); - }; - - const onAuxMouseClick = (event: React.MouseEvent) => { - if (isAuxMouseEvent(event)) { - layout.auxMouseClick(node, event); - } - }; - - const onContextMenu = (event: React.MouseEvent) => { - layout.showContextMenu(node, event); - }; - - const onClick = () => { - layout.doAction(Actions.selectTab(node.getId())); - }; - - const onDoubleClick = (event: React.MouseEvent) => { - if (node.isEnableRename()) { - onRename(); - event.stopPropagation(); - } - }; - - const onRename = () => { - layout.setEditingTab(node); - layout.getCurrentDocument()!.body.addEventListener("pointerdown", onEndEdit); - }; - - const onEndEdit = (event: Event) => { - if (event.target !== contentRef.current!) { - layout.getCurrentDocument()!.body.removeEventListener("pointerdown", onEndEdit); - layout.setEditingTab(undefined); - } - }; - - const isClosable = () => { - const closeType = node.getCloseType(); - if (selected || closeType === ICloseType.Always) { - return true; - } - if (closeType === ICloseType.Visible) { - // not selected but x should be visible due to hover - if (window.matchMedia && window.matchMedia("(hover: hover) and (pointer: fine)").matches) { - return true; - } - } - return false; - }; - - const onClose = (event: React.MouseEvent) => { - if (isClosable()) { - layout.doAction(Actions.deleteTab(node.getId())); - event.stopPropagation(); - } - }; - - const onClosePointerDown = (event: React.PointerEvent) => { - event.stopPropagation(); - }; - - const onTextBoxPointerDown = (event: React.PointerEvent) => { - event.stopPropagation(); - }; - - const onTextBoxKeyPress = (event: React.KeyboardEvent) => { - if (event.code === 'Escape') { - // esc - layout.setEditingTab(undefined); - } else if (event.code === 'Enter' || event.code === 'NumpadEnter') { - // enter - layout.setEditingTab(undefined); - layout.doAction(Actions.renameTab(node.getId(), (event.target as HTMLInputElement).value)); - } - }; - - const cm = layout.getClassName; - const parentNode = node.getParent() as TabSetNode; - - const isStretch = parentNode.isEnableSingleTabStretch() && parentNode.getChildren().length === 1; - const baseClassName = isStretch ? CLASSES.FLEXLAYOUT__TAB_BUTTON_STRETCH : CLASSES.FLEXLAYOUT__TAB_BUTTON; - let classNames = cm(baseClassName); - classNames += " " + cm(baseClassName + "_" + parentNode.getTabLocation()); - - if (!isStretch) { - if (selected) { - classNames += " " + cm(baseClassName + "--selected"); - } else { - classNames += " " + cm(baseClassName + "--unselected"); - } - } + const { layout, node, selected, path } = props; + const selfRef = React.useRef(null); + const contentRef = React.useRef(null); + const icons = layout.getIcons(); - if (node.getClassName() !== undefined) { - classNames += " " + node.getClassName(); + React.useLayoutEffect(() => { + node.setTabRect(layout.getBoundingClientRect(selfRef.current!)); + if (layout.getEditingTab() === node) { + (contentRef.current! as HTMLInputElement).select(); + } + }); + + const onDragStart = (event: React.DragEvent) => { + if (node.isEnableDrag()) { + event.stopPropagation(); // prevent starting a tabset drag as well + layout.setDragNode(event.nativeEvent, node as TabNode); + } else { + event.preventDefault(); } + }; - const renderState = getRenderStateEx(layout, node); + const onDragEnd = (event: React.DragEvent) => { + layout.clearDragMain(); + }; - let content = renderState.content ? ( -
- {renderState.content} -
) : null; + const onAuxMouseClick = ( + event: React.MouseEvent, + ) => { + if (isAuxMouseEvent(event)) { + layout.auxMouseClick(node, event); + } + }; - const leading = renderState.leading ? ( -
- {renderState.leading} -
) : null; + const onContextMenu = (event: React.MouseEvent) => { + layout.showContextMenu(node, event); + }; - if (layout.getEditingTab() === node) { - content = ( - - ); + const onClick = () => { + layout.doAction(Actions.selectTab(node.getId())); + }; + + const onDoubleClick = (event: React.MouseEvent) => { + if (node.isEnableRename()) { + onRename(); + event.stopPropagation(); } + }; + + const onRename = () => { + layout.setEditingTab(node); + layout + .getCurrentDocument()! + .body.addEventListener('pointerdown', onEndEdit); + }; + + const onEndEdit = (event: Event) => { + if (event.target !== contentRef.current!) { + layout + .getCurrentDocument()! + .body.removeEventListener('pointerdown', onEndEdit); + layout.setEditingTab(undefined); + } + }; - if (node.isEnableClose() && !isStretch) { - const closeTitle = layout.i18nName(I18nLabel.Close_Tab); - renderState.buttons.push( -
- {(typeof icons.close === "function") ? icons.close(node) : icons.close} -
- ); + const isClosable = () => { + const closeType = node.getCloseType(); + if (selected || closeType === ICloseType.Always) { + return true; + } + if (closeType === ICloseType.Visible) { + // not selected but x should be visible due to hover + if ( + window.matchMedia && + window.matchMedia('(hover: hover) and (pointer: fine)').matches + ) { + return true; + } } + return false; + }; - return ( -
- {leading} - {content} - {renderState.buttons} -
+ const onClose = (event: React.MouseEvent) => { + if (isClosable()) { + layout.doAction(Actions.deleteTab(node.getId())); + event.stopPropagation(); + } + }; + + const onClosePointerDown = (event: React.PointerEvent) => { + event.stopPropagation(); + }; + + const onTextBoxPointerDown = ( + event: React.PointerEvent, + ) => { + event.stopPropagation(); + }; + + const onTextBoxKeyPress = (event: React.KeyboardEvent) => { + if (event.code === 'Escape') { + // esc + layout.setEditingTab(undefined); + } else if (event.code === 'Enter' || event.code === 'NumpadEnter') { + // enter + layout.setEditingTab(undefined); + layout.doAction( + Actions.renameTab( + node.getId(), + (event.target as HTMLInputElement).value, + ), + ); + } + }; + + const cm = layout.getClassName; + const parentNode = node.getParent() as TabSetNode; + + const isStretch = + parentNode.isEnableSingleTabStretch() && + parentNode.getChildren().length === 1; + const baseClassName = isStretch + ? CLASSES.FLEXLAYOUT__TAB_BUTTON_STRETCH + : CLASSES.FLEXLAYOUT__TAB_BUTTON; + let classNames = cm(baseClassName); + classNames += ' ' + cm(baseClassName + '_' + parentNode.getTabLocation()); + + if (!isStretch) { + if (selected) { + classNames += ' ' + cm(baseClassName + '--selected'); + } else { + classNames += ' ' + cm(baseClassName + '--unselected'); + } + } + + if (node.getClassName() !== undefined) { + classNames += ' ' + node.getClassName(); + } + + const renderState = getRenderStateEx(layout, node); + + let content = renderState.content ? ( +
+ {renderState.content} +
+ ) : null; + + const leading = renderState.leading ? ( +
+ {renderState.leading} +
+ ) : null; + + if (layout.getEditingTab() === node) { + content = ( + + ); + } + + if (node.isEnableClose() && !isStretch) { + const closeTitle = layout.i18nName(I18nLabel.Close_Tab); + renderState.buttons.push( +
+ {typeof icons.close === 'function' ? icons.close(node) : icons.close} +
, ); + } + + return ( +
+ {leading} + {content} + {renderState.buttons} +
+ ); }; diff --git a/src/view/TabButtonStamp.tsx b/src/view/TabButtonStamp.tsx index a66a449f..eb46e629 100755 --- a/src/view/TabButtonStamp.tsx +++ b/src/view/TabButtonStamp.tsx @@ -1,42 +1,42 @@ -import { TabNode } from "../model/TabNode"; -import { LayoutInternal } from "./Layout"; -import { CLASSES } from "../Types"; -import { getRenderStateEx } from "./Utils"; +import { TabNode } from '../model/TabNode'; +import { LayoutInternal } from './Layout'; +import { CLASSES } from '../Types'; +import { getRenderStateEx } from './Utils'; /** @internal */ export interface ITabButtonStampProps { - node: TabNode; - layout: LayoutInternal; + node: TabNode; + layout: LayoutInternal; } /** @internal */ export const TabButtonStamp = (props: ITabButtonStampProps) => { - const { layout, node } = props; - - const cm = layout.getClassName; - - const classNames = cm(CLASSES.FLEXLAYOUT__TAB_BUTTON_STAMP); - - const renderState = getRenderStateEx(layout, node); - - const content = renderState.content ? ( -
- {renderState.content} -
) - : node.getNameForOverflowMenu(); - - const leading = renderState.leading ? ( -
- {renderState.leading} -
) : null; - - return ( -
- {leading} - {content} -
- ); + const { layout, node } = props; + + const cm = layout.getClassName; + + const classNames = cm(CLASSES.FLEXLAYOUT__TAB_BUTTON_STAMP); + + const renderState = getRenderStateEx(layout, node); + + const content = renderState.content ? ( +
+ {renderState.content} +
+ ) : ( + node.getNameForOverflowMenu() + ); + + const leading = renderState.leading ? ( +
+ {renderState.leading} +
+ ) : null; + + return ( +
+ {leading} + {content} +
+ ); }; diff --git a/src/view/TabOverflowHook.tsx b/src/view/TabOverflowHook.tsx index 6f7a254a..23059695 100755 --- a/src/view/TabOverflowHook.tsx +++ b/src/view/TabOverflowHook.tsx @@ -1,317 +1,342 @@ -import * as React from "react"; -import { TabSetNode } from "../model/TabSetNode"; -import { BorderNode } from "../model/BorderNode"; -import { Orientation } from "../Orientation"; -import { LayoutInternal } from "./Layout"; -import { TabNode } from "../model/TabNode"; -import { startDrag } from "./Utils"; -import { Rect } from "../Rect"; +import * as React from 'react'; +import { TabSetNode } from '../model/TabSetNode'; +import { BorderNode } from '../model/BorderNode'; +import { Orientation } from '../Orientation'; +import { LayoutInternal } from './Layout'; +import { TabNode } from '../model/TabNode'; +import { startDrag } from './Utils'; +import { Rect } from '../Rect'; /** @internal */ export const useTabOverflow = ( - layout: LayoutInternal, - node: TabSetNode | BorderNode, - orientation: Orientation, - tabStripRef: React.RefObject, - miniScrollRef: React.RefObject, - tabClassName: string + layout: LayoutInternal, + node: TabSetNode | BorderNode, + orientation: Orientation, + tabStripRef: React.RefObject, + miniScrollRef: React.RefObject, + tabClassName: string, ) => { - const [hiddenTabs, setHiddenTabs] = React.useState([]); - const [isShowHiddenTabs, setShowHiddenTabs] = React.useState(false); - const [isDockStickyButtons, setDockStickyButtons] = React.useState(false); - - const selfRef = React.useRef(null); - const userControlledPositionRef = React.useRef(false); - const updateHiddenTabsTimerRef = React.useRef(undefined); - const hiddenTabsRef = React.useRef([]); - const thumbInternalPos = React.useRef(0); - const repositioningRef = React.useRef(false); - hiddenTabsRef.current = hiddenTabs; - - // if node id changes (new model) then reset scroll to 0 - React.useLayoutEffect(() => { - if (tabStripRef.current) { - setScrollPosition(0); - } - }, [node.getId()]); - - // if selected node or tabset/border rectangle change then unset usercontrolled (so selected tab will be kept in view) - React.useLayoutEffect(() => { - userControlledPositionRef.current = false; - }, [node.getSelectedNode(), node.getRect().width, node.getRect().height]); + const [hiddenTabs, setHiddenTabs] = React.useState([]); + const [isShowHiddenTabs, setShowHiddenTabs] = React.useState(false); + const [isDockStickyButtons, setDockStickyButtons] = + React.useState(false); + + const selfRef = React.useRef(null); + const userControlledPositionRef = React.useRef(false); + const updateHiddenTabsTimerRef = React.useRef( + undefined, + ); + const hiddenTabsRef = React.useRef([]); + const thumbInternalPos = React.useRef(0); + const repositioningRef = React.useRef(false); + hiddenTabsRef.current = hiddenTabs; + + // if node id changes (new model) then reset scroll to 0 + React.useLayoutEffect(() => { + if (tabStripRef.current) { + setScrollPosition(0); + } + }, [node.getId()]); - React.useLayoutEffect(() => { - checkForOverflow(); // if tabs + sticky buttons length > scroll area => move sticky buttons to right buttons + // if selected node or tabset/border rectangle change then unset usercontrolled (so selected tab will be kept in view) + React.useLayoutEffect(() => { + userControlledPositionRef.current = false; + }, [node.getSelectedNode(), node.getRect().width, node.getRect().height]); - if (userControlledPositionRef.current === false) { - scrollIntoView(); - } + React.useLayoutEffect(() => { + checkForOverflow(); // if tabs + sticky buttons length > scroll area => move sticky buttons to right buttons - updateScrollMetrics(); - updateHiddenTabs(); - }); + if (userControlledPositionRef.current === false) { + scrollIntoView(); + } - React.useEffect(() => { - selfRef.current?.addEventListener("wheel", onWheel, { passive: false }); - return () => { - selfRef.current?.removeEventListener("wheel", onWheel); - }; - }, [selfRef.current]); + updateScrollMetrics(); + updateHiddenTabs(); + }); - // needed to prevent default mouse wheel over tabset/border when page scrolled (cannot do with react event?) - const onWheel = (event: Event) => { - event.preventDefault(); + React.useEffect(() => { + selfRef.current?.addEventListener('wheel', onWheel, { passive: false }); + return () => { + selfRef.current?.removeEventListener('wheel', onWheel); }; - - function scrollIntoView() { - const selectedTabNode = node.getSelectedNode() as TabNode; - if (selectedTabNode && tabStripRef.current) { - const stripRect = layout.getBoundingClientRect(tabStripRef.current); - const selectedRect = selectedTabNode.getTabRect()!; - - let shift = getNear(stripRect) - getNear(selectedRect); - if (shift > 0 || getSize(selectedRect) > getSize(stripRect)) { - setScrollPosition(getScrollPosition(tabStripRef.current) - shift); - repositioningRef.current = true; // prevent onScroll setting userControlledPosition - } else { - shift = getFar(selectedRect) - getFar(stripRect); - if (shift > 0) { - setScrollPosition(getScrollPosition(tabStripRef.current) + shift); - repositioningRef.current = true; - } - } + }, [selfRef.current]); + + // needed to prevent default mouse wheel over tabset/border when page scrolled (cannot do with react event?) + const onWheel = (event: Event) => { + event.preventDefault(); + }; + + function scrollIntoView() { + const selectedTabNode = node.getSelectedNode() as TabNode; + if (selectedTabNode && tabStripRef.current) { + const stripRect = layout.getBoundingClientRect(tabStripRef.current); + const selectedRect = selectedTabNode.getTabRect()!; + + let shift = getNear(stripRect) - getNear(selectedRect); + if (shift > 0 || getSize(selectedRect) > getSize(stripRect)) { + setScrollPosition(getScrollPosition(tabStripRef.current) - shift); + repositioningRef.current = true; // prevent onScroll setting userControlledPosition + } else { + shift = getFar(selectedRect) - getFar(stripRect); + if (shift > 0) { + setScrollPosition(getScrollPosition(tabStripRef.current) + shift); + repositioningRef.current = true; } + } } - - const updateScrollMetrics = () => { - if (tabStripRef.current && miniScrollRef.current) { - const t = tabStripRef.current; - const s = miniScrollRef.current; - - const size = getElementSize(t); - const scrollSize = getScrollSize(t); - const position = getScrollPosition(t); - - if (scrollSize > size && scrollSize > 0) { - let thumbSize = size * size / scrollSize; - let adjust = 0; - if (thumbSize < 20) { - adjust = 20 - thumbSize; - thumbSize = 20; - } - const thumbPos = position * (size - adjust) / scrollSize; - if (orientation === Orientation.HORZ) { - s.style.width = thumbSize + "px"; - s.style.left = thumbPos + "px"; - } else { - s.style.height = thumbSize + "px"; - s.style.top = thumbPos + "px"; - } - s.style.display = "block"; - } else { - s.style.display = "none"; - } - - if (orientation === Orientation.HORZ) { - s.style.bottom = "0px"; - } else { - s.style.right = "0px"; - } + } + + const updateScrollMetrics = () => { + if (tabStripRef.current && miniScrollRef.current) { + const t = tabStripRef.current; + const s = miniScrollRef.current; + + const size = getElementSize(t); + const scrollSize = getScrollSize(t); + const position = getScrollPosition(t); + + if (scrollSize > size && scrollSize > 0) { + let thumbSize = (size * size) / scrollSize; + let adjust = 0; + if (thumbSize < 20) { + adjust = 20 - thumbSize; + thumbSize = 20; } - } - - const updateHiddenTabs = () => { - const newHiddenTabs = findHiddenTabs(); - const showHidden = newHiddenTabs.length > 0; - - if (showHidden !== isShowHiddenTabs) { - setShowHiddenTabs(showHidden); + const thumbPos = (position * (size - adjust)) / scrollSize; + if (orientation === Orientation.HORZ) { + s.style.width = thumbSize + 'px'; + s.style.left = thumbPos + 'px'; + } else { + s.style.height = thumbSize + 'px'; + s.style.top = thumbPos + 'px'; } + s.style.display = 'block'; + } else { + s.style.display = 'none'; + } + + if (orientation === Orientation.HORZ) { + s.style.bottom = '0px'; + } else { + s.style.right = '0px'; + } + } + }; - if (updateHiddenTabsTimerRef.current === undefined) { - // throttle updates to prevent Maximum update depth exceeded error - updateHiddenTabsTimerRef.current = setTimeout(() => { - const newHiddenTabs = findHiddenTabs(); - if (!arraysEqual(newHiddenTabs, hiddenTabsRef.current)) { - setHiddenTabs(newHiddenTabs); - } + const updateHiddenTabs = () => { + const newHiddenTabs = findHiddenTabs(); + const showHidden = newHiddenTabs.length > 0; - updateHiddenTabsTimerRef.current = undefined; - }, 100); - } + if (showHidden !== isShowHiddenTabs) { + setShowHiddenTabs(showHidden); } - const onScroll = () => { - if (!repositioningRef.current){ - userControlledPositionRef.current=true; + if (updateHiddenTabsTimerRef.current === undefined) { + // throttle updates to prevent Maximum update depth exceeded error + updateHiddenTabsTimerRef.current = setTimeout(() => { + const newHiddenTabs = findHiddenTabs(); + if (!arraysEqual(newHiddenTabs, hiddenTabsRef.current)) { + setHiddenTabs(newHiddenTabs); } - repositioningRef.current = false; - updateScrollMetrics() - updateHiddenTabs(); - }; - const onScrollPointerDown = (event: React.PointerEvent) => { - event.stopPropagation(); - miniScrollRef.current!.setPointerCapture(event.pointerId) - const r = miniScrollRef.current?.getBoundingClientRect()!; - if (orientation === Orientation.HORZ) { - thumbInternalPos.current = event.clientX - r.x; - } else { - thumbInternalPos.current = event.clientY - r.y; - } - startDrag(event.currentTarget.ownerDocument, event, onDragMove, onDragEnd, onDragCancel); + updateHiddenTabsTimerRef.current = undefined; + }, 100); } + }; - const onDragMove = (x: number, y: number) => { - if (tabStripRef.current && miniScrollRef.current) { - const t = tabStripRef.current; - const s = miniScrollRef.current; - const size = getElementSize(t); - const scrollSize = getScrollSize(t); - const thumbSize = getElementSize(s); - - const r = t.getBoundingClientRect()!; - let thumb = 0; - if (orientation === Orientation.HORZ) { - thumb = x - r.x - thumbInternalPos.current; - } else { - thumb = y - r.y - thumbInternalPos.current - } - - thumb = Math.max(0, Math.min(scrollSize - thumbSize, thumb)); - if (size > 0) { - const scrollPos = thumb * scrollSize / size; - setScrollPosition(scrollPos); - } - } + const onScroll = () => { + if (!repositioningRef.current) { + userControlledPositionRef.current = true; } - - const onDragEnd = () => { + repositioningRef.current = false; + updateScrollMetrics(); + updateHiddenTabs(); + }; + + const onScrollPointerDown = (event: React.PointerEvent) => { + event.stopPropagation(); + miniScrollRef.current!.setPointerCapture(event.pointerId); + const miniScrollElem = miniScrollRef.current; + if (!miniScrollElem) return; + const r = miniScrollElem.getBoundingClientRect(); + if (orientation === Orientation.HORZ) { + thumbInternalPos.current = event.clientX - r.x; + } else { + thumbInternalPos.current = event.clientY - r.y; } - - const onDragCancel = () => { + startDrag( + event.currentTarget.ownerDocument, + event, + onDragMove, + onDragEnd, + onDragCancel, + ); + }; + + const onDragMove = (x: number, y: number) => { + if (tabStripRef.current && miniScrollRef.current) { + const t = tabStripRef.current; + const s = miniScrollRef.current; + const size = getElementSize(t); + const scrollSize = getScrollSize(t); + const thumbSize = getElementSize(s); + + const r = t.getBoundingClientRect()!; + let thumb = 0; + if (orientation === Orientation.HORZ) { + thumb = x - r.x - thumbInternalPos.current; + } else { + thumb = y - r.y - thumbInternalPos.current; + } + + thumb = Math.max(0, Math.min(scrollSize - thumbSize, thumb)); + if (size > 0) { + const scrollPos = (thumb * scrollSize) / size; + setScrollPosition(scrollPos); + } } + }; + + const onDragEnd = () => {}; + + const onDragCancel = () => {}; - const checkForOverflow = () => { - if (tabStripRef.current) { - const strip = tabStripRef.current; - const tabContainer = strip.firstElementChild!; + const checkForOverflow = () => { + if (tabStripRef.current) { + const strip = tabStripRef.current; + const tabContainer = strip.firstElementChild!; - const offset = isDockStickyButtons ? 10 : 0; // prevents flashing, after sticky buttons docked set, must be 10 pixels smaller before unsetting - const dock = (getElementSize(tabContainer) + offset) > getElementSize(tabStripRef.current); - if (dock !== isDockStickyButtons) { - setDockStickyButtons(dock); - } + const offset = isDockStickyButtons ? 10 : 0; // prevents flashing, after sticky buttons docked set, must be 10 pixels smaller before unsetting + const dock = + getElementSize(tabContainer) + offset > + getElementSize(tabStripRef.current); + if (dock !== isDockStickyButtons) { + setDockStickyButtons(dock); + } + } + }; + + const findHiddenTabs: () => number[] = () => { + const hidden: number[] = []; + if (tabStripRef.current) { + const strip = tabStripRef.current; + const stripRect = strip.getBoundingClientRect(); + const visibleNear = getNear(stripRect) - 1; + const visibleFar = getFar(stripRect) + 1; + + const tabContainer = strip.firstElementChild!; + + let i = 0; + Array.from(tabContainer.children).forEach((child) => { + const tabRect = child.getBoundingClientRect(); + + if (child.classList.contains(tabClassName)) { + if (getNear(tabRect) < visibleNear || getFar(tabRect) > visibleFar) { + hidden.push(i); + } + i++; } + }); } - const findHiddenTabs: () => number[] = () => { - const hidden: number[] = []; - if (tabStripRef.current) { - const strip = tabStripRef.current; - const stripRect = strip.getBoundingClientRect(); - const visibleNear = getNear(stripRect) - 1; - const visibleFar = getFar(stripRect) + 1; - - const tabContainer = strip.firstElementChild!; - - let i = 0; - Array.from(tabContainer.children).forEach((child) => { - const tabRect = child.getBoundingClientRect(); - - if (child.classList.contains(tabClassName)) { - if (getNear(tabRect) < visibleNear || getFar(tabRect) > visibleFar) { - hidden.push(i); - } - i++; - } - }); - } + return hidden; + }; - return hidden; - }; + const onMouseWheel = (event: React.WheelEvent) => { + if (tabStripRef.current) { + if (node.getChildren().length === 0) return; - const onMouseWheel = (event: React.WheelEvent) => { - if (tabStripRef.current) { - if (node.getChildren().length === 0) return; - - let delta = 0; - if (Math.abs(event.deltaY) > 0) { - delta = -event.deltaY; - if (event.deltaMode === 1) { - // DOM_DELTA_LINE 0x01 The delta values are specified in lines. - delta *= 40; - } - const newPos = getScrollPosition(tabStripRef.current) - delta; - const maxScroll = getScrollSize(tabStripRef.current) - getElementSize(tabStripRef.current); - const p = Math.max(0, Math.min(maxScroll, newPos)); - setScrollPosition(p); - event.stopPropagation(); - } + let delta = 0; + if (Math.abs(event.deltaY) > 0) { + delta = -event.deltaY; + if (event.deltaMode === 1) { + // DOM_DELTA_LINE 0x01 The delta values are specified in lines. + delta *= 40; } - }; - - // orientation helpers: + const newPos = getScrollPosition(tabStripRef.current) - delta; + const maxScroll = + getScrollSize(tabStripRef.current) - + getElementSize(tabStripRef.current); + const p = Math.max(0, Math.min(maxScroll, newPos)); + setScrollPosition(p); + event.stopPropagation(); + } + } + }; - const getNear = (rect: DOMRect | Rect) => { - if (orientation === Orientation.HORZ) { - return rect.x; - } else { - return rect.y; - } - }; + // orientation helpers: - const getFar = (rect: DOMRect | Rect) => { - if (orientation === Orientation.HORZ) { - return rect.right; - } else { - return rect.bottom; - } - }; + const getNear = (rect: DOMRect | Rect) => { + if (orientation === Orientation.HORZ) { + return rect.x; + } else { + return rect.y; + } + }; - const getElementSize = (elm: Element) => { - if (orientation === Orientation.HORZ) { - return elm.clientWidth; - } else { - return elm.clientHeight; - } + const getFar = (rect: DOMRect | Rect) => { + if (orientation === Orientation.HORZ) { + return rect.right; + } else { + return rect.bottom; } + }; - const getSize = (rect: DOMRect | Rect) => { - if (orientation === Orientation.HORZ) { - return rect.width; - } else { - return rect.height; - } + const getElementSize = (elm: Element) => { + if (orientation === Orientation.HORZ) { + return elm.clientWidth; + } else { + return elm.clientHeight; } + }; - const getScrollSize = (elm: Element) => { - if (orientation === Orientation.HORZ) { - return elm.scrollWidth; - } else { - return elm.scrollHeight; - } + const getSize = (rect: DOMRect | Rect) => { + if (orientation === Orientation.HORZ) { + return rect.width; + } else { + return rect.height; } + }; - const setScrollPosition = (p: number) => { - if (orientation === Orientation.HORZ) { - tabStripRef.current!.scrollLeft = p; - } else { - tabStripRef.current!.scrollTop = p; - } + const getScrollSize = (elm: Element) => { + if (orientation === Orientation.HORZ) { + return elm.scrollWidth; + } else { + return elm.scrollHeight; } + }; - const getScrollPosition = (elm: Element) => { - if (orientation === Orientation.HORZ) { - return elm.scrollLeft; - } else { - return elm.scrollTop; - } + const setScrollPosition = (p: number) => { + if (orientation === Orientation.HORZ) { + tabStripRef.current!.scrollLeft = p; + } else { + tabStripRef.current!.scrollTop = p; } + }; - return { selfRef, userControlledPositionRef, onScroll, onScrollPointerDown, hiddenTabs, onMouseWheel, isDockStickyButtons, isShowHiddenTabs }; + const getScrollPosition = (elm: Element) => { + if (orientation === Orientation.HORZ) { + return elm.scrollLeft; + } else { + return elm.scrollTop; + } + }; + + return { + selfRef, + userControlledPositionRef, + onScroll, + onScrollPointerDown, + hiddenTabs, + onMouseWheel, + isDockStickyButtons, + isShowHiddenTabs, + }; }; function arraysEqual(arr1: number[], arr2: number[]) { - return arr1.length === arr2.length && arr1.every((val, index) => val === arr2[index]); + return ( + arr1.length === arr2.length && + arr1.every((val, index) => val === arr2[index]) + ); } diff --git a/src/view/TabSet.tsx b/src/view/TabSet.tsx index 998127cd..9add762b 100755 --- a/src/view/TabSet.tsx +++ b/src/view/TabSet.tsx @@ -1,487 +1,605 @@ -import * as React from "react"; -import { I18nLabel } from "../I18nLabel"; -import { Actions } from "../model/Actions"; -import { TabNode } from "../model/TabNode"; -import { TabSetNode } from "../model/TabSetNode"; -import { showPopup } from "./PopupMenu"; -import { LayoutInternal, ITabSetRenderValues } from "./Layout"; -import { TabButton } from "./TabButton"; -import { useTabOverflow } from "./TabOverflowHook"; -import { Orientation } from "../Orientation"; -import { CLASSES } from "../Types"; -import { isAuxMouseEvent } from "./Utils"; -import { createPortal } from "react-dom"; -import { splitterDragging } from "./Splitter"; +import * as React from 'react'; +import { I18nLabel } from '../I18nLabel'; +import { Actions } from '../model/Actions'; +import { TabNode } from '../model/TabNode'; +import { TabSetNode } from '../model/TabSetNode'; +import { showPopup } from './PopupMenu'; +import { LayoutInternal, ITabSetRenderValues } from './Layout'; +import { TabButton } from './TabButton'; +import { useTabOverflow } from './TabOverflowHook'; +import { Orientation } from '../Orientation'; +import { CLASSES } from '../Types'; +import { isAuxMouseEvent } from './Utils'; +import { createPortal } from 'react-dom'; +import { splitterDragging } from './Splitter'; /** @internal */ export interface ITabSetProps { - layout: LayoutInternal; - node: TabSetNode; + layout: LayoutInternal; + node: TabSetNode; } /** @internal */ export const TabSet = (props: ITabSetProps) => { - const { node, layout } = props; + const { node, layout } = props; - const tabStripRef = React.useRef(null); - const miniScrollRef = React.useRef(null); - const tabStripInnerRef = React.useRef(null); - const contentRef = React.useRef(null); - const buttonBarRef = React.useRef(null); - const overflowbuttonRef = React.useRef(null); - const stickyButtonsRef = React.useRef(null); - const timer = React.useRef(undefined); + const tabStripRef = React.useRef(null); + const miniScrollRef = React.useRef(null); + const tabStripInnerRef = React.useRef(null); + const contentRef = React.useRef(null); + const buttonBarRef = React.useRef(null); + const overflowbuttonRef = React.useRef(null); + const stickyButtonsRef = React.useRef(null); + const timer = React.useRef(undefined); - const icons = layout.getIcons(); + const icons = layout.getIcons(); - React.useLayoutEffect(() => { - node.setRect(layout.getBoundingClientRect(selfRef.current!)); + React.useLayoutEffect(() => { + node.setRect(layout.getBoundingClientRect(selfRef.current!)); - if (tabStripRef.current) { - node.setTabStripRect(layout.getBoundingClientRect(tabStripRef.current!)); - } + if (tabStripRef.current) { + node.setTabStripRect(layout.getBoundingClientRect(tabStripRef.current!)); + } - const newContentRect = layout.getBoundingClientRect(contentRef.current!); - if (!node.getContentRect().equals(newContentRect) && !isNaN(newContentRect.x)) { - node.setContentRect(newContentRect); - if (splitterDragging) { // next movement will draw tabs again, only redraw after pause/end - if (timer.current) { - clearTimeout(timer.current); - } - timer.current = setTimeout(() => { - layout.redrawInternal("border content rect " + newContentRect); - timer.current = undefined; - }, 50); - } else { - layout.redrawInternal("border content rect " + newContentRect); - } + const newContentRect = layout.getBoundingClientRect(contentRef.current!); + if ( + !node.getContentRect().equals(newContentRect) && + !isNaN(newContentRect.x) + ) { + node.setContentRect(newContentRect); + if (splitterDragging) { + // next movement will draw tabs again, only redraw after pause/end + if (timer.current) { + clearTimeout(timer.current); } + timer.current = setTimeout(() => { + layout.redrawInternal('border content rect ' + newContentRect); + timer.current = undefined; + }, 50); + } else { + layout.redrawInternal('border content rect ' + newContentRect); + } + } + }); + + // this must be after the useEffect, so the node rect is already set (else window popin will not position tabs correctly) + const { + selfRef, + userControlledPositionRef, + onScroll, + onScrollPointerDown, + hiddenTabs, + onMouseWheel, + isDockStickyButtons, + isShowHiddenTabs, + } = useTabOverflow( + layout, + node, + Orientation.HORZ, + tabStripInnerRef, + miniScrollRef, + layout.getClassName(CLASSES.FLEXLAYOUT__TAB_BUTTON), + ); + + const onOverflowClick = ( + event: React.MouseEvent, + ) => { + const callback = layout.getShowOverflowMenu(); + const items = hiddenTabs.map((h) => { + return { index: h, node: node.getChildren()[h] as TabNode }; }); + if (callback !== undefined) { + callback(node, event, items, onOverflowItemSelect); + } else { + const element = overflowbuttonRef.current!; + showPopup(element, node, items, onOverflowItemSelect, layout); + } + event.stopPropagation(); + }; - // this must be after the useEffect, so the node rect is already set (else window popin will not position tabs correctly) - const { selfRef, userControlledPositionRef, onScroll, onScrollPointerDown, hiddenTabs, onMouseWheel, isDockStickyButtons, isShowHiddenTabs } = - useTabOverflow(layout, node, Orientation.HORZ, tabStripInnerRef, miniScrollRef, - layout.getClassName(CLASSES.FLEXLAYOUT__TAB_BUTTON)); - - const onOverflowClick = (event: React.MouseEvent) => { - const callback = layout.getShowOverflowMenu(); - const items = hiddenTabs.map(h => { return { index: h, node: (node.getChildren()[h] as TabNode) }; }); - if (callback !== undefined) { - callback(node, event, items, onOverflowItemSelect); - } else { - const element = overflowbuttonRef.current!; - showPopup( - element, - node, - items, - onOverflowItemSelect, - layout - ); - } - event.stopPropagation(); - }; - - const onOverflowItemSelect = (item: { node: TabNode; index: number }) => { - layout.doAction(Actions.selectTab(item.node.getId())); - userControlledPositionRef.current = false; - }; - - const onDragStart = (event: React.DragEvent) => { - if (!layout.getEditingTab()) { - if (node.isEnableDrag()) { - event.stopPropagation(); - layout.setDragNode(event.nativeEvent, node as TabSetNode); - } else { - event.preventDefault(); - } - } else { - event.preventDefault(); - } - }; - - const onPointerDown = (event: React.PointerEvent) => { - if (!isAuxMouseEvent(event)) { - layout.doAction(Actions.setActiveTabset(node.getId(), layout.getWindowId())); - } - }; - - const onAuxMouseClick = (event: React.MouseEvent) => { - if (isAuxMouseEvent(event)) { - layout.auxMouseClick(node, event); - } - }; - - const onContextMenu = (event: React.MouseEvent) => { - layout.showContextMenu(node, event); - }; - - const onInterceptPointerDown = (event: React.PointerEvent) => { - event.stopPropagation(); - }; - - const onMaximizeToggle = (event: React.MouseEvent) => { - if (node.canMaximize()) { - layout.maximize(node); - } - event.stopPropagation(); - }; - - const onClose = (event: React.MouseEvent) => { - layout.doAction(Actions.deleteTabset(node.getId())); - event.stopPropagation(); - }; - - const onCloseTab = (event: React.MouseEvent) => { - layout.doAction(Actions.deleteTab(node.getChildren()[0].getId())); - event.stopPropagation(); - }; + const onOverflowItemSelect = (item: { node: TabNode; index: number }) => { + layout.doAction(Actions.selectTab(item.node.getId())); + userControlledPositionRef.current = false; + }; - const onPopoutTab = (event: React.MouseEvent) => { - if (selectedTabNode !== undefined) { - layout.doAction(Actions.popoutTab(selectedTabNode.getId())); - // layout.doAction(Actions.popoutTabset(node.getId())); - } + const onDragStart = (event: React.DragEvent) => { + if (!layout.getEditingTab()) { + if (node.isEnableDrag()) { event.stopPropagation(); - }; - - const onDoubleClick = (event: React.MouseEvent) => { - if (node.canMaximize()) { - layout.maximize(node); - } - }; - - // Start Render - - const cm = layout.getClassName; - const selectedTabNode: TabNode = node.getSelectedNode() as TabNode; - const path = node.getPath(); - - const tabs = []; - if (node.isEnableTabStrip()) { - for (let i = 0; i < node.getChildren().length; i++) { - const child = node.getChildren()[i] as TabNode; - const isSelected = node.getSelected() === i; - tabs.push( - ); - if (i < node.getChildren().length - 1) { - tabs.push( -
- ); - } - } + layout.setDragNode(event.nativeEvent, node as TabSetNode); + } else { + event.preventDefault(); + } + } else { + event.preventDefault(); } + }; - let leading : React.ReactNode = undefined; - let stickyButtons: React.ReactNode[] = []; - let buttons: React.ReactNode[] = []; - - // allow customization of header contents and buttons - const renderState: ITabSetRenderValues = { leading, stickyButtons, buttons, overflowPosition: undefined }; - layout.customizeTabSet(node, renderState); - leading = renderState.leading; - stickyButtons = renderState.stickyButtons; - buttons = renderState.buttons; - - const isTabStretch = node.isEnableSingleTabStretch() && node.getChildren().length === 1; - const showClose = (isTabStretch && ((node.getChildren()[0] as TabNode).isEnableClose())) || node.isEnableClose(); - - if (renderState.overflowPosition === undefined) { - renderState.overflowPosition = stickyButtons.length; + const onPointerDown = (event: React.PointerEvent) => { + if (!isAuxMouseEvent(event)) { + layout.doAction( + Actions.setActiveTabset(node.getId(), layout.getWindowId()), + ); } + }; - if (stickyButtons.length > 0) { - if (!node.isEnableTabWrap() && (isDockStickyButtons || isTabStretch)) { - buttons = [...stickyButtons, ...buttons]; - } else { - tabs.push(
{ e.preventDefault() }} - className={cm(CLASSES.FLEXLAYOUT__TAB_TOOLBAR_STICKY_BUTTONS_CONTAINER)} - > - {stickyButtons} -
); - } + const onAuxMouseClick = ( + event: React.MouseEvent, + ) => { + if (isAuxMouseEvent(event)) { + layout.auxMouseClick(node, event); } + }; - if (!node.isEnableTabWrap()) { - if (isShowHiddenTabs) { - const overflowTitle = layout.i18nName(I18nLabel.Overflow_Menu_Tooltip); - let overflowContent; - if (typeof icons.more === "function") { - const items = hiddenTabs.map(h => { return { index: h, node: (node.getChildren()[h] as TabNode) }; }); - overflowContent = icons.more(node, items); - } else { - overflowContent = (<> - {icons.more} -
{hiddenTabs.length > 0 ? hiddenTabs.length : ""}
- ); - } - buttons.splice(Math.min(renderState.overflowPosition, buttons.length), 0, - - ); - } - } + const onContextMenu = (event: React.MouseEvent) => { + layout.showContextMenu(node, event); + }; - if (selectedTabNode !== undefined && - layout.isSupportsPopout() && - selectedTabNode.isEnablePopout() && - selectedTabNode.isEnablePopoutIcon()) { - - const popoutTitle = layout.i18nName(I18nLabel.Popout_Tab); - buttons.push( - - ); - } + const onInterceptPointerDown = (event: React.PointerEvent) => { + event.stopPropagation(); + }; + const onMaximizeToggle = ( + event: React.MouseEvent, + ) => { if (node.canMaximize()) { - const minTitle = layout.i18nName(I18nLabel.Restore); - const maxTitle = layout.i18nName(I18nLabel.Maximize); - buttons.push( - - ); + layout.maximize(node); } - - if (!node.isMaximized() && showClose) { - const title = isTabStretch ? layout.i18nName(I18nLabel.Close_Tab) : layout.i18nName(I18nLabel.Close_Tabset); - buttons.push( - - ); + event.stopPropagation(); + }; + + const onClose = (event: React.MouseEvent) => { + layout.doAction(Actions.deleteTabset(node.getId())); + event.stopPropagation(); + }; + + const onCloseTab = (event: React.MouseEvent) => { + layout.doAction(Actions.deleteTab(node.getChildren()[0].getId())); + event.stopPropagation(); + }; + + const onPopoutTab = (event: React.MouseEvent) => { + if (selectedTabNode !== undefined) { + layout.doAction(Actions.popoutTab(selectedTabNode.getId())); + // layout.doAction(Actions.popoutTabset(node.getId())); } + event.stopPropagation(); + }; - if (node.isActive() && node.isEnableActiveIcon()) { - const title = layout.i18nName(I18nLabel.Active_Tabset); - buttons.push( -
- {(typeof icons.activeTabset === "function") ? icons.activeTabset(node) : icons.activeTabset} -
+ const onDoubleClick = (event: React.MouseEvent) => { + if (node.canMaximize()) { + layout.maximize(node); + } + }; + + // Start Render + + const cm = layout.getClassName; + const selectedTabNode: TabNode = node.getSelectedNode() as TabNode; + const path = node.getPath(); + + const tabs = []; + if (node.isEnableTabStrip()) { + for (let i = 0; i < node.getChildren().length; i++) { + const child = node.getChildren()[i] as TabNode; + const isSelected = node.getSelected() === i; + tabs.push( + , + ); + if (i < node.getChildren().length - 1) { + tabs.push( +
, ); + } } - - const buttonbar = ( -
{ e.preventDefault() }} + } + + let leading: React.ReactNode = undefined; + let stickyButtons: React.ReactNode[] = []; + let buttons: React.ReactNode[] = []; + + // allow customization of header contents and buttons + const renderState: ITabSetRenderValues = { + leading, + stickyButtons, + buttons, + overflowPosition: undefined, + }; + layout.customizeTabSet(node, renderState); + leading = renderState.leading; + stickyButtons = renderState.stickyButtons; + buttons = renderState.buttons; + + const isTabStretch = + node.isEnableSingleTabStretch() && node.getChildren().length === 1; + const showClose = + (isTabStretch && (node.getChildren()[0] as TabNode).isEnableClose()) || + node.isEnableClose(); + + if (renderState.overflowPosition === undefined) { + renderState.overflowPosition = stickyButtons.length; + } + + if (stickyButtons.length > 0) { + if (!node.isEnableTabWrap() && (isDockStickyButtons || isTabStretch)) { + buttons = [...stickyButtons, ...buttons]; + } else { + tabs.push( +
{ + e.preventDefault(); + }} + className={cm( + CLASSES.FLEXLAYOUT__TAB_TOOLBAR_STICKY_BUTTONS_CONTAINER, + )} > - {buttons} -
- ); - - let tabStrip; - - let tabStripClasses = cm(CLASSES.FLEXLAYOUT__TABSET_TABBAR_OUTER); - if (node.getClassNameTabStrip() !== undefined) { - tabStripClasses += " " + node.getClassNameTabStrip(); - } - tabStripClasses += " " + CLASSES.FLEXLAYOUT__TABSET_TABBAR_OUTER_ + node.getTabLocation(); - - if (node.isActive()) { - tabStripClasses += " " + cm(CLASSES.FLEXLAYOUT__TABSET_SELECTED); - } - - if (node.isMaximized()) { - tabStripClasses += " " + cm(CLASSES.FLEXLAYOUT__TABSET_MAXIMIZED); - } - - if (isTabStretch) { - const tabNode = node.getChildren()[0] as TabNode; - if (tabNode.getTabSetClassName() !== undefined) { - tabStripClasses += " " + tabNode.getTabSetClassName(); - } + {stickyButtons} +
, + ); } - - let leadingContainer: React.ReactNode = undefined; - if (leading) { - leadingContainer = ( -
- {leading} + } + + if (!node.isEnableTabWrap()) { + if (isShowHiddenTabs) { + const overflowTitle = layout.i18nName(I18nLabel.Overflow_Menu_Tooltip); + let overflowContent; + if (typeof icons.more === 'function') { + const items = hiddenTabs.map((h) => { + return { index: h, node: node.getChildren()[h] as TabNode }; + }); + overflowContent = icons.more(node, items); + } else { + overflowContent = ( + <> + {icons.more} +
+ {hiddenTabs.length > 0 ? hiddenTabs.length : ''}
+ ); + } + buttons.splice( + Math.min(renderState.overflowPosition, buttons.length), + 0, + , + ); } - - if (node.isEnableTabWrap()) { - if (node.isEnableTabStrip()) { - tabStrip = ( -
- {leadingContainer} - {tabs} -
- {buttonbar} -
- ); + } + + if ( + selectedTabNode !== undefined && + layout.isSupportsPopout() && + selectedTabNode.isEnablePopout() && + selectedTabNode.isEnablePopoutIcon() + ) { + const popoutTitle = layout.i18nName(I18nLabel.Popout_Tab); + buttons.push( + , + ); + } + + if (node.canMaximize()) { + const minTitle = layout.i18nName(I18nLabel.Restore); + const maxTitle = layout.i18nName(I18nLabel.Maximize); + buttons.push( + , + ); + } + + if (!node.isMaximized() && showClose) { + const title = isTabStretch + ? layout.i18nName(I18nLabel.Close_Tab) + : layout.i18nName(I18nLabel.Close_Tabset); + buttons.push( + , + ); + } + + if (node.isActive() && node.isEnableActiveIcon()) { + const title = layout.i18nName(I18nLabel.Active_Tabset); + buttons.push( +
+ {typeof icons.activeTabset === 'function' + ? icons.activeTabset(node) + : icons.activeTabset} +
, + ); + } + + const buttonbar = ( +
{ + e.preventDefault(); + }} + > + {buttons}
+ ); - if (node.getTabLocation() === "top") { - content = <>{tabStrip}{content}; - } else { - content = <>{content}{tabStrip}; - } + let tabStrip; + + let tabStripClasses = cm(CLASSES.FLEXLAYOUT__TABSET_TABBAR_OUTER); + if (node.getClassNameTabStrip() !== undefined) { + tabStripClasses += ' ' + node.getClassNameTabStrip(); + } + tabStripClasses += + ' ' + CLASSES.FLEXLAYOUT__TABSET_TABBAR_OUTER_ + node.getTabLocation(); - const style: Record = { - flexGrow: Math.max(1, node.getWeight() * 1000), - minWidth: node.getMinWidth(), - minHeight: node.getMinHeight(), - maxWidth: node.getMaxWidth(), - maxHeight: node.getMaxHeight() - }; + if (node.isActive()) { + tabStripClasses += ' ' + cm(CLASSES.FLEXLAYOUT__TABSET_SELECTED); + } - if (node.getModel().getMaximizedTabset(layout.getWindowId()) !== undefined && !node.isMaximized()) { - style.display = "none"; + if (node.isMaximized()) { + tabStripClasses += ' ' + cm(CLASSES.FLEXLAYOUT__TABSET_MAXIMIZED); + } + + if (isTabStretch) { + const tabNode = node.getChildren()[0] as TabNode; + if (tabNode.getTabSetClassName() !== undefined) { + tabStripClasses += ' ' + tabNode.getTabSetClassName(); } + } - // note: tabset container is needed to allow flexbox to size without border/padding/margin - // then inner tabset can have border/padding/margin for styling - const tabset = ( -
{leading}
+ ); + } + + if (node.isEnableTabWrap()) { + if (node.isEnableTabStrip()) { + tabStrip = ( +
-
+ {buttonbar} +
+ ); + } + } else { + if (node.isEnableTabStrip()) { + let miniScrollbar = undefined; + if (node.isEnableTabScrollbar()) { + miniScrollbar = ( +
+ ); + } + tabStrip = ( +
+ {leadingContainer} +
+
- {content} +
+ {tabs} +
+ {miniScrollbar} +
+ {buttonbar}
- ); + ); + } + } - if (node.isMaximized()) { - if (layout.getMainElement()) { - return createPortal( -
- {tabset} -
, layout.getMainElement()!); - } else { - return tabset; - } - } else { - return tabset; + let emptyTabset: React.ReactNode; + if (node.getChildren().length === 0) { + const placeHolderCallback = layout.getTabSetPlaceHolderCallback(); + if (placeHolderCallback) { + emptyTabset = placeHolderCallback(node); } + } + let content = ( +
+ {emptyTabset} +
+ ); + + if (node.getTabLocation() === 'top') { + content = ( + <> + {tabStrip} + {content} + + ); + } else { + content = ( + <> + {content} + {tabStrip} + + ); + } + + const style: Record = { + flexGrow: Math.max(1, node.getWeight() * 1000), + minWidth: node.getMinWidth(), + minHeight: node.getMinHeight(), + maxWidth: node.getMaxWidth(), + maxHeight: node.getMaxHeight(), + }; + + if ( + node.getModel().getMaximizedTabset(layout.getWindowId()) !== undefined && + !node.isMaximized() + ) { + style.display = 'none'; + } + + // note: tabset container is needed to allow flexbox to size without border/padding/margin + // then inner tabset can have border/padding/margin for styling + const tabset = ( +
+
+ {content} +
+
+ ); + + if (node.isMaximized()) { + if (layout.getMainElement()) { + return createPortal( +
+ {tabset} +
, + layout.getMainElement()!, + ); + } else { + return tabset; + } + } else { + return tabset; + } }; - - diff --git a/src/view/Utils.tsx b/src/view/Utils.tsx index 7c9654f5..fb33364f 100644 --- a/src/view/Utils.tsx +++ b/src/view/Utils.tsx @@ -1,135 +1,182 @@ -import * as React from "react"; -import { Node } from "../model/Node"; -import { TabNode } from "../model/TabNode"; -import { LayoutInternal } from "./Layout"; -import { TabSetNode } from "../model/TabSetNode"; +import * as React from 'react'; +import { Node } from '../model/Node'; +import { TabNode } from '../model/TabNode'; +import { LayoutInternal } from './Layout'; +import { TabSetNode } from '../model/TabSetNode'; /** @internal */ export function isDesktop() { - const desktop = typeof window !== "undefined" && window.matchMedia && window.matchMedia("(hover: hover) and (pointer: fine)").matches; - return desktop; + const desktop = + typeof window !== 'undefined' && + window.matchMedia && + window.matchMedia('(hover: hover) and (pointer: fine)').matches; + return desktop; } /** @internal */ export function getRenderStateEx( - layout: LayoutInternal, - node: TabNode, - iconAngle?: number + layout: LayoutInternal, + node: TabNode, + iconAngle?: number, ) { - let leadingContent = undefined; - const titleContent: React.ReactNode = node.getName(); - const name = node.getName(); - if (iconAngle === undefined) { - iconAngle = 0; - } + let leadingContent = undefined; + const titleContent: React.ReactNode = node.getName(); + const name = node.getName(); + if (iconAngle === undefined) { + iconAngle = 0; + } - if (leadingContent === undefined && node.getIcon() !== undefined) { - if (iconAngle !== 0) { - leadingContent = leadingContent; - } else { - leadingContent = leadingContent; - } + if (leadingContent === undefined && node.getIcon() !== undefined) { + if (iconAngle !== 0) { + leadingContent = ( + leadingContent + ); + } else { + leadingContent = ( + leadingContent + ); } + } - const buttons: any[] = []; + const buttons: any[] = []; - // allow customization of leading contents (icon) and contents - const renderState = { leading: leadingContent, content: titleContent, name, buttons }; - layout.customizeTab(node, renderState); + // allow customization of leading contents (icon) and contents + const renderState = { + leading: leadingContent, + content: titleContent, + name, + buttons, + }; + layout.customizeTab(node, renderState); - node.setRenderedName(renderState.name); + node.setRenderedName(renderState.name); - return renderState; + return renderState; } /** @internal */ -export function isAuxMouseEvent(event: React.MouseEvent | React.TouchEvent) { - let auxEvent = false; - if (event.nativeEvent instanceof MouseEvent) { - if (event.nativeEvent.button !== 0 || event.ctrlKey || event.altKey || event.metaKey || event.shiftKey) { - auxEvent = true; - } +export function isAuxMouseEvent( + event: + | React.MouseEvent + | React.TouchEvent, +) { + let auxEvent = false; + if (event.nativeEvent instanceof MouseEvent) { + if ( + event.nativeEvent.button !== 0 || + event.ctrlKey || + event.altKey || + event.metaKey || + event.shiftKey + ) { + auxEvent = true; } - return auxEvent; + } + return auxEvent; } -export function enablePointerOnIFrames(enable: boolean, currentDocument: Document) { - const iframes = [ - ...getElementsByTagName('iframe', currentDocument), - ...getElementsByTagName('webview', currentDocument), - ]; +export function enablePointerOnIFrames( + enable: boolean, + currentDocument: Document, +) { + const iframes = [ + ...getElementsByTagName('iframe', currentDocument), + ...getElementsByTagName('webview', currentDocument), + ]; - for (const iframe of iframes) { - (iframe as HTMLElement).style.pointerEvents = enable ? 'auto' : 'none'; - } -}; + for (const iframe of iframes) { + (iframe as HTMLElement).style.pointerEvents = enable ? 'auto' : 'none'; + } +} -export function getElementsByTagName(tag: string, currentDocument: Document): Element[] { - return [...currentDocument.getElementsByTagName(tag)]; +export function getElementsByTagName( + tag: string, + currentDocument: Document, +): Element[] { + return [...currentDocument.getElementsByTagName(tag)]; } export function startDrag( - doc: Document, - event: React.PointerEvent, - drag: (x: number, y: number) => void, - dragEnd: () => void, - dragCancel: () => void) { - - event.preventDefault(); - - const pointerMove = (ev: PointerEvent) => { - ev.preventDefault(); - drag(ev.clientX, ev.clientY); - }; - - const pointerCancel = (ev: PointerEvent) => { - ev.preventDefault(); - dragCancel(); - }; - const pointerUp = () => { - doc.removeEventListener("pointermove", pointerMove); - doc.removeEventListener("pointerup", pointerUp); - doc.removeEventListener("pointercancel", pointerCancel); - dragEnd(); - }; - - doc.addEventListener("pointermove", pointerMove); - doc.addEventListener("pointerup", pointerUp); - doc.addEventListener('pointercancel', pointerCancel); + doc: Document, + event: React.PointerEvent, + drag: (x: number, y: number) => void, + dragEnd: () => void, + dragCancel: () => void, +) { + event.preventDefault(); + + const pointerMove = (ev: PointerEvent) => { + ev.preventDefault(); + drag(ev.clientX, ev.clientY); + }; + + const pointerCancel = (ev: PointerEvent) => { + ev.preventDefault(); + dragCancel(); + }; + const pointerUp = () => { + doc.removeEventListener('pointermove', pointerMove); + doc.removeEventListener('pointerup', pointerUp); + doc.removeEventListener('pointercancel', pointerCancel); + dragEnd(); + }; + + doc.addEventListener('pointermove', pointerMove); + doc.addEventListener('pointerup', pointerUp); + doc.addEventListener('pointercancel', pointerCancel); } export function canDockToWindow(node: Node) { - if (node instanceof TabNode) { - return node.isEnablePopout(); - } else if (node instanceof TabSetNode) { - for (const child of node.getChildren()) { - if ((child as TabNode).isEnablePopout() === false) { - return false; - } - } - return true; + if (node instanceof TabNode) { + return node.isEnablePopout(); + } else if (node instanceof TabSetNode) { + for (const child of node.getChildren()) { + if ((child as TabNode).isEnablePopout() === false) { + return false; + } } - return false; + return true; + } + return false; } -export function copyInlineStyles(source: HTMLElement, target: HTMLElement): boolean { - // Get the inline style attribute from the source element - const sourceStyle = source.getAttribute('style'); - const targetStyle = target.getAttribute('style'); - if (sourceStyle === targetStyle) return false; - - // console.log("copyInlineStyles", sourceStyle); - - if (sourceStyle) { - // Set the style attribute on the target element - target.setAttribute('style', sourceStyle); - } else { - // If the source has no inline style, clear the target's style attribute - target.removeAttribute('style'); - } - return true; +export function copyInlineStyles( + source: HTMLElement, + target: HTMLElement, +): boolean { + // Get the inline style attribute from the source element + const sourceStyle = source.getAttribute('style'); + const targetStyle = target.getAttribute('style'); + if (sourceStyle === targetStyle) return false; + + // console.log("copyInlineStyles", sourceStyle); + + if (sourceStyle) { + // Set the style attribute on the target element + target.setAttribute('style', sourceStyle); + } else { + // If the source has no inline style, clear the target's style attribute + target.removeAttribute('style'); + } + return true; } export function isSafari() { - const userAgent = navigator.userAgent; - return userAgent.includes("Safari") && !userAgent.includes("Chrome") && !userAgent.includes("Chromium"); - } + const userAgent = navigator.userAgent; + return ( + userAgent.includes('Safari') && + !userAgent.includes('Chrome') && + !userAgent.includes('Chromium') + ); +} From e2c9cd851ae3c7f94f77ad894c5c6bd98954001b Mon Sep 17 00:00:00 2001 From: Ralalu <101653470+Ralalu@users.noreply.github.com> Date: Wed, 17 Sep 2025 13:31:13 +0200 Subject: [PATCH 3/3] Revert "prettify" This reverts commit 58a612b404ab2baca6f47046f65789591344c6d7. --- .prettierrc | 10 - src/Attribute.ts | 106 +- src/AttributeDefinitions.ts | 270 ++-- src/DockLocation.ts | 240 ++- src/DropInfo.ts | 38 +- src/I18nLabel.ts | 26 +- src/Orientation.ts | 40 +- src/Rect.ts | 328 ++-- src/model/Action.ts | 12 +- src/model/Actions.ts | 391 +++-- src/model/BorderNode.ts | 1001 ++++++------ src/model/BorderSet.ts | 149 +- src/model/ICloseType.ts | 6 +- src/model/IDraggable.ts | 9 +- src/model/IDropTarget.ts | 30 +- src/model/IJsonModel.ts | 518 +++---- src/model/LayoutWindow.ts | 244 ++- src/model/Model.ts | 1525 +++++++++---------- src/model/Node.ts | 531 +++---- src/model/RowNode.ts | 1074 ++++++------- src/model/TabNode.ts | 899 ++++++----- src/model/TabSetNode.ts | 1248 +++++++-------- src/model/Utils.ts | 90 +- src/view/BorderButton.tsx | 382 +++-- src/view/BorderTab.tsx | 193 ++- src/view/BorderTabSet.tsx | 661 ++++---- src/view/DragContainer.tsx | 39 +- src/view/ErrorBoundary.tsx | 86 +- src/view/Icons.tsx | 257 +--- src/view/Layout.tsx | 2768 +++++++++++++++------------------- src/view/Overlay.tsx | 25 +- src/view/PopoutWindow.tsx | 303 ++-- src/view/PopupMenu.tsx | 284 ++-- src/view/Row.tsx | 108 +- src/view/SizeTracker.tsx | 56 +- src/view/Splitter.tsx | 498 +++--- src/view/Tab.tsx | 239 ++- src/view/TabButton.tsx | 385 +++-- src/view/TabButtonStamp.tsx | 68 +- src/view/TabOverflowHook.tsx | 559 ++++--- src/view/TabSet.tsx | 986 ++++++------ src/view/Utils.tsx | 251 ++- 42 files changed, 7675 insertions(+), 9258 deletions(-) delete mode 100644 .prettierrc diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index 213c7f6c..00000000 --- a/.prettierrc +++ /dev/null @@ -1,10 +0,0 @@ -{ - "arrowParens": "always", - "printWidth": 80, - "proseWrap": "never", - "semi": true, - "singleQuote": true, - "tabWidth": 2, - "trailingComma": "all", - "useTabs": false -} diff --git a/src/Attribute.ts b/src/Attribute.ts index 4e8c438b..9cd91d55 100755 --- a/src/Attribute.ts +++ b/src/Attribute.ts @@ -1,68 +1,64 @@ /** @internal */ export class Attribute { - static NUMBER = 'number'; - static STRING = 'string'; - static BOOLEAN = 'boolean'; + static NUMBER = "number"; + static STRING = "string"; + static BOOLEAN = "boolean"; - name: string; - alias: string | undefined; - modelName?: string; - pairedAttr?: Attribute; - pairedType?: string; - defaultValue: any; - alwaysWriteJson?: boolean; - type?: string; - required: boolean; - fixed: boolean; - description?: string; + name: string; + alias: string | undefined; + modelName?: string; + pairedAttr?: Attribute; + pairedType?: string; + defaultValue: any; + alwaysWriteJson?: boolean; + type?: string; + required: boolean; + fixed: boolean; + description?: string; - constructor( - name: string, - modelName: string | undefined, - defaultValue: any, - alwaysWriteJson?: boolean, - ) { - this.name = name; - this.alias = undefined; - this.modelName = modelName; - this.defaultValue = defaultValue; - this.alwaysWriteJson = alwaysWriteJson; - this.required = false; - this.fixed = false; + constructor(name: string, modelName: string | undefined, defaultValue: any, alwaysWriteJson?: boolean) { + this.name = name; + this.alias = undefined; + this.modelName = modelName; + this.defaultValue = defaultValue; + this.alwaysWriteJson = alwaysWriteJson; + this.required = false; + this.fixed = false; - this.type = 'any'; - } + this.type = "any"; + } - setType(value: string) { - this.type = value; - return this; - } + setType(value: string) { + this.type = value; + return this; + } - setAlias(value: string) { - this.alias = value; - return this; - } + setAlias(value: string) { + this.alias = value; + return this; + } - setDescription(value: string) { - this.description = value; - } + setDescription(value: string) { + this.description = value; + } - setRequired() { - this.required = true; - return this; - } + setRequired() { + this.required = true; + return this; + } - setFixed() { - this.fixed = true; - return this; - } + setFixed() { + this.fixed = true; + return this; + } - // sets modelAttr for nodes, and nodeAttr for model - setPairedAttr(value: Attribute) { - this.pairedAttr = value; - } + // sets modelAttr for nodes, and nodeAttr for model + setpairedAttr(value: Attribute) { + this.pairedAttr = value; + } + + setPairedType(value: string) { + this.pairedType = value; + } - setPairedType(value: string) { - this.pairedType = value; - } } diff --git a/src/AttributeDefinitions.ts b/src/AttributeDefinitions.ts index 8247738d..4e7ea196 100755 --- a/src/AttributeDefinitions.ts +++ b/src/AttributeDefinitions.ts @@ -1,154 +1,144 @@ -import { Attribute } from './Attribute'; +import { Attribute } from "./Attribute"; /** @internal */ export class AttributeDefinitions { - attributes: Attribute[]; - nameToAttribute: Map; - - constructor() { - this.attributes = []; - this.nameToAttribute = new Map(); - } - - addWithAll( - name: string, - modelName: string | undefined, - defaultValue: any, - alwaysWriteJson?: boolean, - ) { - const attr = new Attribute(name, modelName, defaultValue, alwaysWriteJson); - this.attributes.push(attr); - this.nameToAttribute.set(name, attr); - return attr; - } - - addInherited(name: string, modelName: string) { - return this.addWithAll(name, modelName, undefined, false); - } - - add(name: string, defaultValue: any, alwaysWriteJson?: boolean) { - return this.addWithAll(name, undefined, defaultValue, alwaysWriteJson); - } - - getAttributes() { - return this.attributes; - } - - getModelName(name: string) { - const conversion = this.nameToAttribute.get(name); - if (conversion !== undefined) { - return conversion.modelName; + attributes: Attribute[]; + nameToAttribute: Map; + + constructor() { + this.attributes = []; + this.nameToAttribute = new Map(); + } + + addWithAll(name: string, modelName: string | undefined, defaultValue: any, alwaysWriteJson?: boolean) { + const attr = new Attribute(name, modelName, defaultValue, alwaysWriteJson); + this.attributes.push(attr); + this.nameToAttribute.set(name, attr); + return attr; } - return undefined; - } - - toJson(jsonObj: any, obj: any) { - for (const attr of this.attributes) { - const fromValue = obj[attr.name]; - if (attr.alwaysWriteJson || fromValue !== attr.defaultValue) { - jsonObj[attr.name] = fromValue; - } + + addInherited(name: string, modelName: string) { + return this.addWithAll(name, modelName, undefined, false); + } + + add(name: string, defaultValue: any, alwaysWriteJson?: boolean) { + return this.addWithAll(name, undefined, defaultValue, alwaysWriteJson); + } + + getAttributes() { + return this.attributes; + } + + getModelName(name: string) { + const conversion = this.nameToAttribute.get(name); + if (conversion !== undefined) { + return conversion.modelName; + } + return undefined; } - } - - fromJson(jsonObj: any, obj: any) { - for (const attr of this.attributes) { - let fromValue = jsonObj[attr.name]; - if (fromValue === undefined && attr.alias) { - fromValue = jsonObj[attr.alias]; - } - if (fromValue === undefined) { - obj[attr.name] = attr.defaultValue; - } else { - obj[attr.name] = fromValue; - } + + toJson(jsonObj: any, obj: any) { + for (const attr of this.attributes) { + const fromValue = obj[attr.name]; + if (attr.alwaysWriteJson || fromValue !== attr.defaultValue) { + jsonObj[attr.name] = fromValue; + } + } } - } - - update(jsonObj: any, obj: any) { - for (const attr of this.attributes) { - if (Object.prototype.hasOwnProperty.call(jsonObj, attr.name)) { - const fromValue = jsonObj[attr.name]; - if (fromValue === undefined) { - delete obj[attr.name]; - } else { - obj[attr.name] = fromValue; + + fromJson(jsonObj: any, obj: any) { + for (const attr of this.attributes) { + let fromValue = jsonObj[attr.name]; + if (fromValue === undefined && attr.alias) { + fromValue = jsonObj[attr.alias]; + } + if (fromValue === undefined) { + obj[attr.name] = attr.defaultValue; + } else { + obj[attr.name] = fromValue; + } } - } } - } - setDefaults(obj: any) { - for (const attr of this.attributes) { - obj[attr.name] = attr.defaultValue; + update(jsonObj: any, obj: any) { + for (const attr of this.attributes) { + if (Object.prototype.hasOwnProperty.call(jsonObj, attr.name)) { + const fromValue = jsonObj[attr.name]; + if (fromValue === undefined) { + delete obj[attr.name]; + } else { + obj[attr.name] = fromValue; + } + } + } } - } - - pairAttributes(type: string, childAttributes: AttributeDefinitions) { - for (const attr of childAttributes.attributes) { - if (attr.modelName && this.nameToAttribute.has(attr.modelName)) { - const pairedAttr = this.nameToAttribute.get(attr.modelName)!; - pairedAttr.setPairedAttr(attr); - attr.setPairedAttr(pairedAttr); - pairedAttr.setPairedType(type); - } + + setDefaults(obj: any) { + for (const attr of this.attributes) { + obj[attr.name] = attr.defaultValue; + } } - } - - toTypescriptInterface( - name: string, - parentAttributes: AttributeDefinitions | undefined, - ) { - const lines = []; - const sorted = this.attributes.sort((a, b) => a.name.localeCompare(b.name)); - // const sorted = this.attributes; - lines.push('export interface I' + name + 'Attributes {'); - for (let i = 0; i < sorted.length; i++) { - const c = sorted[i]; - let type = c.type; - let defaultValue = undefined; - - let attr = c; - let inherited = undefined; - if (attr.defaultValue !== undefined) { - defaultValue = attr.defaultValue; - } else if ( - attr.modelName !== undefined && - parentAttributes !== undefined && - parentAttributes.nameToAttribute.get(attr.modelName) !== undefined - ) { - inherited = attr.modelName; - attr = parentAttributes.nameToAttribute.get(inherited)!; - defaultValue = attr.defaultValue; - type = attr.type; - } - - const defValue = JSON.stringify(defaultValue); - - const required = attr.required ? '' : '?'; - - let sb = '\t/**\n\t '; - if (c.description) { - sb += c.description; - } else if (c.pairedType && c.pairedAttr?.description) { - sb += `Value for ${c.pairedType} attribute ${c.pairedAttr.name} if not overridden`; - sb += '\n\n\t '; - sb += c.pairedAttr?.description; - } - sb += '\n\n\t '; - if (c.fixed) { - sb += `Fixed value: ${defValue}`; - } else if (inherited) { - sb += `Default: inherited from Global attribute ${c.modelName} (default ${defValue})`; - } else { - sb += `Default: ${defValue}`; - } - sb += '\n\t */'; - lines.push(sb); - lines.push('\t' + c.name + required + ': ' + type + ';\n'); + + pairAttributes(type: string, childAttributes: AttributeDefinitions) { + for (const attr of childAttributes.attributes) { + if (attr.modelName && this.nameToAttribute.has(attr.modelName)) { + const pairedAttr = this.nameToAttribute.get(attr.modelName)!; + pairedAttr.setpairedAttr(attr); + attr.setpairedAttr(pairedAttr); + pairedAttr.setPairedType(type); + } + } } - lines.push('}'); - return lines.join('\n'); - } + toTypescriptInterface(name: string, parentAttributes: AttributeDefinitions | undefined) { + const lines = []; + const sorted = this.attributes.sort((a, b) => a.name.localeCompare(b.name)); + // const sorted = this.attributes; + lines.push("export interface I" + name + "Attributes {"); + for (let i = 0; i < sorted.length; i++) { + const c = sorted[i]; + let type = c.type; + let defaultValue = undefined; + + let attr = c; + let inherited = undefined; + if (attr.defaultValue !== undefined) { + defaultValue = attr.defaultValue; + } else if (attr.modelName !== undefined + && parentAttributes !== undefined + && parentAttributes.nameToAttribute.get(attr.modelName) !== undefined) { + inherited = attr.modelName; + attr = parentAttributes.nameToAttribute.get(inherited)!; + defaultValue = attr.defaultValue; + type = attr.type; + } + + const defValue = JSON.stringify(defaultValue); + + const required = attr.required ? "" : "?"; + + let sb = "\t/**\n\t "; + if (c.description) { + sb += c.description; + } else if (c.pairedType && c.pairedAttr?.description) { + sb += `Value for ${c.pairedType} attribute ${c.pairedAttr.name} if not overridden` + sb += "\n\n\t "; + sb += c.pairedAttr?.description; + } + sb += "\n\n\t "; + if (c.fixed) { + sb += `Fixed value: ${defValue}`; + } else if (inherited) { + sb += `Default: inherited from Global attribute ${c.modelName} (default ${defValue})`; + } else { + sb += `Default: ${defValue}`; + } + sb += "\n\t */"; + lines.push(sb); + lines.push("\t" + c.name + required + ": " + type + ";\n"); + } + lines.push("}"); + + return lines.join("\n"); + } } diff --git a/src/DockLocation.ts b/src/DockLocation.ts index 6dd96949..25d6af3d 100755 --- a/src/DockLocation.ts +++ b/src/DockLocation.ts @@ -1,149 +1,133 @@ -import { Orientation } from './Orientation'; -import { Rect } from './Rect'; +import { Orientation } from "./Orientation"; +import { Rect } from "./Rect"; export class DockLocation { - static values = new Map(); - static TOP = new DockLocation('top', Orientation.VERT, 0); - static BOTTOM = new DockLocation('bottom', Orientation.VERT, 1); - static LEFT = new DockLocation('left', Orientation.HORZ, 0); - static RIGHT = new DockLocation('right', Orientation.HORZ, 1); - static CENTER = new DockLocation('center', Orientation.VERT, 0); + static values = new Map(); + static TOP = new DockLocation("top", Orientation.VERT, 0); + static BOTTOM = new DockLocation("bottom", Orientation.VERT, 1); + static LEFT = new DockLocation("left", Orientation.HORZ, 0); + static RIGHT = new DockLocation("right", Orientation.HORZ, 1); + static CENTER = new DockLocation("center", Orientation.VERT, 0); - /** @internal */ - static getByName(name: string): DockLocation { - return DockLocation.values.get(name)!; - } - - /** @internal */ - static getLocation(rect: Rect, x: number, y: number) { - x = (x - rect.x) / rect.width; - y = (y - rect.y) / rect.height; - - if (x >= 0.25 && x < 0.75 && y >= 0.25 && y < 0.75) { - return DockLocation.CENTER; + /** @internal */ + static getByName(name: string): DockLocation { + return DockLocation.values.get(name)!; } - // Whether or not the point is in the bottom-left half of the rect - // +-----+ - // |\ | - // |x\ | - // |xx\ | - // |xxx\ | - // |xxxx\| - // +-----+ - const bl = y >= x; + /** @internal */ + static getLocation(rect: Rect, x: number, y: number) { + x = (x - rect.x) / rect.width; + y = (y - rect.y) / rect.height; - // Whether or not the point is in the bottom-right half of the rect - // +-----+ - // | /| - // | /x| - // | /xx| - // | /xxx| - // |/xxxx| - // +-----+ - const br = y >= 1 - x; + if (x >= 0.25 && x < 0.75 && y >= 0.25 && y < 0.75) { + return DockLocation.CENTER; + } - if (bl) { - return br ? DockLocation.BOTTOM : DockLocation.LEFT; - } else { - return br ? DockLocation.RIGHT : DockLocation.TOP; - } - } + // Whether or not the point is in the bottom-left half of the rect + // +-----+ + // |\ | + // |x\ | + // |xx\ | + // |xxx\ | + // |xxxx\| + // +-----+ + const bl = y >= x; - /** @internal */ - name: string; - /** @internal */ - orientation: Orientation; - /** @internal */ - indexPlus: number; + // Whether or not the point is in the bottom-right half of the rect + // +-----+ + // | /| + // | /x| + // | /xx| + // | /xxx| + // |/xxxx| + // +-----+ + const br = y >= 1 - x; - /** @internal */ - constructor(_name: string, _orientation: Orientation, _indexPlus: number) { - this.name = _name; - this.orientation = _orientation; - this.indexPlus = _indexPlus; - DockLocation.values.set(this.name, this); - } - - getName() { - return this.name; - } + if (bl) { + return br ? DockLocation.BOTTOM : DockLocation.LEFT; + } else { + return br ? DockLocation.RIGHT : DockLocation.TOP; + } + } - getOrientation() { - return this.orientation; - } + /** @internal */ + name: string; + /** @internal */ + orientation: Orientation; + /** @internal */ + indexPlus: number; - /** @internal */ - getDockRect(r: Rect) { - if (this === DockLocation.TOP) { - return new Rect(r.x, r.y, r.width, r.height / 2); - } else if (this === DockLocation.BOTTOM) { - return new Rect(r.x, r.getBottom() - r.height / 2, r.width, r.height / 2); + /** @internal */ + constructor(_name: string, _orientation: Orientation, _indexPlus: number) { + this.name = _name; + this.orientation = _orientation; + this.indexPlus = _indexPlus; + DockLocation.values.set(this.name, this); } - if (this === DockLocation.LEFT) { - return new Rect(r.x, r.y, r.width / 2, r.height); - } else if (this === DockLocation.RIGHT) { - return new Rect(r.getRight() - r.width / 2, r.y, r.width / 2, r.height); - } else { - return r.clone(); + + getName() { + return this.name; } - } - /** @internal */ - split(rect: Rect, size: number) { - if (this === DockLocation.TOP) { - const r1 = new Rect(rect.x, rect.y, rect.width, size); - const r2 = new Rect( - rect.x, - rect.y + size, - rect.width, - rect.height - size, - ); - return { start: r1, end: r2 }; - } else if (this === DockLocation.LEFT) { - const r1 = new Rect(rect.x, rect.y, size, rect.height); - const r2 = new Rect( - rect.x + size, - rect.y, - rect.width - size, - rect.height, - ); - return { start: r1, end: r2 }; + getOrientation() { + return this.orientation; } - if (this === DockLocation.RIGHT) { - const r1 = new Rect(rect.getRight() - size, rect.y, size, rect.height); - const r2 = new Rect(rect.x, rect.y, rect.width - size, rect.height); - return { start: r1, end: r2 }; - } else { - // if (this === DockLocation.BOTTOM) { - const r1 = new Rect(rect.x, rect.getBottom() - size, rect.width, size); - const r2 = new Rect(rect.x, rect.y, rect.width, rect.height - size); - return { start: r1, end: r2 }; + + /** @internal */ + getDockRect(r: Rect) { + if (this === DockLocation.TOP) { + return new Rect(r.x, r.y, r.width, r.height / 2); + } else if (this === DockLocation.BOTTOM) { + return new Rect(r.x, r.getBottom() - r.height / 2, r.width, r.height / 2); + } + if (this === DockLocation.LEFT) { + return new Rect(r.x, r.y, r.width / 2, r.height); + } else if (this === DockLocation.RIGHT) { + return new Rect(r.getRight() - r.width / 2, r.y, r.width / 2, r.height); + } else { + return r.clone(); + } } - } - /** @internal */ - reflect() { - if (this === DockLocation.TOP) { - return DockLocation.BOTTOM; - } else if (this === DockLocation.LEFT) { - return DockLocation.RIGHT; + /** @internal */ + split(rect: Rect, size: number) { + if (this === DockLocation.TOP) { + const r1 = new Rect(rect.x, rect.y, rect.width, size); + const r2 = new Rect(rect.x, rect.y + size, rect.width, rect.height - size); + return { start: r1, end: r2 }; + } else if (this === DockLocation.LEFT) { + const r1 = new Rect(rect.x, rect.y, size, rect.height); + const r2 = new Rect(rect.x + size, rect.y, rect.width - size, rect.height); + return { start: r1, end: r2 }; + } + if (this === DockLocation.RIGHT) { + const r1 = new Rect(rect.getRight() - size, rect.y, size, rect.height); + const r2 = new Rect(rect.x, rect.y, rect.width - size, rect.height); + return { start: r1, end: r2 }; + } else { + // if (this === DockLocation.BOTTOM) { + const r1 = new Rect(rect.x, rect.getBottom() - size, rect.width, size); + const r2 = new Rect(rect.x, rect.y, rect.width, rect.height - size); + return { start: r1, end: r2 }; + } } - if (this === DockLocation.RIGHT) { - return DockLocation.LEFT; - } else { - // if (this === DockLocation.BOTTOM) { - return DockLocation.TOP; + + /** @internal */ + reflect() { + if (this === DockLocation.TOP) { + return DockLocation.BOTTOM; + } else if (this === DockLocation.LEFT) { + return DockLocation.RIGHT; + } + if (this === DockLocation.RIGHT) { + return DockLocation.LEFT; + } else { + // if (this === DockLocation.BOTTOM) { + return DockLocation.TOP; + } } - } - toString() { - return ( - '(DockLocation: name=' + - this.name + - ', orientation=' + - this.orientation + - ')' - ); - } + toString() { + return "(DockLocation: name=" + this.name + ", orientation=" + this.orientation + ")"; + } } diff --git a/src/DropInfo.ts b/src/DropInfo.ts index d1390a2e..fe56efa1 100755 --- a/src/DropInfo.ts +++ b/src/DropInfo.ts @@ -1,26 +1,20 @@ -import { DockLocation } from './DockLocation'; -import { IDropTarget } from './model/IDropTarget'; -import { Node } from './model/Node'; -import { Rect } from './Rect'; +import { DockLocation } from "./DockLocation"; +import { IDropTarget } from "./model/IDropTarget"; +import { Node } from "./model/Node"; +import { Rect } from "./Rect"; export class DropInfo { - node: Node & IDropTarget; - rect: Rect; - location: DockLocation; - index: number; - className: string; + node: Node & IDropTarget; + rect: Rect; + location: DockLocation; + index: number; + className: string; - constructor( - node: Node & IDropTarget, - rect: Rect, - location: DockLocation, - index: number, - className: string, - ) { - this.node = node; - this.rect = rect; - this.location = location; - this.index = index; - this.className = className; - } + constructor(node: Node & IDropTarget, rect: Rect, location: DockLocation, index: number, className: string) { + this.node = node; + this.rect = rect; + this.location = location; + this.index = index; + this.className = className; + } } diff --git a/src/I18nLabel.ts b/src/I18nLabel.ts index c3c026ca..94fc1d22 100644 --- a/src/I18nLabel.ts +++ b/src/I18nLabel.ts @@ -1,15 +1,15 @@ export enum I18nLabel { - Close_Tab = 'Close', - Close_Tabset = 'Close tab set', - Active_Tabset = 'Active tab set', - Move_Tabset = 'Move tab set', - Move_Tabs = 'Move tabs(?)', - Maximize = 'Maximize tab set', - Restore = 'Restore tab set', - Popout_Tab = 'Popout selected tab', - Pin_Tab = 'Pin tab', - Unpin_Tab = 'Unpin tab', - Overflow_Menu_Tooltip = 'Hidden tabs', - Error_rendering_component = 'Error rendering component', - Error_rendering_component_retry = 'Retry', + Close_Tab = "Close", + Close_Tabset = "Close tab set", + Active_Tabset = "Active tab set", + Move_Tabset = "Move tab set", + Move_Tabs = "Move tabs(?)", + Maximize = "Maximize tab set", + Restore = "Restore tab set", + Popout_Tab = "Popout selected tab", + Pin_Tab = "Pin tab", + Unpin_Tab = "Unpin tab", + Overflow_Menu_Tooltip = "Hidden tabs", + Error_rendering_component = "Error rendering component", + Error_rendering_component_retry = "Retry", } diff --git a/src/Orientation.ts b/src/Orientation.ts index d4129093..102cdabe 100755 --- a/src/Orientation.ts +++ b/src/Orientation.ts @@ -1,28 +1,28 @@ export class Orientation { - static HORZ = new Orientation('horz'); - static VERT = new Orientation('vert'); + static HORZ = new Orientation("horz"); + static VERT = new Orientation("vert"); - static flip(from: Orientation) { - if (from === Orientation.HORZ) { - return Orientation.VERT; - } else { - return Orientation.HORZ; + static flip(from: Orientation) { + if (from === Orientation.HORZ) { + return Orientation.VERT; + } else { + return Orientation.HORZ; + } } - } - /** @internal */ - private _name: string; + /** @internal */ + private _name: string; - /** @internal */ - private constructor(name: string) { - this._name = name; - } + /** @internal */ + private constructor(name: string) { + this._name = name; + } - getName() { - return this._name; - } + getName() { + return this._name; + } - toString() { - return this._name; - } + toString() { + return this._name; + } } diff --git a/src/Rect.ts b/src/Rect.ts index da9dd3e4..0bcf8ad4 100755 --- a/src/Rect.ts +++ b/src/Rect.ts @@ -1,187 +1,147 @@ -import { IJsonRect } from './model/IJsonModel'; -import { Orientation } from './Orientation'; +import { IJsonRect } from "./model/IJsonModel"; +import { Orientation } from "./Orientation"; export class Rect { - static empty() { - return new Rect(0, 0, 0, 0); - } - - static fromJson(json: IJsonRect): Rect { - return new Rect(json.x, json.y, json.width, json.height); - } - - x: number; - y: number; - width: number; - height: number; - - constructor(x: number, y: number, width: number, height: number) { - this.x = x; - this.y = y; - this.width = width; - this.height = height; - } - - toJson() { - return { x: this.x, y: this.y, width: this.width, height: this.height }; - } - - snap(round: number) { - this.x = Math.round(this.x / round) * round; - this.y = Math.round(this.y / round) * round; - this.width = Math.round(this.width / round) * round; - this.height = Math.round(this.height / round) * round; - } - - static getBoundingClientRect(element: Element) { - const { x, y, width, height } = element.getBoundingClientRect(); - return new Rect(x, y, width, height); - } - - static getContentRect(element: HTMLElement) { - const rect = element.getBoundingClientRect(); - const style = window.getComputedStyle(element); - - const paddingLeft = parseFloat(style.paddingLeft); - const paddingRight = parseFloat(style.paddingRight); - const paddingTop = parseFloat(style.paddingTop); - const paddingBottom = parseFloat(style.paddingBottom); - const borderLeftWidth = parseFloat(style.borderLeftWidth); - const borderRightWidth = parseFloat(style.borderRightWidth); - const borderTopWidth = parseFloat(style.borderTopWidth); - const borderBottomWidth = parseFloat(style.borderBottomWidth); - - const contentWidth = - rect.width - - borderLeftWidth - - paddingLeft - - paddingRight - - borderRightWidth; - const contentHeight = - rect.height - - borderTopWidth - - paddingTop - - paddingBottom - - borderBottomWidth; - - return new Rect( - rect.left + borderLeftWidth + paddingLeft, - rect.top + borderTopWidth + paddingTop, - contentWidth, - contentHeight, - ); - } - - static fromDomRect(domRect: DOMRect) { - return new Rect(domRect.x, domRect.y, domRect.width, domRect.height); - } - - relativeTo(r: Rect | DOMRect) { - return new Rect(this.x - r.x, this.y - r.y, this.width, this.height); - } - - clone() { - return new Rect(this.x, this.y, this.width, this.height); - } - - equals(rect: Rect | null | undefined) { - return ( - this.x === rect?.x && - this.y === rect?.y && - this.width === rect?.width && - this.height === rect?.height - ); - } - - equalSize(rect: Rect | null | undefined) { - return this.width === rect?.width && this.height === rect?.height; - } - - getBottom() { - return this.y + this.height; - } - - getRight() { - return this.x + this.width; - } - - get bottom() { - return this.y + this.height; - } - - get right() { - return this.x + this.width; - } - - getCenter() { - return { x: this.x + this.width / 2, y: this.y + this.height / 2 }; - } - - positionElement(element: HTMLElement, position?: string) { - this.styleWithPosition(element.style, position); - } - - styleWithPosition(style: Record, position: string = 'absolute') { - style.left = this.x + 'px'; - style.top = this.y + 'px'; - style.width = Math.max(0, this.width) + 'px'; // need Math.max to prevent -ve, cause error in IE - style.height = Math.max(0, this.height) + 'px'; - style.position = position; - return style; - } - - contains(x: number, y: number) { - if ( - this.x <= x && - x <= this.getRight() && - this.y <= y && - y <= this.getBottom() - ) { - return true; - } else { - return false; - } - } - - removeInsets(insets: { - top: number; - left: number; - bottom: number; - right: number; - }) { - return new Rect( - this.x + insets.left, - this.y + insets.top, - Math.max(0, this.width - insets.left - insets.right), - Math.max(0, this.height - insets.top - insets.bottom), - ); - } - - centerInRect(outerRect: Rect) { - this.x = (outerRect.width - this.width) / 2; - this.y = (outerRect.height - this.height) / 2; - } - - /** @internal */ - _getSize(orientation: Orientation) { - let prefSize = this.width; - if (orientation === Orientation.VERT) { - prefSize = this.height; - } - return prefSize; - } - - toString() { - return ( - '(Rect: x=' + - this.x + - ', y=' + - this.y + - ', width=' + - this.width + - ', height=' + - this.height + - ')' - ); - } + static empty() { + return new Rect(0, 0, 0, 0); + } + + static fromJson(json: IJsonRect): Rect { + return new Rect(json.x, json.y, json.width, json.height); + } + + x: number; + y: number; + width: number; + height: number; + + constructor(x: number, y: number, width: number, height: number) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + } + + toJson() { + return {x: this.x, y: this.y, width: this.width, height: this.height}; + } + + snap(round: number) { + this.x = Math.round(this.x / round) * round; + this.y = Math.round(this.y / round) * round; + this.width = Math.round(this.width / round) * round; + this.height= Math.round(this.height / round) * round; + } + + static getBoundingClientRect(element: Element) { + const { x, y, width, height } = element.getBoundingClientRect(); + return new Rect(x, y, width, height); + } + + static getContentRect(element: HTMLElement) { + const rect = element.getBoundingClientRect(); + const style = window.getComputedStyle(element); + + const paddingLeft = parseFloat(style.paddingLeft); + const paddingRight = parseFloat(style.paddingRight); + const paddingTop = parseFloat(style.paddingTop); + const paddingBottom = parseFloat(style.paddingBottom); + const borderLeftWidth = parseFloat(style.borderLeftWidth); + const borderRightWidth = parseFloat(style.borderRightWidth); + const borderTopWidth = parseFloat(style.borderTopWidth); + const borderBottomWidth = parseFloat(style.borderBottomWidth); + + const contentWidth = rect.width - borderLeftWidth - paddingLeft - paddingRight - borderRightWidth; + const contentHeight = rect.height - borderTopWidth - paddingTop - paddingBottom - borderBottomWidth; + + return new Rect( + rect.left + borderLeftWidth + paddingLeft, + rect.top + borderTopWidth + paddingTop, + contentWidth, + contentHeight, + ); + } + + static fromDomRect(domRect: DOMRect) { + return new Rect(domRect.x, domRect.y, domRect.width, domRect.height); + } + + relativeTo(r: Rect | DOMRect) { + return new Rect(this.x - r.x, this.y - r.y, this.width, this.height); + } + + clone() { + return new Rect(this.x, this.y, this.width, this.height); + } + + equals(rect: Rect | null | undefined) { + return this.x === rect?.x && this.y === rect?.y && this.width === rect?.width && this.height === rect?.height + } + + equalSize(rect: Rect | null | undefined) { + return this.width === rect?.width && this.height === rect?.height + } + + getBottom() { + return this.y + this.height; + } + + getRight() { + return this.x + this.width; + } + + get bottom() { + return this.y + this.height; + } + + get right() { + return this.x + this.width; + } + + getCenter() { + return { x: this.x + this.width / 2, y: this.y + this.height / 2 }; + } + + positionElement(element: HTMLElement, position?: string) { + this.styleWithPosition(element.style, position); + } + + styleWithPosition(style: Record, position: string = "absolute") { + style.left = this.x + "px"; + style.top = this.y + "px"; + style.width = Math.max(0, this.width) + "px"; // need Math.max to prevent -ve, cause error in IE + style.height = Math.max(0, this.height) + "px"; + style.position = position; + return style; + } + + contains(x: number, y: number) { + if (this.x <= x && x <= this.getRight() && this.y <= y && y <= this.getBottom()) { + return true; + } else { + return false; + } + } + + removeInsets(insets: { top: number; left: number; bottom: number; right: number }) { + return new Rect(this.x + insets.left, this.y + insets.top, Math.max(0, this.width - insets.left - insets.right), Math.max(0, this.height - insets.top - insets.bottom)); + } + + centerInRect(outerRect: Rect) { + this.x = (outerRect.width - this.width) / 2; + this.y = (outerRect.height - this.height) / 2; + } + + /** @internal */ + _getSize(orientation: Orientation) { + let prefSize = this.width; + if (orientation === Orientation.VERT) { + prefSize = this.height; + } + return prefSize; + } + + toString() { + return "(Rect: x=" + this.x + ", y=" + this.y + ", width=" + this.width + ", height=" + this.height + ")"; + } } diff --git a/src/model/Action.ts b/src/model/Action.ts index ddf3dab7..a8a9412d 100755 --- a/src/model/Action.ts +++ b/src/model/Action.ts @@ -1,9 +1,9 @@ export class Action { - type: string; - data: Record; + type: string; + data: Record; - constructor(type: string, data: Record) { - this.type = type; - this.data = data; - } + constructor(type: string, data: Record) { + this.type = type; + this.data = data; + } } diff --git a/src/model/Actions.ts b/src/model/Actions.ts index 99deb7e0..506ff840 100755 --- a/src/model/Actions.ts +++ b/src/model/Actions.ts @@ -1,215 +1,188 @@ -import { DockLocation } from '../DockLocation'; -import { Action } from './Action'; -import { IJsonRect, IJsonRowNode } from './IJsonModel'; +import { DockLocation } from "../DockLocation"; +import { Action } from "./Action"; +import { IJsonRect, IJsonRowNode } from "./IJsonModel"; /** * The Action creator class for FlexLayout model actions */ export class Actions { - static ADD_NODE = 'FlexLayout_AddNode'; - static MOVE_NODE = 'FlexLayout_MoveNode'; - static DELETE_TAB = 'FlexLayout_DeleteTab'; - static DELETE_TABSET = 'FlexLayout_DeleteTabset'; - static RENAME_TAB = 'FlexLayout_RenameTab'; - static SELECT_TAB = 'FlexLayout_SelectTab'; - static SET_ACTIVE_TABSET = 'FlexLayout_SetActiveTabset'; - static ADJUST_WEIGHTS = 'FlexLayout_AdjustWeights'; - static ADJUST_BORDER_SPLIT = 'FlexLayout_AdjustBorderSplit'; - static MAXIMIZE_TOGGLE = 'FlexLayout_MaximizeToggle'; - static UPDATE_MODEL_ATTRIBUTES = 'FlexLayout_UpdateModelAttributes'; - static UPDATE_NODE_ATTRIBUTES = 'FlexLayout_UpdateNodeAttributes'; - static POPOUT_TAB = 'FlexLayout_PopoutTab'; - static POPOUT_TABSET = 'FlexLayout_PopoutTabset'; - static CLOSE_WINDOW = 'FlexLayout_CloseWindow'; - static CREATE_WINDOW = 'FlexLayout_CreateWindow'; - - /** - * Adds a tab node to the given tabset node - * @param json the json for the new tab node e.g {type:"tab", component:"table"} - * @param toNodeId the new tab node will be added to the tabset with this node id - * @param location the location where the new tab will be added, one of the DockLocation enum values. - * @param index for docking to the center this value is the index of the tab, use -1 to add to the end. - * @param select (optional) whether to select the new tab, overriding autoSelectTab - * @returns {Action} the action - */ - static addNode( - json: any, - toNodeId: string, - location: DockLocation, - index: number, - select?: boolean, - ): Action { - return new Action(Actions.ADD_NODE, { - json, - toNode: toNodeId, - location: location.getName(), - index, - select, - }); - } - - /** - * Moves a node (tab or tabset) from one location to another - * @param fromNodeId the id of the node to move - * @param toNodeId the id of the node to receive the moved node - * @param location the location where the moved node will be added, one of the DockLocation enum values. - * @param index for docking to the center this value is the index of the tab, use -1 to add to the end. - * @param select (optional) whether to select the moved tab(s) in new tabset, overriding autoSelectTab - * @returns {Action} the action - */ - static moveNode( - fromNodeId: string, - toNodeId: string, - location: DockLocation, - index: number, - select?: boolean, - ): Action { - return new Action(Actions.MOVE_NODE, { - fromNode: fromNodeId, - toNode: toNodeId, - location: location.getName(), - index, - select, - }); - } - - /** - * Deletes a tab node from the layout - * @param tabNodeId the id of the tab node to delete - * @returns {Action} the action - */ - static deleteTab(tabNodeId: string): Action { - return new Action(Actions.DELETE_TAB, { node: tabNodeId }); - } - - /** - * Deletes a tabset node and all it's child tab nodes from the layout - * @param tabsetNodeId the id of the tabset node to delete - * @returns {Action} the action - */ - static deleteTabset(tabsetNodeId: string): Action { - return new Action(Actions.DELETE_TABSET, { node: tabsetNodeId }); - } - - /** - * Change the given nodes tab text - * @param tabNodeId the id of the node to rename - * @param text the test of the tab - * @returns {Action} the action - */ - static renameTab(tabNodeId: string, text: string): Action { - return new Action(Actions.RENAME_TAB, { node: tabNodeId, text }); - } - - /** - * Selects the given tab in its parent tabset - * @param tabNodeId the id of the node to set selected - * @returns {Action} the action - */ - static selectTab(tabNodeId: string): Action { - return new Action(Actions.SELECT_TAB, { tabNode: tabNodeId }); - } - - /** - * Set the given tabset node as the active tabset - * @param tabsetNodeId the id of the tabset node to set as active - * @returns {Action} the action - */ - static setActiveTabset( - tabsetNodeId: string | undefined, - windowId?: string | undefined, - ): Action { - return new Action(Actions.SET_ACTIVE_TABSET, { - tabsetNode: tabsetNodeId, - windowId: windowId, - }); - } - - /** - * Adjust the weights of a row, used when the splitter is moved - * @param nodeId the row node whose childrens weights are being adjusted - * @param weights an array of weights to be applied to the children - * @returns {Action} the action - */ - static adjustWeights(nodeId: string, weights: number[]): Action { - return new Action(Actions.ADJUST_WEIGHTS, { nodeId, weights }); - } - - static adjustBorderSplit(nodeId: string, pos: number): Action { - return new Action(Actions.ADJUST_BORDER_SPLIT, { node: nodeId, pos }); - } - - /** - * Maximizes the given tabset - * @param tabsetNodeId the id of the tabset to maximize - * @returns {Action} the action - */ - static maximizeToggle( - tabsetNodeId: string, - windowId?: string | undefined, - ): Action { - return new Action(Actions.MAXIMIZE_TOGGLE, { - node: tabsetNodeId, - windowId: windowId, - }); - } - - /** - * Updates the global model jsone attributes - * @param attributes the json for the model attributes to update (merge into the existing attributes) - * @returns {Action} the action - */ - static updateModelAttributes(attributes: any): Action { - return new Action(Actions.UPDATE_MODEL_ATTRIBUTES, { json: attributes }); - } - - /** - * Updates the given nodes json attributes - * @param nodeId the id of the node to update - * @param attributes the json attributes to update (merge with the existing attributes) - * @returns {Action} the action - */ - static updateNodeAttributes(nodeId: string, attributes: any): Action { - return new Action(Actions.UPDATE_NODE_ATTRIBUTES, { - node: nodeId, - json: attributes, - }); - } - - /** - * Pops out the given tab node into a new browser window - * @param nodeId the tab node to popout - * @returns - */ - static popoutTab(nodeId: string): Action { - return new Action(Actions.POPOUT_TAB, { node: nodeId }); - } - - /** - * Pops out the given tab set node into a new browser window - * @param nodeId the tab set node to popout - * @returns - */ - static popoutTabset(nodeId: string): Action { - return new Action(Actions.POPOUT_TABSET, { node: nodeId }); - } - - /** - * Closes the popout window - * @param windowId the id of the popout window to close - * @returns - */ - static closeWindow(windowId: string): Action { - return new Action(Actions.CLOSE_WINDOW, { windowId }); - } - - /** - * Creates a new empty popout window with the given layout - * @param layout the json layout for the new window - * @param rect the window rectangle in screen coordinates - * @returns - */ - static createWindow(layout: IJsonRowNode, rect: IJsonRect): Action { - return new Action(Actions.CREATE_WINDOW, { layout, rect }); - } + static ADD_NODE = "FlexLayout_AddNode"; + static MOVE_NODE = "FlexLayout_MoveNode"; + static DELETE_TAB = "FlexLayout_DeleteTab"; + static DELETE_TABSET = "FlexLayout_DeleteTabset"; + static RENAME_TAB = "FlexLayout_RenameTab"; + static SELECT_TAB = "FlexLayout_SelectTab"; + static SET_ACTIVE_TABSET = "FlexLayout_SetActiveTabset"; + static ADJUST_WEIGHTS = "FlexLayout_AdjustWeights"; + static ADJUST_BORDER_SPLIT = "FlexLayout_AdjustBorderSplit"; + static MAXIMIZE_TOGGLE = "FlexLayout_MaximizeToggle"; + static UPDATE_MODEL_ATTRIBUTES = "FlexLayout_UpdateModelAttributes"; + static UPDATE_NODE_ATTRIBUTES = "FlexLayout_UpdateNodeAttributes"; + static POPOUT_TAB = "FlexLayout_PopoutTab"; + static POPOUT_TABSET = "FlexLayout_PopoutTabset"; + static CLOSE_WINDOW = "FlexLayout_CloseWindow"; + static CREATE_WINDOW = "FlexLayout_CreateWindow"; + + /** + * Adds a tab node to the given tabset node + * @param json the json for the new tab node e.g {type:"tab", component:"table"} + * @param toNodeId the new tab node will be added to the tabset with this node id + * @param location the location where the new tab will be added, one of the DockLocation enum values. + * @param index for docking to the center this value is the index of the tab, use -1 to add to the end. + * @param select (optional) whether to select the new tab, overriding autoSelectTab + * @returns {Action} the action + */ + static addNode(json: any, toNodeId: string, location: DockLocation, index: number, select?: boolean): Action { + return new Action(Actions.ADD_NODE, { + json, + toNode: toNodeId, + location: location.getName(), + index, + select, + }); + } + + /** + * Moves a node (tab or tabset) from one location to another + * @param fromNodeId the id of the node to move + * @param toNodeId the id of the node to receive the moved node + * @param location the location where the moved node will be added, one of the DockLocation enum values. + * @param index for docking to the center this value is the index of the tab, use -1 to add to the end. + * @param select (optional) whether to select the moved tab(s) in new tabset, overriding autoSelectTab + * @returns {Action} the action + */ + static moveNode(fromNodeId: string, toNodeId: string, location: DockLocation, index: number, select?: boolean): Action { + return new Action(Actions.MOVE_NODE, { + fromNode: fromNodeId, + toNode: toNodeId, + location: location.getName(), + index, + select, + }); + } + + /** + * Deletes a tab node from the layout + * @param tabNodeId the id of the tab node to delete + * @returns {Action} the action + */ + static deleteTab(tabNodeId: string): Action { + return new Action(Actions.DELETE_TAB, { node: tabNodeId }); + } + + /** + * Deletes a tabset node and all it's child tab nodes from the layout + * @param tabsetNodeId the id of the tabset node to delete + * @returns {Action} the action + */ + static deleteTabset(tabsetNodeId: string): Action { + return new Action(Actions.DELETE_TABSET, { node: tabsetNodeId }); + } + + /** + * Change the given nodes tab text + * @param tabNodeId the id of the node to rename + * @param text the test of the tab + * @returns {Action} the action + */ + static renameTab(tabNodeId: string, text: string): Action { + return new Action(Actions.RENAME_TAB, { node: tabNodeId, text }); + } + + /** + * Selects the given tab in its parent tabset + * @param tabNodeId the id of the node to set selected + * @returns {Action} the action + */ + static selectTab(tabNodeId: string): Action { + return new Action(Actions.SELECT_TAB, { tabNode: tabNodeId }); + } + + /** + * Set the given tabset node as the active tabset + * @param tabsetNodeId the id of the tabset node to set as active + * @returns {Action} the action + */ + static setActiveTabset(tabsetNodeId: string | undefined, windowId?: string | undefined): Action { + return new Action(Actions.SET_ACTIVE_TABSET, { tabsetNode: tabsetNodeId, windowId: windowId }); + } + + /** + * Adjust the weights of a row, used when the splitter is moved + * @param nodeId the row node whose childrens weights are being adjusted + * @param weights an array of weights to be applied to the children + * @returns {Action} the action + */ + static adjustWeights(nodeId: string, weights: number[]): Action { + return new Action(Actions.ADJUST_WEIGHTS, {nodeId, weights}); + } + + static adjustBorderSplit(nodeId: string, pos: number): Action { + return new Action(Actions.ADJUST_BORDER_SPLIT, { node: nodeId, pos }); + } + + /** + * Maximizes the given tabset + * @param tabsetNodeId the id of the tabset to maximize + * @returns {Action} the action + */ + static maximizeToggle(tabsetNodeId: string, windowId?: string | undefined): Action { + return new Action(Actions.MAXIMIZE_TOGGLE, { node: tabsetNodeId, windowId: windowId }); + } + + /** + * Updates the global model jsone attributes + * @param attributes the json for the model attributes to update (merge into the existing attributes) + * @returns {Action} the action + */ + static updateModelAttributes(attributes: any): Action { + return new Action(Actions.UPDATE_MODEL_ATTRIBUTES, { json: attributes }); + } + + /** + * Updates the given nodes json attributes + * @param nodeId the id of the node to update + * @param attributes the json attributes to update (merge with the existing attributes) + * @returns {Action} the action + */ + static updateNodeAttributes(nodeId: string, attributes: any): Action { + return new Action(Actions.UPDATE_NODE_ATTRIBUTES, { node: nodeId, json: attributes }); + } + + /** + * Pops out the given tab node into a new browser window + * @param nodeId the tab node to popout + * @returns + */ + static popoutTab(nodeId: string): Action { + return new Action(Actions.POPOUT_TAB, { node: nodeId }); + } + + /** + * Pops out the given tab set node into a new browser window + * @param nodeId the tab set node to popout + * @returns + */ + static popoutTabset(nodeId: string): Action { + return new Action(Actions.POPOUT_TABSET, { node: nodeId }); + } + + /** + * Closes the popout window + * @param windowId the id of the popout window to close + * @returns + */ + static closeWindow(windowId: string): Action { + return new Action(Actions.CLOSE_WINDOW, { windowId }); + } + + /** + * Creates a new empty popout window with the given layout + * @param layout the json layout for the new window + * @param rect the window rectangle in screen coordinates + * @returns + */ + static createWindow(layout: IJsonRowNode, rect: IJsonRect): Action { + return new Action(Actions.CREATE_WINDOW, { layout, rect}); + } } diff --git a/src/model/BorderNode.ts b/src/model/BorderNode.ts index b29fea27..3b8503d1 100755 --- a/src/model/BorderNode.ts +++ b/src/model/BorderNode.ts @@ -1,575 +1,446 @@ -import { Attribute } from '../Attribute'; -import { AttributeDefinitions } from '../AttributeDefinitions'; -import { DockLocation } from '../DockLocation'; -import { DropInfo } from '../DropInfo'; -import { Orientation } from '../Orientation'; -import { Rect } from '../Rect'; -import { CLASSES } from '../Types'; -import { IDraggable } from './IDraggable'; -import { IDropTarget } from './IDropTarget'; -import { IJsonBorderNode } from './IJsonModel'; -import { Model } from './Model'; -import { Node } from './Node'; -import { TabNode } from './TabNode'; -import { TabSetNode } from './TabSetNode'; -import { adjustSelectedIndex } from './Utils'; +import { Attribute } from "../Attribute"; +import { AttributeDefinitions } from "../AttributeDefinitions"; +import { DockLocation } from "../DockLocation"; +import { DropInfo } from "../DropInfo"; +import { Orientation } from "../Orientation"; +import { Rect } from "../Rect"; +import { CLASSES } from "../Types"; +import { IDraggable } from "./IDraggable"; +import { IDropTarget } from "./IDropTarget"; +import { IJsonBorderNode } from "./IJsonModel"; +import { Model } from "./Model"; +import { Node } from "./Node"; +import { TabNode } from "./TabNode"; +import { TabSetNode } from "./TabSetNode"; +import { adjustSelectedIndex } from "./Utils"; export class BorderNode extends Node implements IDropTarget { - static readonly TYPE = 'border'; - - /** @internal */ - static fromJson(json: any, model: Model) { - const location = DockLocation.getByName(json.location); - const border = new BorderNode(location, json, model); - if (json.children) { - border.children = json.children.map((jsonChild: any) => { - const child = TabNode.fromJson(jsonChild, model); - child.setParent(border); - return child; - }); - } - - return border; - } - /** @internal */ - private static attributeDefinitions: AttributeDefinitions = - BorderNode.createAttributeDefinitions(); - - /** @internal */ - private contentRect: Rect = Rect.empty(); - /** @internal */ - private tabHeaderRect: Rect = Rect.empty(); - /** @internal */ - private location: DockLocation; - - /** @internal */ - constructor(location: DockLocation, json: any, model: Model) { - super(model); - - this.location = location; - this.attributes.id = `border_${location.getName()}`; - BorderNode.attributeDefinitions.fromJson(json, this.attributes); - model.addNode(this); - } - - getLocation() { - return this.location; - } - - getClassName() { - return this.getAttr('className') as string | undefined; - } - - isHorizontal() { - return this.location.orientation === Orientation.HORZ; - } - - getSize() { - const defaultSize = this.getAttr('size') as number; - const selected = this.getSelected(); - if (selected === -1) { - return defaultSize; - } else { - const tabNode = this.children[selected] as TabNode; - const tabBorderSize = this.isHorizontal() - ? tabNode.getAttr('borderWidth') - : tabNode.getAttr('borderHeight'); - if (tabBorderSize === -1) { - return defaultSize; - } else { - return tabBorderSize; - } - } - } - - getMinSize() { - const selectedNode = this.getSelectedNode(); - let min = this.getAttr('minSize') as number; - if (selectedNode) { - const nodeMin = this.isHorizontal() - ? selectedNode.getMinWidth() - : selectedNode.getMinHeight(); - min = Math.max(min, nodeMin); - } - return min; - } - - getMaxSize() { - const selectedNode = this.getSelectedNode(); - let max = this.getAttr('maxSize') as number; - if (selectedNode) { - const nodeMax = this.isHorizontal() - ? selectedNode.getMaxWidth() - : selectedNode.getMaxHeight(); - max = Math.min(max, nodeMax); - } - return max; - } - - getSelected(): number { - return this.attributes.selected as number; - } - - isAutoHide() { - return this.getAttr('enableAutoHide') as boolean; - } - - getSelectedNode(): TabNode | undefined { - if (this.getSelected() !== -1) { - return this.children[this.getSelected()] as TabNode; - } - return undefined; - } - - getOrientation() { - return this.location.getOrientation(); - } - - /** - * Returns the config attribute that can be used to store node specific data that - * WILL be saved to the json. The config attribute should be changed via the action Actions.updateNodeAttributes rather - * than directly, for example: - * this.state.model.doAction( - * FlexLayout.Actions.updateNodeAttributes(node.getId(), {config:myConfigObject})); - */ - getConfig() { - return this.attributes.config; - } - - isMaximized() { - return false; - } - - isShowing() { - return this.attributes.show as boolean; - } - - toJson(): IJsonBorderNode { - const json: any = {}; - BorderNode.attributeDefinitions.toJson(json, this.attributes); - json.location = this.location.getName(); - json.children = this.children.map((child) => (child as TabNode).toJson()); - return json; - } - - /** @internal */ - isAutoSelectTab(whenOpen?: boolean) { - if (whenOpen == null) { - whenOpen = this.getSelected() !== -1; - } - if (whenOpen) { - return this.getAttr('autoSelectTabWhenOpen') as boolean; - } else { - return this.getAttr('autoSelectTabWhenClosed') as boolean; - } - } - - isEnableTabScrollbar() { - return this.getAttr('enableTabScrollbar') as boolean; - } - - /** @internal */ - setSelected(index: number) { - this.attributes.selected = index; - } - - /** @internal */ - getTabHeaderRect() { - return this.tabHeaderRect; - } - - /** @internal */ - setTabHeaderRect(r: Rect) { - this.tabHeaderRect = r; - } - - /** @internal */ - getRect() { - return this.tabHeaderRect!; - } - - /** @internal */ - getContentRect() { - return this.contentRect; - } - - /** @internal */ - setContentRect(r: Rect) { - this.contentRect = r; - } - - /** @internal */ - isEnableDrop() { - return this.getAttr('enableDrop') as boolean; - } - - /** @internal */ - setSize(pos: number) { - const selected = this.getSelected(); - if (selected === -1) { - this.attributes.size = pos; - } else { - const tabNode = this.children[selected] as TabNode; - const tabBorderSize = this.isHorizontal() - ? tabNode.getAttr('borderWidth') - : tabNode.getAttr('borderHeight'); - if (tabBorderSize === -1) { - this.attributes.size = pos; - } else { - if (this.isHorizontal()) { - tabNode.setBorderWidth(pos); - } else { - tabNode.setBorderHeight(pos); + static readonly TYPE = "border"; + + /** @internal */ + static fromJson(json: any, model: Model) { + const location = DockLocation.getByName(json.location); + const border = new BorderNode(location, json, model); + if (json.children) { + border.children = json.children.map((jsonChild: any) => { + const child = TabNode.fromJson(jsonChild, model); + child.setParent(border); + return child; + }); } - } - } - } - - /** @internal */ - updateAttrs(json: any) { - BorderNode.attributeDefinitions.update(json, this.attributes); - } - - /** @internal */ - remove(node: TabNode) { - const removedIndex = this.removeChild(node); - if (this.getSelected() !== -1) { - adjustSelectedIndex(this, removedIndex); - } - } - - /** @internal */ - canDrop( - dragNode: Node & IDraggable, - x: number, - y: number, - ): DropInfo | undefined { - if (!(dragNode instanceof TabNode)) { - return undefined; - } - - let dropInfo; - const dockLocation = DockLocation.CENTER; - - if (this.tabHeaderRect!.contains(x, y)) { - if (this.location.orientation === Orientation.VERT) { - if (this.children.length > 0) { - let child = this.children[0]; - let childRect = (child as TabNode).getTabRect()!; - const childY = childRect.y; - - const childHeight = childRect.height; - - let pos = this.tabHeaderRect!.x; - let childCenter = 0; - for (let i = 0; i < this.children.length; i++) { - child = this.children[i]; - childRect = (child as TabNode).getTabRect()!; - childCenter = childRect.x + childRect.width / 2; - if (x >= pos && x < childCenter) { - const outlineRect = new Rect( - childRect.x - 2, - childY, - 3, - childHeight, - ); - dropInfo = new DropInfo( - this, - outlineRect, - dockLocation, - i, - CLASSES.FLEXLAYOUT__OUTLINE_RECT, - ); - break; + + return border; + } + /** @internal */ + private static attributeDefinitions: AttributeDefinitions = BorderNode.createAttributeDefinitions(); + + /** @internal */ + private contentRect: Rect = Rect.empty(); + /** @internal */ + private tabHeaderRect: Rect = Rect.empty(); + /** @internal */ + private location: DockLocation; + + /** @internal */ + constructor(location: DockLocation, json: any, model: Model) { + super(model); + + this.location = location; + this.attributes.id = `border_${location.getName()}`; + BorderNode.attributeDefinitions.fromJson(json, this.attributes); + model.addNode(this); + } + + getLocation() { + return this.location; + } + + getClassName() { + return this.getAttr("className") as string | undefined; + } + + isHorizontal() { + return this.location.orientation === Orientation.HORZ; + } + + getSize() { + const defaultSize = this.getAttr("size") as number; + const selected = this.getSelected(); + if (selected === -1) { + return defaultSize; + } else { + const tabNode = this.children[selected] as TabNode; + const tabBorderSize = this.isHorizontal() ? tabNode.getAttr("borderWidth") : tabNode.getAttr("borderHeight"); + if (tabBorderSize === -1) { + return defaultSize; + } else { + return tabBorderSize; } - pos = childCenter; - } - if (dropInfo == null) { - const outlineRect = new Rect( - childRect.getRight() - 2, - childY, - 3, - childHeight, - ); - dropInfo = new DropInfo( - this, - outlineRect, - dockLocation, - this.children.length, - CLASSES.FLEXLAYOUT__OUTLINE_RECT, - ); - } + } + } + + getMinSize() { + const selectedNode = this.getSelectedNode(); + let min = this.getAttr("minSize") as number; + if (selectedNode) { + const nodeMin = this.isHorizontal() ? selectedNode.getMinWidth() : selectedNode.getMinHeight(); + min = Math.max(min, nodeMin); + } + return min; + } + + getMaxSize() { + const selectedNode = this.getSelectedNode(); + let max = this.getAttr("maxSize") as number; + if (selectedNode) { + const nodeMax = this.isHorizontal() ? selectedNode.getMaxWidth() : selectedNode.getMaxHeight(); + max = Math.min(max, nodeMax); + } + return max; + } + + getSelected(): number { + return this.attributes.selected as number; + } + + isAutoHide() { + return this.getAttr("enableAutoHide") as boolean; + } + + getSelectedNode(): TabNode | undefined { + if (this.getSelected() !== -1) { + return this.children[this.getSelected()] as TabNode; + } + return undefined; + } + + getOrientation() { + return this.location.getOrientation(); + } + + /** + * Returns the config attribute that can be used to store node specific data that + * WILL be saved to the json. The config attribute should be changed via the action Actions.updateNodeAttributes rather + * than directly, for example: + * this.state.model.doAction( + * FlexLayout.Actions.updateNodeAttributes(node.getId(), {config:myConfigObject})); + */ + getConfig() { + return this.attributes.config; + } + + isMaximized() { + return false; + } + + isShowing() { + return this.attributes.show as boolean; + } + + toJson(): IJsonBorderNode { + const json: any = {}; + BorderNode.attributeDefinitions.toJson(json, this.attributes); + json.location = this.location.getName(); + json.children = this.children.map((child) => (child as TabNode).toJson()); + return json; + } + + /** @internal */ + isAutoSelectTab(whenOpen?: boolean) { + if (whenOpen == null) { + whenOpen = this.getSelected() !== -1; + } + if (whenOpen) { + return this.getAttr("autoSelectTabWhenOpen") as boolean; } else { - const outlineRect = new Rect( - this.tabHeaderRect!.x + 1, - this.tabHeaderRect!.y + 2, - 3, - 18, - ); - dropInfo = new DropInfo( - this, - outlineRect, - dockLocation, - 0, - CLASSES.FLEXLAYOUT__OUTLINE_RECT, - ); + return this.getAttr("autoSelectTabWhenClosed") as boolean; } - } else { - if (this.children.length > 0) { - let child = this.children[0]; - let childRect = (child as TabNode).getTabRect()!; - const childX = childRect.x; - const childWidth = childRect.width; - - let pos = this.tabHeaderRect!.y; - let childCenter = 0; - for (let i = 0; i < this.children.length; i++) { - child = this.children[i]; - childRect = (child as TabNode).getTabRect()!; - childCenter = childRect.y + childRect.height / 2; - if (y >= pos && y < childCenter) { - const outlineRect = new Rect( - childX, - childRect.y - 2, - childWidth, - 3, - ); - dropInfo = new DropInfo( - this, - outlineRect, - dockLocation, - i, - CLASSES.FLEXLAYOUT__OUTLINE_RECT, - ); - break; + } + + isEnableTabScrollbar() { + return this.getAttr("enableTabScrollbar") as boolean; + } + + /** @internal */ + setSelected(index: number) { + this.attributes.selected = index; + } + + /** @internal */ + getTabHeaderRect() { + return this.tabHeaderRect; + } + + /** @internal */ + setTabHeaderRect(r: Rect) { + this.tabHeaderRect = r; + } + + /** @internal */ + getRect() { + return this.tabHeaderRect!; + } + + /** @internal */ + getContentRect() { + return this.contentRect; + } + + /** @internal */ + setContentRect(r: Rect) { + this.contentRect = r; + } + + /** @internal */ + isEnableDrop() { + return this.getAttr("enableDrop") as boolean; + } + + /** @internal */ + setSize(pos: number) { + const selected = this.getSelected(); + if (selected === -1) { + this.attributes.size = pos; + } else { + const tabNode = this.children[selected] as TabNode; + const tabBorderSize = this.isHorizontal() ? tabNode.getAttr("borderWidth") : tabNode.getAttr("borderHeight"); + if (tabBorderSize === -1) { + this.attributes.size = pos; + } else { + if (this.isHorizontal()) { + tabNode.setBorderWidth(pos); + } else { + tabNode.setBorderHeight(pos); + } + } + } + } + + /** @internal */ + updateAttrs(json: any) { + BorderNode.attributeDefinitions.update(json, this.attributes); + } + + /** @internal */ + remove(node: TabNode) { + const removedIndex = this.removeChild(node); + if (this.getSelected() !== -1) { + adjustSelectedIndex(this, removedIndex); + } + } + + /** @internal */ + canDrop(dragNode: Node & IDraggable, x: number, y: number): DropInfo | undefined { + if (!(dragNode instanceof TabNode)) { + return undefined; + } + + let dropInfo; + const dockLocation = DockLocation.CENTER; + + if (this.tabHeaderRect!.contains(x, y)) { + if (this.location.orientation === Orientation.VERT) { + if (this.children.length > 0) { + let child = this.children[0]; + let childRect = (child as TabNode).getTabRect()!; + const childY = childRect.y; + + const childHeight = childRect.height; + + let pos = this.tabHeaderRect!.x; + let childCenter = 0; + for (let i = 0; i < this.children.length; i++) { + child = this.children[i]; + childRect = (child as TabNode).getTabRect()!; + childCenter = childRect.x + childRect.width / 2; + if (x >= pos && x < childCenter) { + const outlineRect = new Rect(childRect.x - 2, childY, 3, childHeight); + dropInfo = new DropInfo(this, outlineRect, dockLocation, i, CLASSES.FLEXLAYOUT__OUTLINE_RECT); + break; + } + pos = childCenter; + } + if (dropInfo == null) { + const outlineRect = new Rect(childRect.getRight() - 2, childY, 3, childHeight); + dropInfo = new DropInfo(this, outlineRect, dockLocation, this.children.length, CLASSES.FLEXLAYOUT__OUTLINE_RECT); + } + } else { + const outlineRect = new Rect(this.tabHeaderRect!.x + 1, this.tabHeaderRect!.y + 2, 3, 18); + dropInfo = new DropInfo(this, outlineRect, dockLocation, 0, CLASSES.FLEXLAYOUT__OUTLINE_RECT); + } + } else { + if (this.children.length > 0) { + let child = this.children[0]; + let childRect = (child as TabNode).getTabRect()!; + const childX = childRect.x; + const childWidth = childRect.width; + + let pos = this.tabHeaderRect!.y; + let childCenter = 0; + for (let i = 0; i < this.children.length; i++) { + child = this.children[i]; + childRect = (child as TabNode).getTabRect()!; + childCenter = childRect.y + childRect.height / 2; + if (y >= pos && y < childCenter) { + const outlineRect = new Rect(childX, childRect.y - 2, childWidth, 3); + dropInfo = new DropInfo(this, outlineRect, dockLocation, i, CLASSES.FLEXLAYOUT__OUTLINE_RECT); + break; + } + pos = childCenter; + } + if (dropInfo == null) { + const outlineRect = new Rect(childX, childRect.getBottom() - 2, childWidth, 3); + dropInfo = new DropInfo(this, outlineRect, dockLocation, this.children.length, CLASSES.FLEXLAYOUT__OUTLINE_RECT); + } + } else { + const outlineRect = new Rect(this.tabHeaderRect!.x + 2, this.tabHeaderRect!.y + 1, 18, 3); + dropInfo = new DropInfo(this, outlineRect, dockLocation, 0, CLASSES.FLEXLAYOUT__OUTLINE_RECT); + } + } + if (!dragNode.canDockInto(dragNode, dropInfo)) { + return undefined; + } + } else if (this.getSelected() !== -1 && this.contentRect!.contains(x, y)) { + const outlineRect = this.contentRect; + dropInfo = new DropInfo(this, outlineRect!, dockLocation, -1, CLASSES.FLEXLAYOUT__OUTLINE_RECT); + if (!dragNode.canDockInto(dragNode, dropInfo)) { + return undefined; + } + } + + return dropInfo; + } + + /** @internal */ + drop(dragNode: Node & IDraggable, location: DockLocation, index: number, select?: boolean): void { + let fromIndex = 0; + const dragParent = dragNode.getParent() as BorderNode | TabSetNode; + if (dragParent !== undefined) { + fromIndex = dragParent.removeChild(dragNode); + // if selected node in border is being docked into a different border then deselect border tabs + if (dragParent !== this && dragParent instanceof BorderNode && dragParent.getSelected() === fromIndex) { + dragParent.setSelected(-1); + } else { + adjustSelectedIndex(dragParent, fromIndex); } - pos = childCenter; - } - if (dropInfo == null) { - const outlineRect = new Rect( - childX, - childRect.getBottom() - 2, - childWidth, - 3, - ); - dropInfo = new DropInfo( - this, - outlineRect, - dockLocation, - this.children.length, - CLASSES.FLEXLAYOUT__OUTLINE_RECT, - ); - } + } + + // if dropping a tab back to same tabset and moving to forward position then reduce insertion index + if (dragNode instanceof TabNode && dragParent === this && fromIndex < index && index > 0) { + index--; + } + + // simple_bundled dock to existing tabset + let insertPos = index; + if (insertPos === -1) { + insertPos = this.children.length; + } + + if (dragNode instanceof TabNode) { + this.addChild(dragNode, insertPos); + } + + if (select || (select !== false && this.isAutoSelectTab())) { + this.setSelected(insertPos); + } + + this.model.tidy(); + } + + /** @internal */ + getSplitterBounds(index: number, useMinSize: boolean = false) { + const pBounds = [0, 0]; + const minSize = useMinSize ? this.getMinSize() : 0; + const maxSize = useMinSize ? this.getMaxSize() : 99999; + const rootRow = this.model.getRoot(Model.MAIN_WINDOW_ID); + const innerRect = rootRow.getRect(); + const splitterSize = this.model.getSplitterSize() + if (this.location === DockLocation.TOP) { + pBounds[0] = this.tabHeaderRect!.getBottom() + minSize; + const maxPos = this.tabHeaderRect!.getBottom() + maxSize; + pBounds[1] = Math.max(pBounds[0], innerRect.getBottom() - rootRow.getMinHeight() - splitterSize); + pBounds[1] = Math.min(pBounds[1], maxPos); + } else if (this.location === DockLocation.LEFT) { + pBounds[0] = this.tabHeaderRect!.getRight() + minSize; + const maxPos = this.tabHeaderRect!.getRight() + maxSize; + pBounds[1] = Math.max(pBounds[0], innerRect.getRight() - rootRow.getMinWidth() - splitterSize); + pBounds[1] = Math.min(pBounds[1], maxPos); + } else if (this.location === DockLocation.BOTTOM) { + pBounds[1] = this.tabHeaderRect!.y - minSize - splitterSize; + const maxPos = this.tabHeaderRect!.y - maxSize - splitterSize; + pBounds[0] = Math.min(pBounds[1], innerRect.y + rootRow.getMinHeight()); + pBounds[0] = Math.max(pBounds[0], maxPos); + } else if (this.location === DockLocation.RIGHT) { + pBounds[1] = this.tabHeaderRect!.x - minSize - splitterSize; + const maxPos = this.tabHeaderRect!.x - maxSize - splitterSize; + pBounds[0] = Math.min(pBounds[1], innerRect.x + rootRow.getMinWidth()); + pBounds[0] = Math.max(pBounds[0], maxPos); + } + return pBounds; + } + + /** @internal */ + calculateSplit(splitter: BorderNode, splitterPos: number) { + const pBounds = this.getSplitterBounds(splitterPos); + if (this.location === DockLocation.BOTTOM || this.location === DockLocation.RIGHT) { + return Math.max(0, pBounds[1] - splitterPos); } else { - const outlineRect = new Rect( - this.tabHeaderRect!.x + 2, - this.tabHeaderRect!.y + 1, - 18, - 3, - ); - dropInfo = new DropInfo( - this, - outlineRect, - dockLocation, - 0, - CLASSES.FLEXLAYOUT__OUTLINE_RECT, - ); + return Math.max(0, splitterPos - pBounds[0]); } - } - if (!dragNode.canDockInto(dragNode, dropInfo)) { - return undefined; - } - } else if (this.getSelected() !== -1 && this.contentRect!.contains(x, y)) { - const outlineRect = this.contentRect; - dropInfo = new DropInfo( - this, - outlineRect!, - dockLocation, - -1, - CLASSES.FLEXLAYOUT__OUTLINE_RECT, - ); - if (!dragNode.canDockInto(dragNode, dropInfo)) { - return undefined; - } - } - - return dropInfo; - } - - /** @internal */ - drop( - dragNode: Node & IDraggable, - location: DockLocation, - index: number, - select?: boolean, - ): void { - let fromIndex = 0; - const dragParent = dragNode.getParent() as BorderNode | TabSetNode; - if (dragParent !== undefined) { - fromIndex = dragParent.removeChild(dragNode); - // if selected node in border is being docked into a different border then deselect border tabs - if ( - dragParent !== this && - dragParent instanceof BorderNode && - dragParent.getSelected() === fromIndex - ) { - dragParent.setSelected(-1); - } else { - adjustSelectedIndex(dragParent, fromIndex); - } - } - - // if dropping a tab back to same tabset and moving to forward position then reduce insertion index - if ( - dragNode instanceof TabNode && - dragParent === this && - fromIndex < index && - index > 0 - ) { - index--; - } - - // simple_bundled dock to existing tabset - let insertPos = index; - if (insertPos === -1) { - insertPos = this.children.length; - } - - if (dragNode instanceof TabNode) { - this.addChild(dragNode, insertPos); - } - - if (select || (select !== false && this.isAutoSelectTab())) { - this.setSelected(insertPos); - } - - this.model.tidy(); - } - - /** @internal */ - getSplitterBounds(index: number, useMinSize: boolean = false) { - const pBounds = [0, 0]; - const minSize = useMinSize ? this.getMinSize() : 0; - const maxSize = useMinSize ? this.getMaxSize() : 99999; - const rootRow = this.model.getRoot(Model.MAIN_WINDOW_ID); - const innerRect = rootRow.getRect(); - const splitterSize = this.model.getSplitterSize(); - if (this.location === DockLocation.TOP) { - pBounds[0] = this.tabHeaderRect!.getBottom() + minSize; - const maxPos = this.tabHeaderRect!.getBottom() + maxSize; - pBounds[1] = Math.max( - pBounds[0], - innerRect.getBottom() - rootRow.getMinHeight() - splitterSize, - ); - pBounds[1] = Math.min(pBounds[1], maxPos); - } else if (this.location === DockLocation.LEFT) { - pBounds[0] = this.tabHeaderRect!.getRight() + minSize; - const maxPos = this.tabHeaderRect!.getRight() + maxSize; - pBounds[1] = Math.max( - pBounds[0], - innerRect.getRight() - rootRow.getMinWidth() - splitterSize, - ); - pBounds[1] = Math.min(pBounds[1], maxPos); - } else if (this.location === DockLocation.BOTTOM) { - pBounds[1] = this.tabHeaderRect!.y - minSize - splitterSize; - const maxPos = this.tabHeaderRect!.y - maxSize - splitterSize; - pBounds[0] = Math.min(pBounds[1], innerRect.y + rootRow.getMinHeight()); - pBounds[0] = Math.max(pBounds[0], maxPos); - } else if (this.location === DockLocation.RIGHT) { - pBounds[1] = this.tabHeaderRect!.x - minSize - splitterSize; - const maxPos = this.tabHeaderRect!.x - maxSize - splitterSize; - pBounds[0] = Math.min(pBounds[1], innerRect.x + rootRow.getMinWidth()); - pBounds[0] = Math.max(pBounds[0], maxPos); - } - return pBounds; - } - - /** @internal */ - calculateSplit(splitter: BorderNode, splitterPos: number) { - const pBounds = this.getSplitterBounds(splitterPos); - if ( - this.location === DockLocation.BOTTOM || - this.location === DockLocation.RIGHT - ) { - return Math.max(0, pBounds[1] - splitterPos); - } else { - return Math.max(0, splitterPos - pBounds[0]); - } - } - - /** @internal */ - getAttributeDefinitions() { - return BorderNode.attributeDefinitions; - } - - /** @internal */ - static getAttributeDefinitions() { - return BorderNode.attributeDefinitions; - } - - /** @internal */ - private static createAttributeDefinitions(): AttributeDefinitions { - const attributeDefinitions = new AttributeDefinitions(); - attributeDefinitions - .add('type', BorderNode.TYPE, true) - .setType(Attribute.STRING) - .setFixed(); - - attributeDefinitions - .add('selected', -1) - .setType(Attribute.NUMBER) - .setDescription( - `index of selected/visible tab in border; -1 means no tab selected`, - ); - attributeDefinitions - .add('show', true) - .setType(Attribute.BOOLEAN) - .setDescription(`show/hide this border`); - attributeDefinitions - .add('config', undefined) - .setType('any') - .setDescription(`a place to hold json config used in your own code`); - - attributeDefinitions - .addInherited('enableDrop', 'borderEnableDrop') - .setType(Attribute.BOOLEAN) - .setDescription(`whether tabs can be dropped into this border`); - attributeDefinitions - .addInherited('className', 'borderClassName') - .setType(Attribute.STRING) - .setDescription(`class applied to tab button`); - attributeDefinitions - .addInherited('autoSelectTabWhenOpen', 'borderAutoSelectTabWhenOpen') - .setType(Attribute.BOOLEAN) - .setDescription( - `whether to select new/moved tabs in border when the border is already open`, - ); - attributeDefinitions - .addInherited('autoSelectTabWhenClosed', 'borderAutoSelectTabWhenClosed') - .setType(Attribute.BOOLEAN) - .setDescription( - `whether to select new/moved tabs in border when the border is currently closed`, - ); - attributeDefinitions - .addInherited('size', 'borderSize') - .setType(Attribute.NUMBER) - .setDescription(`size of the tab area when selected`); - attributeDefinitions - .addInherited('minSize', 'borderMinSize') - .setType(Attribute.NUMBER) - .setDescription(`the minimum size of the tab area`); - attributeDefinitions - .addInherited('maxSize', 'borderMaxSize') - .setType(Attribute.NUMBER) - .setDescription(`the maximum size of the tab area`); - attributeDefinitions - .addInherited('enableAutoHide', 'borderEnableAutoHide') - .setType(Attribute.BOOLEAN) - .setDescription(`hide border if it has zero tabs`); - attributeDefinitions - .addInherited('enableTabScrollbar', 'borderEnableTabScrollbar') - .setType(Attribute.BOOLEAN) - .setDescription(`whether to show a mini scrollbar for the tabs`); - return attributeDefinitions; - } + } + + /** @internal */ + getAttributeDefinitions() { + return BorderNode.attributeDefinitions; + } + + /** @internal */ + static getAttributeDefinitions() { + return BorderNode.attributeDefinitions; + } + + /** @internal */ + private static createAttributeDefinitions(): AttributeDefinitions { + const attributeDefinitions = new AttributeDefinitions(); + attributeDefinitions.add("type", BorderNode.TYPE, true).setType(Attribute.STRING).setFixed(); + + attributeDefinitions.add("selected", -1).setType(Attribute.NUMBER).setDescription( + `index of selected/visible tab in border; -1 means no tab selected` + ); + attributeDefinitions.add("show", true).setType(Attribute.BOOLEAN).setDescription( + `show/hide this border` + ); + attributeDefinitions.add("config", undefined).setType("any").setDescription( + `a place to hold json config used in your own code` + ); + + attributeDefinitions.addInherited("enableDrop", "borderEnableDrop").setType(Attribute.BOOLEAN).setDescription( + `whether tabs can be dropped into this border` + ); + attributeDefinitions.addInherited("className", "borderClassName").setType(Attribute.STRING).setDescription( + `class applied to tab button` + ); + attributeDefinitions.addInherited("autoSelectTabWhenOpen", "borderAutoSelectTabWhenOpen").setType(Attribute.BOOLEAN).setDescription( + `whether to select new/moved tabs in border when the border is already open` + ); + attributeDefinitions.addInherited("autoSelectTabWhenClosed", "borderAutoSelectTabWhenClosed").setType(Attribute.BOOLEAN).setDescription( + `whether to select new/moved tabs in border when the border is currently closed` + ); + attributeDefinitions.addInherited("size", "borderSize").setType(Attribute.NUMBER).setDescription( + `size of the tab area when selected` + ); + attributeDefinitions.addInherited("minSize", "borderMinSize").setType(Attribute.NUMBER).setDescription( + `the minimum size of the tab area` + ); + attributeDefinitions.addInherited("maxSize", "borderMaxSize").setType(Attribute.NUMBER).setDescription( + `the maximum size of the tab area` + ); + attributeDefinitions.addInherited("enableAutoHide", "borderEnableAutoHide").setType(Attribute.BOOLEAN).setDescription( + `hide border if it has zero tabs` + ); + attributeDefinitions.addInherited("enableTabScrollbar", "borderEnableTabScrollbar").setType(Attribute.BOOLEAN).setDescription( + `whether to show a mini scrollbar for the tabs` + ); + return attributeDefinitions; + } } diff --git a/src/model/BorderSet.ts b/src/model/BorderSet.ts index 7c07999d..0a355867 100755 --- a/src/model/BorderSet.ts +++ b/src/model/BorderSet.ts @@ -1,92 +1,87 @@ -import { DockLocation } from '../DockLocation'; -import { DropInfo } from '../DropInfo'; -import { BorderNode } from './BorderNode'; -import { IDraggable } from './IDraggable'; -import { Model } from './Model'; -import { Node } from './Node'; +import { DockLocation } from "../DockLocation"; +import { DropInfo } from "../DropInfo"; +import { BorderNode } from "./BorderNode"; +import { IDraggable } from "./IDraggable"; +import { Model } from "./Model"; +import { Node } from "./Node"; export class BorderSet { - /** @internal */ - static fromJson(json: any, model: Model) { - const borderSet = new BorderSet(model); - borderSet.borders = json.map((borderJson: any) => - BorderNode.fromJson(borderJson, model), - ); - for (const border of borderSet.borders) { - borderSet.borderMap.set(border.getLocation(), border); + /** @internal */ + static fromJson(json: any, model: Model) { + const borderSet = new BorderSet(model); + borderSet.borders = json.map((borderJson: any) => BorderNode.fromJson(borderJson, model)); + for (const border of borderSet.borders) { + borderSet.borderMap.set(border.getLocation(), border); + } + return borderSet; } - return borderSet; - } - /** @internal */ - private borders: BorderNode[]; - /** @internal */ - private borderMap: Map; - /** @internal */ - private layoutHorizontal: boolean; - - /** @internal */ - constructor(_model: Model) { - this.borders = []; - this.borderMap = new Map(); - this.layoutHorizontal = true; - } + /** @internal */ + private borders: BorderNode[]; + /** @internal */ + private borderMap: Map; + /** @internal */ + private layoutHorizontal: boolean; - toJson() { - return this.borders.map((borderNode) => borderNode.toJson()); - } + /** @internal */ + constructor(_model: Model) { + this.borders = []; + this.borderMap = new Map(); + this.layoutHorizontal = true; + } - /** @internal */ - getLayoutHorizontal() { - return this.layoutHorizontal; - } + toJson() { + return this.borders.map((borderNode) => borderNode.toJson()); + } - /** @internal */ - getBorders() { - return this.borders; - } + /** @internal */ + getLayoutHorizontal () { + return this.layoutHorizontal; + } - /** @internal */ - getBorderMap() { - return this.borderMap; - } + /** @internal */ + getBorders() { + return this.borders; + } - /** @internal */ - forEachNode(fn: (node: Node, level: number) => void) { - for (const borderNode of this.borders) { - fn(borderNode, 0); - for (const node of borderNode.getChildren()) { - node.forEachNode(fn, 1); - } + /** @internal */ + getBorderMap() { + return this.borderMap; } - } - /** @internal */ - setPaths() { - for (const borderNode of this.borders) { - const path = '/border/' + borderNode.getLocation().getName(); - borderNode.setPath(path); - let i = 0; - for (const node of borderNode.getChildren()) { - node.setPath(path + '/t' + i); - i++; - } + /** @internal */ + forEachNode(fn: (node: Node, level: number) => void) { + for (const borderNode of this.borders) { + fn(borderNode, 0); + for (const node of borderNode.getChildren()) { + node.forEachNode(fn, 1); + } + } } - } - /** @internal */ - findDropTargetNode( - dragNode: Node & IDraggable, - x: number, - y: number, - ): DropInfo | undefined { - for (const border of this.borders) { - if (border.isShowing()) { - const dropInfo = border.canDrop(dragNode, x, y); - if (dropInfo !== undefined) { - return dropInfo; + /** @internal */ + setPaths() { + for (const borderNode of this.borders) { + const path = "/border/" + borderNode.getLocation().getName(); + borderNode.setPath(path); + let i = 0; + for (const node of borderNode.getChildren()) { + node.setPath( path + "/t" + i); + i++; + } + } + } + + + /** @internal */ + findDropTargetNode(dragNode: Node & IDraggable, x: number, y: number): DropInfo | undefined { + for (const border of this.borders) { + if (border.isShowing()) { + const dropInfo = border.canDrop(dragNode, x, y); + if (dropInfo !== undefined) { + return dropInfo; + } + } } - } + return undefined; } - return undefined; - } } diff --git a/src/model/ICloseType.ts b/src/model/ICloseType.ts index 00b54ec2..9cabd6d6 100644 --- a/src/model/ICloseType.ts +++ b/src/model/ICloseType.ts @@ -1,5 +1,5 @@ export enum ICloseType { - Visible = 1, // close if selected or hovered, i.e. when x is visible (will only close selected on mobile, where css hover is not available) - Always = 2, // close always (both selected and unselected when x rect tapped e.g where a custom image has been added for close) - Selected = 3, // close only if selected + Visible = 1, // close if selected or hovered, i.e. when x is visible (will only close selected on mobile, where css hover is not available) + Always = 2, // close always (both selected and unselected when x rect tapped e.g where a custom image has been added for close) + Selected = 3, // close only if selected } diff --git a/src/model/IDraggable.ts b/src/model/IDraggable.ts index 7106e2e9..bcb79134 100644 --- a/src/model/IDraggable.ts +++ b/src/model/IDraggable.ts @@ -1,6 +1,7 @@ export interface IDraggable { - /** @internal */ - isEnableDrag(): boolean; - /** @internal */ - getName(): string | undefined; + /** @internal */ + isEnableDrag(): boolean; + /** @internal */ + getName(): string | undefined; } + diff --git a/src/model/IDropTarget.ts b/src/model/IDropTarget.ts index 96e28cf2..016885d9 100644 --- a/src/model/IDropTarget.ts +++ b/src/model/IDropTarget.ts @@ -1,22 +1,14 @@ -import { DockLocation } from '../DockLocation'; -import { DropInfo } from '../DropInfo'; -import { IDraggable } from './IDraggable'; -import { Node } from './Node'; +import { DockLocation } from "../DockLocation"; +import { DropInfo } from "../DropInfo"; +import { IDraggable } from "./IDraggable"; +import { Node } from "./Node"; export interface IDropTarget { - /** @internal */ - canDrop( - dragNode: Node & IDraggable, - x: number, - y: number, - ): DropInfo | undefined; - /** @internal */ - drop( - dragNode: Node & IDraggable, - location: DockLocation, - index: number, - select?: boolean, - ): void; - /** @internal */ - isEnableDrop(): boolean; + /** @internal */ + canDrop(dragNode: Node & IDraggable, x: number, y: number): DropInfo | undefined; + /** @internal */ + drop(dragNode: Node & IDraggable, location: DockLocation, index: number, select?: boolean): void; + /** @internal */ + isEnableDrop(): boolean; } + diff --git a/src/model/IJsonModel.ts b/src/model/IJsonModel.ts index 2fbb1103..c5508d34 100755 --- a/src/model/IJsonModel.ts +++ b/src/model/IJsonModel.ts @@ -1,43 +1,43 @@ -import { ICloseType } from './ICloseType'; -export type IBorderLocation = 'top' | 'bottom' | 'left' | 'right'; -export type ITabLocation = 'top' | 'bottom'; +import { ICloseType } from "./ICloseType"; +export type IBorderLocation = "top" | "bottom" | "left" | "right"; +export type ITabLocation = "top" | "bottom"; export interface IJsonModel { - global?: IGlobalAttributes; - borders?: IJsonBorderNode[]; - layout: IJsonRowNode; // top level 'row' is horizontal, rows inside rows take opposite orientation to parent row (ie can act as columns) - popouts?: Record; + global?: IGlobalAttributes; + borders?: IJsonBorderNode[]; + layout: IJsonRowNode; // top level 'row' is horizontal, rows inside rows take opposite orientation to parent row (ie can act as columns) + popouts?: Record; } export interface IJsonRect { - x: number; - y: number; - width: number; - height: number; + x: number; + y: number; + width: number; + height: number; } export interface IJsonPopout { - layout: IJsonRowNode; - rect: IJsonRect; + layout: IJsonRowNode; + rect: IJsonRect; } export interface IJsonBorderNode extends IBorderAttributes { - location: IBorderLocation; - children: IJsonTabNode[]; + location: IBorderLocation; + children: IJsonTabNode[]; } export interface IJsonRowNode extends IRowAttributes { - children: (IJsonRowNode | IJsonTabSetNode)[]; + children: (IJsonRowNode | IJsonTabSetNode)[]; } export interface IJsonTabSetNode extends ITabSetAttributes { - /** Marks this as the active tab set, read from initial json but - * must subseqently be set on the model (only one tab set can be active)*/ - active?: boolean; - /** Marks this tab set as being maximized, read from initial json but - * must subseqently be set on the model (only one tab set can be maximized) */ - maximized?: boolean; - children: IJsonTabNode[]; + /** Marks this as the active tab set, read from initial json but + * must subseqently be set on the model (only one tab set can be active)*/ + active?: boolean; + /** Marks this tab set as being maximized, read from initial json but + * must subseqently be set on the model (only one tab set can be maximized) */ + maximized?: boolean; + children: IJsonTabNode[]; } // eslint-disable-next-line @typescript-eslint/no-empty-object-type @@ -47,218 +47,218 @@ export interface IJsonTabNode extends ITabAttributes {} // below this line is autogenerated from attributes in code via Model static method toTypescriptInterfaces() //---------------------------------------------------------------------------------------------------------- export interface IGlobalAttributes { - /** + /** Value for BorderNode attribute autoSelectTabWhenClosed if not overridden whether to select new/moved tabs in border when the border is currently closed Default: false */ - borderAutoSelectTabWhenClosed?: boolean; + borderAutoSelectTabWhenClosed?: boolean; - /** + /** Value for BorderNode attribute autoSelectTabWhenOpen if not overridden whether to select new/moved tabs in border when the border is already open Default: true */ - borderAutoSelectTabWhenOpen?: boolean; + borderAutoSelectTabWhenOpen?: boolean; - /** + /** Value for BorderNode attribute className if not overridden class applied to tab button Default: undefined */ - borderClassName?: string; + borderClassName?: string; - /** + /** Value for BorderNode attribute enableAutoHide if not overridden hide border if it has zero tabs Default: false */ - borderEnableAutoHide?: boolean; + borderEnableAutoHide?: boolean; - /** + /** Value for BorderNode attribute enableDrop if not overridden whether tabs can be dropped into this border Default: true */ - borderEnableDrop?: boolean; + borderEnableDrop?: boolean; - /** + /** Value for BorderNode attribute enableTabScrollbar if not overridden whether to show a mini scrollbar for the tabs Default: false */ - borderEnableTabScrollbar?: boolean; + borderEnableTabScrollbar?: boolean; - /** + /** Value for BorderNode attribute maxSize if not overridden the maximum size of the tab area Default: 99999 */ - borderMaxSize?: number; + borderMaxSize?: number; - /** + /** Value for BorderNode attribute minSize if not overridden the minimum size of the tab area Default: 0 */ - borderMinSize?: number; + borderMinSize?: number; - /** + /** Value for BorderNode attribute size if not overridden size of the tab area when selected Default: 200 */ - borderSize?: number; + borderSize?: number; - /** + /** enable docking to the edges of the layout, this will show the edge indicators Default: true */ - enableEdgeDock?: boolean; + enableEdgeDock?: boolean; - /** + /** boolean indicating if tab icons should rotate with the text in the left and right borders Default: true */ - enableRotateBorderIcons?: boolean; + enableRotateBorderIcons?: boolean; - /** + /** the top level 'row' will layout horizontally by default, set this option true to make it layout vertically Default: false */ - rootOrientationVertical?: boolean; + rootOrientationVertical?: boolean; - /** + /** enable a small centralized handle on all splitters Default: false */ - splitterEnableHandle?: boolean; + splitterEnableHandle?: boolean; - /** + /** additional width in pixels of the splitter hit test area Default: 0 */ - splitterExtra?: number; + splitterExtra?: number; - /** + /** width in pixels of all splitters between tabsets/borders Default: 8 */ - splitterSize?: number; + splitterSize?: number; - /** + /** Value for TabNode attribute borderHeight if not overridden height when added to border, -1 will use border size Default: -1 */ - tabBorderHeight?: number; + tabBorderHeight?: number; - /** + /** Value for TabNode attribute borderWidth if not overridden width when added to border, -1 will use border size Default: -1 */ - tabBorderWidth?: number; + tabBorderWidth?: number; - /** + /** Value for TabNode attribute className if not overridden class applied to tab button Default: undefined */ - tabClassName?: string; + tabClassName?: string; - /** + /** Value for TabNode attribute closeType if not overridden see values in ICloseType Default: 1 */ - tabCloseType?: ICloseType; + tabCloseType?: ICloseType; - /** + /** Value for TabNode attribute contentClassName if not overridden class applied to tab content Default: undefined */ - tabContentClassName?: string; + tabContentClassName?: string; - /** + /** Default: 0.3 */ - tabDragSpeed?: number; + tabDragSpeed?: number; - /** + /** Value for TabNode attribute enableClose if not overridden allow user to close tab via close button Default: true */ - tabEnableClose?: boolean; + tabEnableClose?: boolean; - /** + /** Value for TabNode attribute enableDrag if not overridden allow user to drag tab to new location Default: true */ - tabEnableDrag?: boolean; + tabEnableDrag?: boolean; - /** + /** Value for TabNode attribute enablePopout if not overridden enable popout (in popout capable browser) Default: false */ - tabEnablePopout?: boolean; + tabEnablePopout?: boolean; - /** + /** Value for TabNode attribute enablePopoutIcon if not overridden whether to show the popout icon in the tabset header if this tab enables popouts Default: true */ - tabEnablePopoutIcon?: boolean; + tabEnablePopoutIcon?: boolean; - /** + /** Value for TabNode attribute enablePopoutOverlay if not overridden if this tab will not work correctly in a popout window when the main window is backgrounded (inactive) @@ -266,704 +266,704 @@ export interface IGlobalAttributes { Default: false */ - tabEnablePopoutOverlay?: boolean; + tabEnablePopoutOverlay?: boolean; - /** + /** Value for TabNode attribute enableRename if not overridden allow user to rename tabs by double clicking Default: true */ - tabEnableRename?: boolean; + tabEnableRename?: boolean; - /** + /** Value for TabNode attribute enableRenderOnDemand if not overridden whether to avoid rendering component until tab is visible Default: true */ - tabEnableRenderOnDemand?: boolean; + tabEnableRenderOnDemand?: boolean; - /** + /** Value for TabNode attribute icon if not overridden the tab icon Default: undefined */ - tabIcon?: string; + tabIcon?: string; - /** + /** Value for TabNode attribute maxHeight if not overridden the max height of this tab Default: 99999 */ - tabMaxHeight?: number; + tabMaxHeight?: number; - /** + /** Value for TabNode attribute maxWidth if not overridden the max width of this tab Default: 99999 */ - tabMaxWidth?: number; + tabMaxWidth?: number; - /** + /** Value for TabNode attribute minHeight if not overridden the min height of this tab Default: 0 */ - tabMinHeight?: number; + tabMinHeight?: number; - /** + /** Value for TabNode attribute minWidth if not overridden the min width of this tab Default: 0 */ - tabMinWidth?: number; + tabMinWidth?: number; - /** + /** Value for TabSetNode attribute autoSelectTab if not overridden whether to select new/moved tabs in tabset Default: true */ - tabSetAutoSelectTab?: boolean; + tabSetAutoSelectTab?: boolean; - /** + /** Value for TabSetNode attribute classNameTabStrip if not overridden a class name to apply to the tab strip Default: undefined */ - tabSetClassNameTabStrip?: string; + tabSetClassNameTabStrip?: string; - /** + /** Value for TabSetNode attribute enableActiveIcon if not overridden whether the active icon (*) should be displayed when the tabset is active Default: false */ - tabSetEnableActiveIcon?: boolean; + tabSetEnableActiveIcon?: boolean; - /** + /** Value for TabSetNode attribute enableClose if not overridden allow user to close tabset via a close button Default: false */ - tabSetEnableClose?: boolean; + tabSetEnableClose?: boolean; - /** + /** Value for TabSetNode attribute enableDeleteWhenEmpty if not overridden whether to delete this tabset when is has no tabs Default: true */ - tabSetEnableDeleteWhenEmpty?: boolean; + tabSetEnableDeleteWhenEmpty?: boolean; - /** + /** Value for TabSetNode attribute enableDivide if not overridden allow user to drag tabs to region of this tabset, splitting into new tabset Default: true */ - tabSetEnableDivide?: boolean; + tabSetEnableDivide?: boolean; - /** + /** Value for TabSetNode attribute enableDrag if not overridden allow user to drag tabs out this tabset Default: true */ - tabSetEnableDrag?: boolean; + tabSetEnableDrag?: boolean; - /** + /** Value for TabSetNode attribute enableDrop if not overridden allow user to drag tabs into this tabset Default: true */ - tabSetEnableDrop?: boolean; + tabSetEnableDrop?: boolean; - /** + /** Value for TabSetNode attribute enableMaximize if not overridden allow user to maximize tabset to fill view via maximize button Default: true */ - tabSetEnableMaximize?: boolean; + tabSetEnableMaximize?: boolean; - /** + /** Value for TabSetNode attribute enableSingleTabStretch if not overridden if the tabset has only a single tab then stretch the single tab to fill area and display in a header style Default: false */ - tabSetEnableSingleTabStretch?: boolean; + tabSetEnableSingleTabStretch?: boolean; - /** + /** Value for TabSetNode attribute enableTabScrollbar if not overridden whether to show a mini scrollbar for the tabs Default: false */ - tabSetEnableTabScrollbar?: boolean; + tabSetEnableTabScrollbar?: boolean; - /** + /** Value for TabSetNode attribute enableTabStrip if not overridden enable tab strip and allow multiple tabs in this tabset Default: true */ - tabSetEnableTabStrip?: boolean; + tabSetEnableTabStrip?: boolean; - /** + /** Value for TabSetNode attribute enableTabWrap if not overridden wrap tabs onto multiple lines Default: false */ - tabSetEnableTabWrap?: boolean; + tabSetEnableTabWrap?: boolean; - /** + /** Value for TabSetNode attribute maxHeight if not overridden maximum height (in px) for this tabset Default: 99999 */ - tabSetMaxHeight?: number; + tabSetMaxHeight?: number; - /** + /** Value for TabSetNode attribute maxWidth if not overridden maximum width (in px) for this tabset Default: 99999 */ - tabSetMaxWidth?: number; + tabSetMaxWidth?: number; - /** + /** Value for TabSetNode attribute minHeight if not overridden minimum height (in px) for this tabset Default: 0 */ - tabSetMinHeight?: number; + tabSetMinHeight?: number; - /** + /** Value for TabSetNode attribute minWidth if not overridden minimum width (in px) for this tabset Default: 0 */ - tabSetMinWidth?: number; + tabSetMinWidth?: number; - /** + /** Value for TabSetNode attribute tabLocation if not overridden the location of the tabs either top or bottom Default: "top" */ - tabSetTabLocation?: ITabLocation; + tabSetTabLocation?: ITabLocation; } export interface IRowAttributes { - /** + /** the unique id of the row, if left undefined a uuid will be assigned Default: undefined */ - id?: string; + id?: string; - /** + /** Fixed value: "row" */ - type?: string; + type?: string; - /** + /** relative weight for sizing of this row in parent row Default: 100 */ - weight?: number; + weight?: number; } export interface ITabSetAttributes { - /** + /** whether to select new/moved tabs in tabset Default: inherited from Global attribute tabSetAutoSelectTab (default true) */ - autoSelectTab?: boolean; + autoSelectTab?: boolean; - /** + /** a class name to apply to the tab strip Default: inherited from Global attribute tabSetClassNameTabStrip (default undefined) */ - classNameTabStrip?: string; + classNameTabStrip?: string; - /** + /** a place to hold json config used in your own code Default: undefined */ - config?: any; + config?: any; - /** + /** whether the active icon (*) should be displayed when the tabset is active Default: inherited from Global attribute tabSetEnableActiveIcon (default false) */ - enableActiveIcon?: boolean; + enableActiveIcon?: boolean; - /** + /** allow user to close tabset via a close button Default: inherited from Global attribute tabSetEnableClose (default false) */ - enableClose?: boolean; + enableClose?: boolean; - /** + /** whether to delete this tabset when is has no tabs Default: inherited from Global attribute tabSetEnableDeleteWhenEmpty (default true) */ - enableDeleteWhenEmpty?: boolean; + enableDeleteWhenEmpty?: boolean; - /** + /** allow user to drag tabs to region of this tabset, splitting into new tabset Default: inherited from Global attribute tabSetEnableDivide (default true) */ - enableDivide?: boolean; + enableDivide?: boolean; - /** + /** allow user to drag tabs out this tabset Default: inherited from Global attribute tabSetEnableDrag (default true) */ - enableDrag?: boolean; + enableDrag?: boolean; - /** + /** allow user to drag tabs into this tabset Default: inherited from Global attribute tabSetEnableDrop (default true) */ - enableDrop?: boolean; + enableDrop?: boolean; - /** + /** allow user to maximize tabset to fill view via maximize button Default: inherited from Global attribute tabSetEnableMaximize (default true) */ - enableMaximize?: boolean; + enableMaximize?: boolean; - /** + /** if the tabset has only a single tab then stretch the single tab to fill area and display in a header style Default: inherited from Global attribute tabSetEnableSingleTabStretch (default false) */ - enableSingleTabStretch?: boolean; + enableSingleTabStretch?: boolean; - /** + /** whether to show a mini scrollbar for the tabs Default: inherited from Global attribute tabSetEnableTabScrollbar (default false) */ - enableTabScrollbar?: boolean; + enableTabScrollbar?: boolean; - /** + /** enable tab strip and allow multiple tabs in this tabset Default: inherited from Global attribute tabSetEnableTabStrip (default true) */ - enableTabStrip?: boolean; + enableTabStrip?: boolean; - /** + /** wrap tabs onto multiple lines Default: inherited from Global attribute tabSetEnableTabWrap (default false) */ - enableTabWrap?: boolean; + enableTabWrap?: boolean; - /** + /** the unique id of the tab set, if left undefined a uuid will be assigned Default: undefined */ - id?: string; + id?: string; - /** + /** maximum height (in px) for this tabset Default: inherited from Global attribute tabSetMaxHeight (default 99999) */ - maxHeight?: number; + maxHeight?: number; - /** + /** maximum width (in px) for this tabset Default: inherited from Global attribute tabSetMaxWidth (default 99999) */ - maxWidth?: number; + maxWidth?: number; - /** + /** minimum height (in px) for this tabset Default: inherited from Global attribute tabSetMinHeight (default 0) */ - minHeight?: number; + minHeight?: number; - /** + /** minimum width (in px) for this tabset Default: inherited from Global attribute tabSetMinWidth (default 0) */ - minWidth?: number; + minWidth?: number; - /** + /** Default: undefined */ - name?: string; + name?: string; - /** + /** index of selected/visible tab in tabset Default: 0 */ - selected?: number; + selected?: number; - /** + /** the location of the tabs either top or bottom Default: inherited from Global attribute tabSetTabLocation (default "top") */ - tabLocation?: ITabLocation; + tabLocation?: ITabLocation; - /** + /** Fixed value: "tabset" */ - type?: string; + type?: string; - /** + /** relative weight for sizing of this tabset in parent row Default: 100 */ - weight?: number; + weight?: number; } export interface ITabAttributes { - /** + /** if there is no name specifed then this value will be used in the overflow menu Default: undefined */ - altName?: string; + altName?: string; - /** + /** height when added to border, -1 will use border size Default: inherited from Global attribute tabBorderHeight (default -1) */ - borderHeight?: number; + borderHeight?: number; - /** + /** width when added to border, -1 will use border size Default: inherited from Global attribute tabBorderWidth (default -1) */ - borderWidth?: number; + borderWidth?: number; - /** + /** class applied to tab button Default: inherited from Global attribute tabClassName (default undefined) */ - className?: string; + className?: string; - /** + /** see values in ICloseType Default: inherited from Global attribute tabCloseType (default 1) */ - closeType?: ICloseType; + closeType?: ICloseType; - /** + /** string identifying which component to run (for factory) Default: undefined */ - component?: string; + component?: string; - /** + /** a place to hold json config for the hosted component Default: undefined */ - config?: any; + config?: any; - /** + /** class applied to tab content Default: inherited from Global attribute tabContentClassName (default undefined) */ - contentClassName?: string; + contentClassName?: string; - /** + /** allow user to close tab via close button Default: inherited from Global attribute tabEnableClose (default true) */ - enableClose?: boolean; + enableClose?: boolean; - /** + /** allow user to drag tab to new location Default: inherited from Global attribute tabEnableDrag (default true) */ - enableDrag?: boolean; + enableDrag?: boolean; - /** + /** enable popout (in popout capable browser) Default: inherited from Global attribute tabEnablePopout (default false) */ - enablePopout?: boolean; + enablePopout?: boolean; - /** + /** whether to show the popout icon in the tabset header if this tab enables popouts Default: inherited from Global attribute tabEnablePopoutIcon (default true) */ - enablePopoutIcon?: boolean; + enablePopoutIcon?: boolean; - /** + /** if this tab will not work correctly in a popout window when the main window is backgrounded (inactive) then enabling this option will gray out this tab Default: inherited from Global attribute tabEnablePopoutOverlay (default false) */ - enablePopoutOverlay?: boolean; + enablePopoutOverlay?: boolean; - /** + /** allow user to rename tabs by double clicking Default: inherited from Global attribute tabEnableRename (default true) */ - enableRename?: boolean; + enableRename?: boolean; - /** + /** whether to avoid rendering component until tab is visible Default: inherited from Global attribute tabEnableRenderOnDemand (default true) */ - enableRenderOnDemand?: boolean; + enableRenderOnDemand?: boolean; - /** + /** if enabled the tab will re-mount when popped out/in Default: false */ - enableWindowReMount?: boolean; + enableWindowReMount?: boolean; - /** + /** whether the tab remains open when clicking elsewhere Default: true */ - pinned?: boolean; + pinned?: boolean; - /** + /** An optional help text for the tab to be displayed upon tab hover. Default: undefined */ - helpText?: string; + helpText?: string; - /** + /** the tab icon Default: inherited from Global attribute tabIcon (default undefined) */ - icon?: string; + icon?: string; - /** + /** the unique id of the tab, if left undefined a uuid will be assigned Default: undefined */ - id?: string; + id?: string; - /** + /** the max height of this tab Default: inherited from Global attribute tabMaxHeight (default 99999) */ - maxHeight?: number; + maxHeight?: number; - /** + /** the max width of this tab Default: inherited from Global attribute tabMaxWidth (default 99999) */ - maxWidth?: number; + maxWidth?: number; - /** + /** the min height of this tab Default: inherited from Global attribute tabMinHeight (default 0) */ - minHeight?: number; + minHeight?: number; - /** + /** the min width of this tab Default: inherited from Global attribute tabMinWidth (default 0) */ - minWidth?: number; + minWidth?: number; - /** + /** name of tab to be displayed in the tab button Default: "[Unnamed Tab]" */ - name?: string; + name?: string; - /** + /** class applied to parent tabset when this is the only tab and it is stretched to fill the tabset Default: undefined */ - tabsetClassName?: string; + tabsetClassName?: string; - /** + /** Fixed value: "tab" */ - type?: string; + type?: string; } export interface IBorderAttributes { - /** + /** whether to select new/moved tabs in border when the border is currently closed Default: inherited from Global attribute borderAutoSelectTabWhenClosed (default false) */ - autoSelectTabWhenClosed?: boolean; + autoSelectTabWhenClosed?: boolean; - /** + /** whether to select new/moved tabs in border when the border is already open Default: inherited from Global attribute borderAutoSelectTabWhenOpen (default true) */ - autoSelectTabWhenOpen?: boolean; + autoSelectTabWhenOpen?: boolean; - /** + /** class applied to tab button Default: inherited from Global attribute borderClassName (default undefined) */ - className?: string; + className?: string; - /** + /** a place to hold json config used in your own code Default: undefined */ - config?: any; + config?: any; - /** + /** hide border if it has zero tabs Default: inherited from Global attribute borderEnableAutoHide (default false) */ - enableAutoHide?: boolean; + enableAutoHide?: boolean; - /** + /** whether tabs can be dropped into this border Default: inherited from Global attribute borderEnableDrop (default true) */ - enableDrop?: boolean; + enableDrop?: boolean; - /** + /** whether to show a mini scrollbar for the tabs Default: inherited from Global attribute borderEnableTabScrollbar (default false) */ - enableTabScrollbar?: boolean; + enableTabScrollbar?: boolean; - /** + /** the maximum size of the tab area Default: inherited from Global attribute borderMaxSize (default 99999) */ - maxSize?: number; + maxSize?: number; - /** + /** the minimum size of the tab area Default: inherited from Global attribute borderMinSize (default 0) */ - minSize?: number; + minSize?: number; - /** + /** index of selected/visible tab in border; -1 means no tab selected Default: -1 */ - selected?: number; + selected?: number; - /** + /** show/hide this border Default: true */ - show?: boolean; + show?: boolean; - /** + /** size of the tab area when selected Default: inherited from Global attribute borderSize (default 200) */ - size?: number; + size?: number; - /** + /** Fixed value: "border" */ - type?: string; + type?: string; } diff --git a/src/model/LayoutWindow.ts b/src/model/LayoutWindow.ts index 0c04514b..64f01900 100644 --- a/src/model/LayoutWindow.ts +++ b/src/model/LayoutWindow.ts @@ -1,129 +1,119 @@ -import { Rect } from '../Rect'; -import { IJsonPopout } from './IJsonModel'; -import { Model } from './Model'; -import { RowNode } from './RowNode'; -import { Node } from './Node'; -import { TabSetNode } from './TabSetNode'; -import { LayoutInternal } from '../view/Layout'; +import { Rect } from "../Rect"; +import { IJsonPopout } from "./IJsonModel"; +import { Model } from "./Model"; +import { RowNode } from "./RowNode"; +import { Node } from "./Node"; +import { TabSetNode } from "./TabSetNode"; +import { LayoutInternal } from "../view/Layout"; export class LayoutWindow { - private _windowId: string; - private _layout: LayoutInternal | undefined; - private _rect: Rect; - private _window?: Window | undefined; - private _root?: RowNode | undefined; - private _maximizedTabSet?: TabSetNode | undefined; - private _activeTabSet?: TabSetNode | undefined; - private _toScreenRectFunction: (rect: Rect) => Rect; - - constructor(windowId: string, rect: Rect) { - this._windowId = windowId; - this._rect = rect; - this._toScreenRectFunction = (r) => r; - } - - public visitNodes(fn: (node: Node, level: number) => void) { - this.root!.forEachNode(fn, 0); - } - - public get windowId(): string { - return this._windowId; - } - - public get rect(): Rect { - return this._rect; - } - - public get layout(): LayoutInternal | undefined { - return this._layout; - } - - public get window(): Window | undefined { - return this._window; - } - - public get root(): RowNode | undefined { - return this._root; - } - - public get maximizedTabSet(): TabSetNode | undefined { - return this._maximizedTabSet; - } - - public get activeTabSet(): TabSetNode | undefined { - return this._activeTabSet; - } - - /** @internal */ - public set rect(value: Rect) { - this._rect = value; - } - - /** @internal */ - public set layout(value: LayoutInternal) { - this._layout = value; - } - - /** @internal */ - public set window(value: Window | undefined) { - this._window = value; - } - - /** @internal */ - public set root(value: RowNode | undefined) { - this._root = value; - } - - /** @internal */ - public set maximizedTabSet(value: TabSetNode | undefined) { - this._maximizedTabSet = value; - } - - /** @internal */ - public set activeTabSet(value: TabSetNode | undefined) { - this._activeTabSet = value; - } - - /** @internal */ - public get toScreenRectFunction(): (rect: Rect) => Rect { - return this._toScreenRectFunction!; - } - - /** @internal */ - public set toScreenRectFunction(value: (rect: Rect) => Rect) { - this._toScreenRectFunction = value; - } - - public toJson(): IJsonPopout { - // chrome sets top,left to large -ve values when minimized, dont save in this case - if (this._window && this._window.screenTop > -10000) { - this.rect = new Rect( - this._window.screenLeft, - this._window.screenTop, - this._window.outerWidth, - this._window.outerHeight, - ); - } - - return { layout: this.root!.toJson(), rect: this.rect.toJson() }; - } - - static fromJson( - windowJson: IJsonPopout, - model: Model, - windowId: string, - ): LayoutWindow { - const count = model.getwindowsMap().size; - const rect = windowJson.rect - ? Rect.fromJson(windowJson.rect) - : new Rect(50 + 50 * count, 50 + 50 * count, 600, 400); - rect.snap(10); // snapping prevents issue where window moves 1 pixel per save/restore on Chrome - const layoutWindow = new LayoutWindow(windowId, rect); - layoutWindow.root = RowNode.fromJson( - windowJson.layout, - model, - layoutWindow, - ); - return layoutWindow; - } -} + private _windowId: string; + private _layout: LayoutInternal | undefined; + private _rect: Rect; + private _window?: Window | undefined; + private _root?: RowNode | undefined; + private _maximizedTabSet?: TabSetNode | undefined; + private _activeTabSet?: TabSetNode | undefined; + private _toScreenRectFunction: (rect: Rect) => Rect; + + constructor(windowId: string, rect: Rect) { + this._windowId = windowId; + this._rect = rect; + this._toScreenRectFunction = (r) => r; + } + + public visitNodes(fn: (node: Node, level: number) => void) { + this.root!.forEachNode(fn, 0); + } + + public get windowId(): string { + return this._windowId; + } + + public get rect(): Rect { + return this._rect; + } + + public get layout(): LayoutInternal | undefined { + return this._layout; + } + + public get window(): Window | undefined { + return this._window; + } + + public get root(): RowNode | undefined { + return this._root; + } + + public get maximizedTabSet(): TabSetNode | undefined { + return this._maximizedTabSet; + } + + public get activeTabSet(): TabSetNode | undefined { + return this._activeTabSet; + } + + /** @internal */ + public set rect(value: Rect) { + this._rect = value; + } + + /** @internal */ + public set layout(value: LayoutInternal) { + this._layout = value; + } + + /** @internal */ + public set window(value: Window | undefined) { + this._window = value; + } + + /** @internal */ + public set root(value: RowNode | undefined) { + this._root = value; + } + + /** @internal */ + public set maximizedTabSet(value: TabSetNode | undefined) { + this._maximizedTabSet = value; + } + + /** @internal */ + public set activeTabSet(value: TabSetNode | undefined) { + this._activeTabSet = value; + } + + /** @internal */ + public get toScreenRectFunction(): (rect: Rect) => Rect { + return this._toScreenRectFunction!; + } + + /** @internal */ + public set toScreenRectFunction(value: (rect: Rect) => Rect) { + this._toScreenRectFunction = value; + } + + public toJson(): IJsonPopout { + // chrome sets top,left to large -ve values when minimized, dont save in this case + if (this._window && this._window.screenTop > -10000) { + this.rect = new Rect( + this._window.screenLeft, + this._window.screenTop, + this._window.outerWidth, + this._window.outerHeight + ); + } + + return { layout: this.root!.toJson(), rect: this.rect.toJson() } + } + + static fromJson(windowJson: IJsonPopout, model: Model, windowId: string): LayoutWindow { + const count = model.getwindowsMap().size; + const rect = windowJson.rect ? Rect.fromJson(windowJson.rect) : new Rect(50 + 50 * count, 50 + 50 * count, 600, 400); + rect.snap(10); // snapping prevents issue where window moves 1 pixel per save/restore on Chrome + const layoutWindow = new LayoutWindow(windowId, rect); + layoutWindow.root = RowNode.fromJson(windowJson.layout, model, layoutWindow); + return layoutWindow; + } +} \ No newline at end of file diff --git a/src/model/Model.ts b/src/model/Model.ts index 9ac3305a..f730b672 100755 --- a/src/model/Model.ts +++ b/src/model/Model.ts @@ -1,21 +1,21 @@ -import { Attribute } from '../Attribute'; -import { AttributeDefinitions } from '../AttributeDefinitions'; -import { DockLocation } from '../DockLocation'; -import { DropInfo } from '../DropInfo'; -import { Rect } from '../Rect'; -import { Action } from './Action'; -import { Actions } from './Actions'; -import { BorderNode } from './BorderNode'; -import { BorderSet } from './BorderSet'; -import { IDraggable } from './IDraggable'; -import { IDropTarget } from './IDropTarget'; -import { IJsonModel, IJsonPopout, ITabSetAttributes } from './IJsonModel'; -import { Node } from './Node'; -import { RowNode } from './RowNode'; -import { TabNode } from './TabNode'; -import { TabSetNode } from './TabSetNode'; -import { randomUUID } from './Utils'; -import { LayoutWindow } from './LayoutWindow'; +import { Attribute } from "../Attribute"; +import { AttributeDefinitions } from "../AttributeDefinitions"; +import { DockLocation } from "../DockLocation"; +import { DropInfo } from "../DropInfo"; +import { Rect } from "../Rect"; +import { Action } from "./Action"; +import { Actions } from "./Actions"; +import { BorderNode } from "./BorderNode"; +import { BorderSet } from "./BorderSet"; +import { IDraggable } from "./IDraggable"; +import { IDropTarget } from "./IDropTarget"; +import { IJsonModel, IJsonPopout, ITabSetAttributes } from "./IJsonModel"; +import { Node } from "./Node"; +import { RowNode } from "./RowNode"; +import { TabNode } from "./TabNode"; +import { TabSetNode } from "./TabSetNode"; +import { randomUUID } from "./Utils"; +import { LayoutWindow } from "./LayoutWindow"; /** @internal */ export const DefaultMin = 0; @@ -26,860 +26,679 @@ export const DefaultMax = 99999; * Class containing the Tree of Nodes used by the FlexLayout component */ export class Model { - static MAIN_WINDOW_ID = '__main_window_id__'; - - /** @internal */ - private static attributeDefinitions: AttributeDefinitions = - Model.createAttributeDefinitions(); - - /** @internal */ - private attributes: Record; - /** @internal */ - private idMap: Map; - /** @internal */ - private changeListeners: ((action: Action) => void)[]; - /** @internal */ - private borders: BorderSet; - /** @internal */ - private onAllowDrop?: (dragNode: Node, dropInfo: DropInfo) => boolean; - /** @internal */ - private onCreateTabSet?: (tabNode?: TabNode) => ITabSetAttributes; - /** @internal */ - private windows: Map; - /** @internal */ - private rootWindow: LayoutWindow; - - /** - * 'private' constructor. Use the static method Model.fromJson(json) to create a model - * @internal - */ - protected constructor() { - this.attributes = {}; - this.idMap = new Map(); - this.borders = new BorderSet(this); - this.windows = new Map(); - this.rootWindow = new LayoutWindow(Model.MAIN_WINDOW_ID, Rect.empty()); - this.windows.set(Model.MAIN_WINDOW_ID, this.rootWindow); - this.changeListeners = []; - } - - /** - * Update the node tree by performing the given action, - * Actions should be generated via static methods on the Actions class - * @param action the action to perform - * @returns added Node for Actions.addNode, windowId for createWindow - */ - doAction(action: Action): any { - let returnVal = undefined; - // console.log(action); - switch (action.type) { - case Actions.ADD_NODE: { - const newNode = new TabNode(this, action.data.json, true); - const toNode = this.idMap.get(action.data.toNode) as Node & IDraggable; - if ( - toNode instanceof TabSetNode || - toNode instanceof BorderNode || - toNode instanceof RowNode - ) { - toNode.drop( - newNode, - DockLocation.getByName(action.data.location), - action.data.index, - action.data.select, - ); - returnVal = newNode; + static MAIN_WINDOW_ID = "__main_window_id__"; + + /** @internal */ + private static attributeDefinitions: AttributeDefinitions = Model.createAttributeDefinitions(); + + /** @internal */ + private attributes: Record; + /** @internal */ + private idMap: Map; + /** @internal */ + private changeListeners: ((action: Action) => void)[]; + /** @internal */ + private borders: BorderSet; + /** @internal */ + private onAllowDrop?: (dragNode: Node, dropInfo: DropInfo) => boolean; + /** @internal */ + private onCreateTabSet?: (tabNode?: TabNode) => ITabSetAttributes; + /** @internal */ + private windows: Map; + /** @internal */ + private rootWindow: LayoutWindow; + + /** + * 'private' constructor. Use the static method Model.fromJson(json) to create a model + * @internal + */ + protected constructor() { + this.attributes = {}; + this.idMap = new Map(); + this.borders = new BorderSet(this); + this.windows = new Map(); + this.rootWindow = new LayoutWindow(Model.MAIN_WINDOW_ID, Rect.empty()); + this.windows.set(Model.MAIN_WINDOW_ID, this.rootWindow); + this.changeListeners = []; + } + + /** + * Update the node tree by performing the given action, + * Actions should be generated via static methods on the Actions class + * @param action the action to perform + * @returns added Node for Actions.addNode, windowId for createWindow + */ + doAction(action: Action): any { + let returnVal = undefined; + // console.log(action); + switch (action.type) { + case Actions.ADD_NODE: { + const newNode = new TabNode(this, action.data.json, true); + const toNode = this.idMap.get(action.data.toNode) as Node & IDraggable; + if (toNode instanceof TabSetNode || toNode instanceof BorderNode || toNode instanceof RowNode) { + toNode.drop(newNode, DockLocation.getByName(action.data.location), action.data.index, action.data.select); + returnVal = newNode; + } + break; + } + case Actions.MOVE_NODE: { + const fromNode = this.idMap.get(action.data.fromNode) as Node & IDraggable; + + if (fromNode instanceof TabNode || fromNode instanceof TabSetNode || fromNode instanceof RowNode) { + if (fromNode === this.getMaximizedTabset(fromNode.getWindowId())) { + const fromWindow = this.windows.get(fromNode.getWindowId())!; + fromWindow.maximizedTabSet = undefined; + } + const toNode = this.idMap.get(action.data.toNode) as Node & IDropTarget; + if (toNode instanceof TabSetNode || toNode instanceof BorderNode || toNode instanceof RowNode) { + toNode.drop(fromNode, DockLocation.getByName(action.data.location), action.data.index, action.data.select); + } + } + this.removeEmptyWindows(); + break; + } + case Actions.DELETE_TAB: { + const node = this.idMap.get(action.data.node); + if (node instanceof TabNode) { + node.delete(); + } + this.removeEmptyWindows(); + break; + } + case Actions.DELETE_TABSET: { + const node = this.idMap.get(action.data.node); + + if (node instanceof TabSetNode) { + // first delete all child tabs that are closeable + const children = [...node.getChildren()]; + for (let i = 0; i < children.length; i++) { + const child = children[i]; + if ((child as TabNode).isEnableClose()) { + (child as TabNode).delete(); + } + } + + if (node.getChildren().length === 0) { + node.delete(); + } + this.tidy(); + } + this.removeEmptyWindows(); + break; + } + case Actions.POPOUT_TABSET: { + const node = this.idMap.get(action.data.node); + if (node instanceof TabSetNode) { + const isMaximized = node.isMaximized(); + const oldLayoutWindow = this.windows.get(node.getWindowId())!; + const windowId = randomUUID() + const layoutWindow = new LayoutWindow(windowId, oldLayoutWindow.toScreenRectFunction(node.getRect())); + const json = { + type: "row", + children: [] + } + const row = RowNode.fromJson(json, this, layoutWindow); + layoutWindow.root = row; + this.windows.set(windowId, layoutWindow); + row.drop(node, DockLocation.CENTER, 0); + + if (isMaximized) { + this.rootWindow.maximizedTabSet = undefined; + } + } + this.removeEmptyWindows(); + break; + } + case Actions.POPOUT_TAB: { + const node = this.idMap.get(action.data.node); + if (node instanceof TabNode) { + const windowId = randomUUID() + let r = Rect.empty(); + if (node.getParent() instanceof TabSetNode) { + r = node.getParent()!.getRect(); + } else { + r = (node.getParent() as BorderNode).getContentRect(); + } + const oldLayoutWindow = this.windows.get(node.getWindowId())!; + const layoutWindow = new LayoutWindow(windowId, oldLayoutWindow.toScreenRectFunction(r)); + const tabsetId = randomUUID(); + const json = { + type: "row", + children: [ + { type: "tabset", id: tabsetId } + ] + } + const row = RowNode.fromJson(json, this, layoutWindow); + layoutWindow.root = row; + this.windows.set(windowId, layoutWindow); + + const tabset = this.idMap.get(tabsetId) as TabSetNode & IDropTarget; + tabset.drop(node, DockLocation.CENTER, 0, true); + } + this.removeEmptyWindows(); + break; + } + case Actions.CLOSE_WINDOW: { + const window = this.windows.get(action.data.windowId); + if (window) { + this.rootWindow.root?.drop(window!.root!, DockLocation.CENTER, -1); + this.rootWindow.visitNodes((node, level) => { + if (node instanceof RowNode) { + node.setWindowId(Model.MAIN_WINDOW_ID); + } + }) + + // this.getFirstTabSet().drop(window?.root!,DockLocation.CENTER, -1); + + this.windows.delete(action.data.windowId); + } + break; + } + case Actions.CREATE_WINDOW: { + const windowId = randomUUID(); + const layoutWindow = new LayoutWindow(windowId, Rect.fromJson(action.data.rect)); + const row = RowNode.fromJson(action.data.layout, this, layoutWindow); + layoutWindow.root = row; + this.windows.set(windowId, layoutWindow); + returnVal = windowId; + break; + } + case Actions.RENAME_TAB: { + const node = this.idMap.get(action.data.node); + if (node instanceof TabNode) { + node.setName(action.data.text); + } + break; + } + case Actions.SELECT_TAB: { + const tabNode = this.idMap.get(action.data.tabNode); + const windowId = action.data.windowId ? action.data.windowId : Model.MAIN_WINDOW_ID; + const window = this.windows.get(windowId)!; + if (tabNode instanceof TabNode) { + const parent = tabNode.getParent() as Node; + const pos = parent.getChildren().indexOf(tabNode); + + if (parent instanceof BorderNode) { + if (parent.getSelected() === pos) { + parent.setSelected(-1); + } else { + parent.setSelected(pos); + } + } else if (parent instanceof TabSetNode) { + if (parent.getSelected() !== pos) { + parent.setSelected(pos); + } + window.activeTabSet = parent; + } + } + break; + } + case Actions.SET_ACTIVE_TABSET: { + const windowId = action.data.windowId ? action.data.windowId : Model.MAIN_WINDOW_ID; + const window = this.windows.get(windowId)!; + if (action.data.tabsetNode === undefined) { + window.activeTabSet = undefined; + } else { + const tabsetNode = this.idMap.get(action.data.tabsetNode); + if (tabsetNode instanceof TabSetNode) { + window.activeTabSet = tabsetNode; + } + } + break; + } + case Actions.ADJUST_WEIGHTS: { + const row = this.idMap.get(action.data.nodeId) as RowNode; + const c = row.getChildren(); + for (let i = 0; i < c.length; i++) { + const n = c[i] as TabSetNode | RowNode; + n.setWeight(action.data.weights[i]); + } + break; + } + case Actions.ADJUST_BORDER_SPLIT: { + const node = this.idMap.get(action.data.node); + if (node instanceof BorderNode) { + node.setSize(action.data.pos); + } + break; + } + case Actions.MAXIMIZE_TOGGLE: { + const windowId = action.data.windowId ? action.data.windowId : Model.MAIN_WINDOW_ID; + const window = this.windows.get(windowId)!; + const node = this.idMap.get(action.data.node); + if (node instanceof TabSetNode) { + if (node === window.maximizedTabSet) { + window.maximizedTabSet = undefined; + } else { + window.maximizedTabSet = node; + window.activeTabSet = node; + } + } + + break; + } + case Actions.UPDATE_MODEL_ATTRIBUTES: { + this.updateAttrs(action.data.json); + break; + } + + case Actions.UPDATE_NODE_ATTRIBUTES: { + const node = this.idMap.get(action.data.node)!; + node.updateAttrs(action.data.json); + break; + } + default: + break; } - break; - } - case Actions.MOVE_NODE: { - const fromNode = this.idMap.get(action.data.fromNode) as Node & - IDraggable; - - if ( - fromNode instanceof TabNode || - fromNode instanceof TabSetNode || - fromNode instanceof RowNode - ) { - if (fromNode === this.getMaximizedTabset(fromNode.getWindowId())) { - const fromWindow = this.windows.get(fromNode.getWindowId())!; - fromWindow.maximizedTabSet = undefined; - } - const toNode = this.idMap.get(action.data.toNode) as Node & - IDropTarget; - if ( - toNode instanceof TabSetNode || - toNode instanceof BorderNode || - toNode instanceof RowNode - ) { - toNode.drop( - fromNode, - DockLocation.getByName(action.data.location), - action.data.index, - action.data.select, - ); - } + + this.updateIdMap(); + + for (const listener of this.changeListeners) { + listener(action); } - this.removeEmptyWindows(); - break; - } - case Actions.DELETE_TAB: { - const node = this.idMap.get(action.data.node); - if (node instanceof TabNode) { - node.delete(); + + return returnVal; + } + + + + /** + * Get the currently active tabset node + */ + getActiveTabset(windowId: string = Model.MAIN_WINDOW_ID) { + const window = this.windows.get(windowId); + if (window && window.activeTabSet && this.getNodeById(window.activeTabSet.getId())) { + return window.activeTabSet; + } else { + return undefined; } - this.removeEmptyWindows(); - break; - } - case Actions.DELETE_TABSET: { - const node = this.idMap.get(action.data.node); - - if (node instanceof TabSetNode) { - // first delete all child tabs that are closeable - const children = [...node.getChildren()]; - for (let i = 0; i < children.length; i++) { - const child = children[i]; - if ((child as TabNode).isEnableClose()) { - (child as TabNode).delete(); + } + + /** + * Get the currently maximized tabset node + */ + getMaximizedTabset(windowId: string = Model.MAIN_WINDOW_ID) { + return this.windows.get(windowId)!.maximizedTabSet; + } + + /** + * Gets the root RowNode of the model + * @returns {RowNode} + */ + getRoot(windowId: string = Model.MAIN_WINDOW_ID) { + return this.windows.get(windowId)!.root!; + } + + isRootOrientationVertical() { + return this.attributes.rootOrientationVertical as boolean; + } + + isEnableRotateBorderIcons() { + return this.attributes.enableRotateBorderIcons as boolean; + } + + /** + * Gets the + * @returns {BorderSet|*} + */ + getBorderSet() { + return this.borders; + } + + getwindowsMap() { + return this.windows; + } + + /** + * Visits all the nodes in the model and calls the given function for each + * @param fn a function that takes visited node and a integer level as parameters + */ + visitNodes(fn: (node: Node, level: number) => void) { + this.borders.forEachNode(fn); + for (const [_, w] of this.windows) { + w.root!.forEachNode(fn, 0); + } + } + + visitWindowNodes(windowId: string, fn: (node: Node, level: number) => void) { + if (this.windows.has(windowId)) { + if (windowId === Model.MAIN_WINDOW_ID) { + this.borders.forEachNode(fn); } - } + this.windows.get(windowId)!.visitNodes(fn); + } + } + + /** + * Gets a node by its id + * @param id the id to find + */ + getNodeById(id: string): Node | undefined { + return this.idMap.get(id); + } - if (node.getChildren().length === 0) { - node.delete(); - } - this.tidy(); + /** + * Finds the first/top left tab set of the given node. + * @param node The top node you want to begin searching from, deafults to the root node + * @returns The first Tab Set + */ + getFirstTabSet(node = this.windows.get(Model.MAIN_WINDOW_ID)!.root as Node): TabSetNode { + const child = node.getChildren()[0]; + if (child instanceof TabSetNode) { + return child; } - this.removeEmptyWindows(); - break; - } - case Actions.POPOUT_TABSET: { - const node = this.idMap.get(action.data.node); - if (node instanceof TabSetNode) { - const isMaximized = node.isMaximized(); - const oldLayoutWindow = this.windows.get(node.getWindowId())!; - const windowId = randomUUID(); - const layoutWindow = new LayoutWindow( - windowId, - oldLayoutWindow.toScreenRectFunction(node.getRect()), - ); - const json = { - type: 'row', - children: [], - }; - const row = RowNode.fromJson(json, this, layoutWindow); - layoutWindow.root = row; - this.windows.set(windowId, layoutWindow); - row.drop(node, DockLocation.CENTER, 0); - - if (isMaximized) { - this.rootWindow.maximizedTabSet = undefined; - } + else { + return this.getFirstTabSet(child); } - this.removeEmptyWindows(); - break; - } - case Actions.POPOUT_TAB: { - const node = this.idMap.get(action.data.node); - if (node instanceof TabNode) { - const windowId = randomUUID(); - let r = Rect.empty(); - if (node.getParent() instanceof TabSetNode) { - r = node.getParent()!.getRect(); - } else { - r = (node.getParent() as BorderNode).getContentRect(); - } - const oldLayoutWindow = this.windows.get(node.getWindowId())!; - const layoutWindow = new LayoutWindow( - windowId, - oldLayoutWindow.toScreenRectFunction(r), - ); - const tabsetId = randomUUID(); - const json = { - type: 'row', - children: [{ type: 'tabset', id: tabsetId }], - }; - const row = RowNode.fromJson(json, this, layoutWindow); - layoutWindow.root = row; - this.windows.set(windowId, layoutWindow); - - const tabset = this.idMap.get(tabsetId) as TabSetNode & IDropTarget; - tabset.drop(node, DockLocation.CENTER, 0, true); + } + + /** + * Loads the model from the given json object + * @param json the json model to load + * @returns {Model} a new Model object + */ + static fromJson(json: IJsonModel) { + const model = new Model(); + Model.attributeDefinitions.fromJson(json.global, model.attributes); + + if (json.borders) { + model.borders = BorderSet.fromJson(json.borders, model); } - this.removeEmptyWindows(); - break; - } - case Actions.CLOSE_WINDOW: { - const window = this.windows.get(action.data.windowId); - if (window) { - this.rootWindow.root?.drop(window!.root!, DockLocation.CENTER, -1); - this.rootWindow.visitNodes((node, level) => { - if (node instanceof RowNode) { - node.setWindowId(Model.MAIN_WINDOW_ID); + if (json.popouts) { + for (const windowId in json.popouts) { + const windowJson = json.popouts[windowId]; + const layoutWindow = LayoutWindow.fromJson(windowJson, model, windowId); + model.windows.set(windowId, layoutWindow); } - }); + } + + model.rootWindow.root = RowNode.fromJson(json.layout, model, model.getwindowsMap().get(Model.MAIN_WINDOW_ID)!); + model.tidy(); // initial tidy of node tree + return model; + } - // this.getFirstTabSet().drop(window?.root!,DockLocation.CENTER, -1); + /** + * Converts the model to a json object + * @returns {IJsonModel} json object that represents this model + */ + toJson(): IJsonModel { + const global: any = {}; + Model.attributeDefinitions.toJson(global, this.attributes); + + // save state of nodes + this.visitNodes((node) => { + node.fireEvent("save", {}); + }); - this.windows.delete(action.data.windowId); + const windows: Record = {}; + for (const [id, window] of this.windows) { + if (id !== Model.MAIN_WINDOW_ID) { + windows[id] = window.toJson(); + } } - break; - } - case Actions.CREATE_WINDOW: { - const windowId = randomUUID(); - const layoutWindow = new LayoutWindow( - windowId, - Rect.fromJson(action.data.rect), - ); - const row = RowNode.fromJson(action.data.layout, this, layoutWindow); - layoutWindow.root = row; - this.windows.set(windowId, layoutWindow); - returnVal = windowId; - break; - } - case Actions.RENAME_TAB: { - const node = this.idMap.get(action.data.node); - if (node instanceof TabNode) { - node.setName(action.data.text); + + return { + global, + borders: this.borders.toJson(), + layout: this.rootWindow.root!.toJson(), + popouts: windows + }; + } + + getSplitterSize() { + return this.attributes.splitterSize as number; + } + + getSplitterExtra() { + return this.attributes.splitterExtra as number; + } + + isEnableEdgeDock() { + return this.attributes.enableEdgeDock as boolean; + } + + isSplitterEnableHandle() { + return this.attributes.splitterEnableHandle as boolean; + } + + /** + * Sets a function to allow/deny dropping a node + * @param onAllowDrop function that takes the drag node and DropInfo and returns true if the drop is allowed + */ + setOnAllowDrop(onAllowDrop: (dragNode: Node, dropInfo: DropInfo) => boolean) { + this.onAllowDrop = onAllowDrop; + } + + /** + * set callback called when a new TabSet is created. + * The tabNode can be undefined if it's the auto created first tabset in the root row (when the last + * tab is deleted, the root tabset can be recreated) + * @param onCreateTabSet + */ + setOnCreateTabSet(onCreateTabSet: (tabNode?: TabNode) => ITabSetAttributes) { + this.onCreateTabSet = onCreateTabSet; + } + + addChangeListener(listener: ((action: Action) => void)) { + this.changeListeners.push(listener); + } + + removeChangeListener(listener: ((action: Action) => void)) { + const pos = this.changeListeners.findIndex(l => l === listener); + if (pos !== -1) { + this.changeListeners.splice(pos, 1); } - break; - } - case Actions.SELECT_TAB: { - const tabNode = this.idMap.get(action.data.tabNode); - const windowId = action.data.windowId - ? action.data.windowId - : Model.MAIN_WINDOW_ID; - const window = this.windows.get(windowId)!; - if (tabNode instanceof TabNode) { - const parent = tabNode.getParent() as Node; - const pos = parent.getChildren().indexOf(tabNode); - - if (parent instanceof BorderNode) { - if (parent.getSelected() === pos) { - parent.setSelected(-1); - } else { - parent.setSelected(pos); - } - } else if (parent instanceof TabSetNode) { - if (parent.getSelected() !== pos) { - parent.setSelected(pos); + } + + toString() { + return JSON.stringify(this.toJson()); + } + + /***********************internal ********************************/ + + /** @internal */ + removeEmptyWindows() { + const emptyWindows = new Set(); + for (const [windowId] of this.windows) { + if (windowId !== Model.MAIN_WINDOW_ID) { + let count = 0; + this.visitWindowNodes(windowId, (node) => { + if (node instanceof TabNode) { + count++; + } + }); + if (count === 0) { + emptyWindows.add(windowId); + } } - window.activeTabSet = parent; - } } - break; - } - case Actions.SET_ACTIVE_TABSET: { - const windowId = action.data.windowId - ? action.data.windowId - : Model.MAIN_WINDOW_ID; - const window = this.windows.get(windowId)!; - if (action.data.tabsetNode === undefined) { - window.activeTabSet = undefined; - } else { - const tabsetNode = this.idMap.get(action.data.tabsetNode); - if (tabsetNode instanceof TabSetNode) { - window.activeTabSet = tabsetNode; - } - } - break; - } - case Actions.ADJUST_WEIGHTS: { - const row = this.idMap.get(action.data.nodeId) as RowNode; - const c = row.getChildren(); - for (let i = 0; i < c.length; i++) { - const n = c[i] as TabSetNode | RowNode; - n.setWeight(action.data.weights[i]); + + for (const windowId of emptyWindows) { + this.windows.delete(windowId); } - break; - } - case Actions.ADJUST_BORDER_SPLIT: { - const node = this.idMap.get(action.data.node); - if (node instanceof BorderNode) { - node.setSize(action.data.pos); + } + /** @internal */ + setActiveTabset(tabsetNode: TabSetNode | undefined, windowId: string) { + const window = this.windows.get(windowId); + if (window) { + if (tabsetNode) { + window.activeTabSet = tabsetNode; + } else { + window.activeTabSet = undefined; + } } - break; - } - case Actions.MAXIMIZE_TOGGLE: { - const windowId = action.data.windowId - ? action.data.windowId - : Model.MAIN_WINDOW_ID; - const window = this.windows.get(windowId)!; - const node = this.idMap.get(action.data.node); - if (node instanceof TabSetNode) { - if (node === window.maximizedTabSet) { - window.maximizedTabSet = undefined; - } else { - window.maximizedTabSet = node; - window.activeTabSet = node; - } + } + + /** @internal */ + setMaximizedTabset(tabsetNode: (TabSetNode | undefined), windowId: string) { + const window = this.windows.get(windowId); + if (window) { + if (tabsetNode) { + window.maximizedTabSet = tabsetNode; + } else { + window.maximizedTabSet = undefined; + } } + } - break; - } - case Actions.UPDATE_MODEL_ATTRIBUTES: { - this.updateAttrs(action.data.json); - break; - } - - case Actions.UPDATE_NODE_ATTRIBUTES: { - const node = this.idMap.get(action.data.node)!; - node.updateAttrs(action.data.json); - break; - } - default: - break; - } - - this.updateIdMap(); - - for (const listener of this.changeListeners) { - listener(action); - } - - return returnVal; - } - - /** - * Get the currently active tabset node - */ - getActiveTabset(windowId: string = Model.MAIN_WINDOW_ID) { - const window = this.windows.get(windowId); - if ( - window && - window.activeTabSet && - this.getNodeById(window.activeTabSet.getId()) - ) { - return window.activeTabSet; - } else { - return undefined; - } - } - - /** - * Get the currently maximized tabset node - */ - getMaximizedTabset(windowId: string = Model.MAIN_WINDOW_ID) { - return this.windows.get(windowId)!.maximizedTabSet; - } - - /** - * Gets the root RowNode of the model - * @returns {RowNode} - */ - getRoot(windowId: string = Model.MAIN_WINDOW_ID) { - return this.windows.get(windowId)!.root!; - } - - isRootOrientationVertical() { - return this.attributes.rootOrientationVertical as boolean; - } - - isEnableRotateBorderIcons() { - return this.attributes.enableRotateBorderIcons as boolean; - } - - /** - * Gets the - * @returns {BorderSet|*} - */ - getBorderSet() { - return this.borders; - } - - getwindowsMap() { - return this.windows; - } - - /** - * Visits all the nodes in the model and calls the given function for each - * @param fn a function that takes visited node and a integer level as parameters - */ - visitNodes(fn: (node: Node, level: number) => void) { - this.borders.forEachNode(fn); - for (const [_, w] of this.windows) { - w.root!.forEachNode(fn, 0); - } - } - - visitWindowNodes(windowId: string, fn: (node: Node, level: number) => void) { - if (this.windows.has(windowId)) { - if (windowId === Model.MAIN_WINDOW_ID) { - this.borders.forEachNode(fn); - } - this.windows.get(windowId)!.visitNodes(fn); - } - } - - /** - * Gets a node by its id - * @param id the id to find - */ - getNodeById(id: string): Node | undefined { - return this.idMap.get(id); - } - - /** - * Finds the first/top left tab set of the given node. - * @param node The top node you want to begin searching from, deafults to the root node - * @returns The first Tab Set - */ - getFirstTabSet( - node = this.windows.get(Model.MAIN_WINDOW_ID)!.root as Node, - ): TabSetNode { - const child = node.getChildren()[0]; - if (child instanceof TabSetNode) { - return child; - } else { - return this.getFirstTabSet(child); - } - } - - /** - * Loads the model from the given json object - * @param json the json model to load - * @returns {Model} a new Model object - */ - static fromJson(json: IJsonModel) { - const model = new Model(); - Model.attributeDefinitions.fromJson(json.global, model.attributes); - - if (json.borders) { - model.borders = BorderSet.fromJson(json.borders, model); - } - if (json.popouts) { - for (const windowId in json.popouts) { - const windowJson = json.popouts[windowId]; - const layoutWindow = LayoutWindow.fromJson(windowJson, model, windowId); - model.windows.set(windowId, layoutWindow); - } - } - - model.rootWindow.root = RowNode.fromJson( - json.layout, - model, - model.getwindowsMap().get(Model.MAIN_WINDOW_ID)!, - ); - model.tidy(); // initial tidy of node tree - return model; - } - - /** - * Converts the model to a json object - * @returns {IJsonModel} json object that represents this model - */ - toJson(): IJsonModel { - const global: any = {}; - Model.attributeDefinitions.toJson(global, this.attributes); - - // save state of nodes - this.visitNodes((node) => { - node.fireEvent('save', {}); - }); - - const windows: Record = {}; - for (const [id, window] of this.windows) { - if (id !== Model.MAIN_WINDOW_ID) { - windows[id] = window.toJson(); - } - } - - return { - global, - borders: this.borders.toJson(), - layout: this.rootWindow.root!.toJson(), - popouts: windows, - }; - } - - getSplitterSize() { - return this.attributes.splitterSize as number; - } - - getSplitterExtra() { - return this.attributes.splitterExtra as number; - } - - isEnableEdgeDock() { - return this.attributes.enableEdgeDock as boolean; - } - - isSplitterEnableHandle() { - return this.attributes.splitterEnableHandle as boolean; - } - - /** - * Sets a function to allow/deny dropping a node - * @param onAllowDrop function that takes the drag node and DropInfo and returns true if the drop is allowed - */ - setOnAllowDrop(onAllowDrop: (dragNode: Node, dropInfo: DropInfo) => boolean) { - this.onAllowDrop = onAllowDrop; - } - - /** - * set callback called when a new TabSet is created. - * The tabNode can be undefined if it's the auto created first tabset in the root row (when the last - * tab is deleted, the root tabset can be recreated) - * @param onCreateTabSet - */ - setOnCreateTabSet(onCreateTabSet: (tabNode?: TabNode) => ITabSetAttributes) { - this.onCreateTabSet = onCreateTabSet; - } - - addChangeListener(listener: (action: Action) => void) { - this.changeListeners.push(listener); - } - - removeChangeListener(listener: (action: Action) => void) { - const pos = this.changeListeners.findIndex((l) => l === listener); - if (pos !== -1) { - this.changeListeners.splice(pos, 1); - } - } - - toString() { - return JSON.stringify(this.toJson()); - } - - /***********************internal ********************************/ - - /** @internal */ - removeEmptyWindows() { - const emptyWindows = new Set(); - for (const [windowId] of this.windows) { - if (windowId !== Model.MAIN_WINDOW_ID) { - let count = 0; - this.visitWindowNodes(windowId, (node) => { - if (node instanceof TabNode) { - count++; - } + /** @internal */ + updateIdMap() { + // regenerate idMap to stop it building up + this.idMap.clear(); + this.visitNodes((node) => { + this.idMap.set(node.getId(), node) + // if (node instanceof RowNode) { + // node.normalizeWeights(); + // } }); - if (count === 0) { - emptyWindows.add(windowId); + // console.log(JSON.stringify(Object.keys(this._idMap))); + } + + /** @internal */ + addNode(node: Node) { + const id = node.getId(); + if (this.idMap.has(id)) { + throw new Error(`Error: each node must have a unique id, duplicate id:${node.getId()}`); } - } - } - - for (const windowId of emptyWindows) { - this.windows.delete(windowId); - } - } - /** @internal */ - setActiveTabset(tabsetNode: TabSetNode | undefined, windowId: string) { - const window = this.windows.get(windowId); - if (window) { - if (tabsetNode) { - window.activeTabSet = tabsetNode; - } else { - window.activeTabSet = undefined; - } - } - } - - /** @internal */ - setMaximizedTabset(tabsetNode: TabSetNode | undefined, windowId: string) { - const window = this.windows.get(windowId); - if (window) { - if (tabsetNode) { - window.maximizedTabSet = tabsetNode; - } else { - window.maximizedTabSet = undefined; - } - } - } - - /** @internal */ - updateIdMap() { - // regenerate idMap to stop it building up - this.idMap.clear(); - this.visitNodes((node) => { - this.idMap.set(node.getId(), node); - // if (node instanceof RowNode) { - // node.normalizeWeights(); - // } - }); - // console.log(JSON.stringify(Object.keys(this._idMap))); - } - - /** @internal */ - addNode(node: Node) { - const id = node.getId(); - if (this.idMap.has(id)) { - throw new Error( - `Error: each node must have a unique id, duplicate id:${node.getId()}`, - ); - } - - this.idMap.set(id, node); - } - - /** @internal */ - findDropTargetNode( - windowId: string, - dragNode: Node & IDraggable, - x: number, - y: number, - ) { - let node = (this.windows.get(windowId)!.root as RowNode).findDropTargetNode( - windowId, - dragNode, - x, - y, - ); - if (node === undefined && windowId === Model.MAIN_WINDOW_ID) { - node = this.borders.findDropTargetNode(dragNode, x, y); - } - return node; - } - - /** @internal */ - tidy() { - // console.log("before _tidy", this.toString()); - for (const [_, window] of this.windows) { - window.root!.tidy(); - } - // console.log("after _tidy", this.toString()); - } - - /** @internal */ - updateAttrs(json: any) { - Model.attributeDefinitions.update(json, this.attributes); - } - - /** @internal */ - nextUniqueId() { - return '#' + randomUUID(); - } - - /** @internal */ - getAttribute(name: string): any { - return this.attributes[name]; - } - - /** @internal */ - getOnAllowDrop() { - return this.onAllowDrop; - } - - /** @internal */ - getOnCreateTabSet() { - return this.onCreateTabSet; - } - - static toTypescriptInterfaces() { - Model.attributeDefinitions.pairAttributes( - 'RowNode', - RowNode.getAttributeDefinitions(), - ); - Model.attributeDefinitions.pairAttributes( - 'TabSetNode', - TabSetNode.getAttributeDefinitions(), - ); - Model.attributeDefinitions.pairAttributes( - 'TabNode', - TabNode.getAttributeDefinitions(), - ); - Model.attributeDefinitions.pairAttributes( - 'BorderNode', - BorderNode.getAttributeDefinitions(), - ); - - const sb = []; - sb.push( - Model.attributeDefinitions.toTypescriptInterface('Global', undefined), - ); - sb.push( - RowNode.getAttributeDefinitions().toTypescriptInterface( - 'Row', - Model.attributeDefinitions, - ), - ); - sb.push( - TabSetNode.getAttributeDefinitions().toTypescriptInterface( - 'TabSet', - Model.attributeDefinitions, - ), - ); - sb.push( - TabNode.getAttributeDefinitions().toTypescriptInterface( - 'Tab', - Model.attributeDefinitions, - ), - ); - sb.push( - BorderNode.getAttributeDefinitions().toTypescriptInterface( - 'Border', - Model.attributeDefinitions, - ), - ); - console.log(sb.join('\n')); - } - - /** @internal */ - private static createAttributeDefinitions(): AttributeDefinitions { - const attributeDefinitions = new AttributeDefinitions(); - - attributeDefinitions - .add('enableEdgeDock', true) - .setType(Attribute.BOOLEAN) - .setDescription( - `enable docking to the edges of the layout, this will show the edge indicators`, - ); - attributeDefinitions - .add('rootOrientationVertical', false) - .setType(Attribute.BOOLEAN) - .setDescription( - `the top level 'row' will layout horizontally by default, set this option true to make it layout vertically`, - ); - attributeDefinitions - .add('enableRotateBorderIcons', true) - .setType(Attribute.BOOLEAN) - .setDescription( - `boolean indicating if tab icons should rotate with the text in the left and right borders`, - ); - - // splitter - attributeDefinitions - .add('splitterSize', 8) - .setType(Attribute.NUMBER) - .setDescription( - `width in pixels of all splitters between tabsets/borders`, - ); - attributeDefinitions - .add('splitterExtra', 0) - .setType(Attribute.NUMBER) - .setDescription( - `additional width in pixels of the splitter hit test area`, - ); - attributeDefinitions - .add('splitterEnableHandle', false) - .setType(Attribute.BOOLEAN) - .setDescription(`enable a small centralized handle on all splitters`); - - // tab - attributeDefinitions.add('tabEnableClose', true).setType(Attribute.BOOLEAN); - attributeDefinitions.add('tabCloseType', 1).setType('ICloseType'); - attributeDefinitions - .add('tabEnablePopout', false) - .setType(Attribute.BOOLEAN) - .setAlias('tabEnableFloat'); - attributeDefinitions - .add('tabEnablePopoutIcon', true) - .setType(Attribute.BOOLEAN); - attributeDefinitions - .add('tabEnablePopoutOverlay', false) - .setType(Attribute.BOOLEAN); - attributeDefinitions.add('tabEnableDrag', true).setType(Attribute.BOOLEAN); - attributeDefinitions - .add('tabEnableRename', true) - .setType(Attribute.BOOLEAN); - attributeDefinitions - .add('tabContentClassName', undefined) - .setType(Attribute.STRING); - attributeDefinitions - .add('tabClassName', undefined) - .setType(Attribute.STRING); - attributeDefinitions.add('tabIcon', undefined).setType(Attribute.STRING); - attributeDefinitions - .add('tabEnableRenderOnDemand', true) - .setType(Attribute.BOOLEAN); - attributeDefinitions.add('tabDragSpeed', 0.3).setType(Attribute.NUMBER); - attributeDefinitions.add('tabBorderWidth', -1).setType(Attribute.NUMBER); - attributeDefinitions.add('tabBorderHeight', -1).setType(Attribute.NUMBER); - - // tabset - attributeDefinitions - .add('tabSetEnableDeleteWhenEmpty', true) - .setType(Attribute.BOOLEAN); - attributeDefinitions - .add('tabSetEnableDrop', true) - .setType(Attribute.BOOLEAN); - attributeDefinitions - .add('tabSetEnableDrag', true) - .setType(Attribute.BOOLEAN); - attributeDefinitions - .add('tabSetEnableDivide', true) - .setType(Attribute.BOOLEAN); - attributeDefinitions - .add('tabSetEnableMaximize', true) - .setType(Attribute.BOOLEAN); - attributeDefinitions - .add('tabSetEnableClose', false) - .setType(Attribute.BOOLEAN); - attributeDefinitions - .add('tabSetEnableSingleTabStretch', false) - .setType(Attribute.BOOLEAN); - attributeDefinitions - .add('tabSetAutoSelectTab', true) - .setType(Attribute.BOOLEAN); - attributeDefinitions - .add('tabSetEnableActiveIcon', false) - .setType(Attribute.BOOLEAN); - attributeDefinitions - .add('tabSetClassNameTabStrip', undefined) - .setType(Attribute.STRING); - attributeDefinitions - .add('tabSetEnableTabStrip', true) - .setType(Attribute.BOOLEAN); - attributeDefinitions - .add('tabSetEnableTabWrap', false) - .setType(Attribute.BOOLEAN); - attributeDefinitions - .add('tabSetTabLocation', 'top') - .setType('ITabLocation'); - attributeDefinitions - .add('tabMinWidth', DefaultMin) - .setType(Attribute.NUMBER); - attributeDefinitions - .add('tabMinHeight', DefaultMin) - .setType(Attribute.NUMBER); - attributeDefinitions - .add('tabSetMinWidth', DefaultMin) - .setType(Attribute.NUMBER); - attributeDefinitions - .add('tabSetMinHeight', DefaultMin) - .setType(Attribute.NUMBER); - attributeDefinitions - .add('tabMaxWidth', DefaultMax) - .setType(Attribute.NUMBER); - attributeDefinitions - .add('tabMaxHeight', DefaultMax) - .setType(Attribute.NUMBER); - attributeDefinitions - .add('tabSetMaxWidth', DefaultMax) - .setType(Attribute.NUMBER); - attributeDefinitions - .add('tabSetMaxHeight', DefaultMax) - .setType(Attribute.NUMBER); - attributeDefinitions - .add('tabSetEnableTabScrollbar', false) - .setType(Attribute.BOOLEAN); - - // border - attributeDefinitions.add('borderSize', 200).setType(Attribute.NUMBER); - attributeDefinitions - .add('borderMinSize', DefaultMin) - .setType(Attribute.NUMBER); - attributeDefinitions - .add('borderMaxSize', DefaultMax) - .setType(Attribute.NUMBER); - attributeDefinitions - .add('borderEnableDrop', true) - .setType(Attribute.BOOLEAN); - attributeDefinitions - .add('borderAutoSelectTabWhenOpen', true) - .setType(Attribute.BOOLEAN); - attributeDefinitions - .add('borderAutoSelectTabWhenClosed', false) - .setType(Attribute.BOOLEAN); - attributeDefinitions - .add('borderClassName', undefined) - .setType(Attribute.STRING); - attributeDefinitions - .add('borderEnableAutoHide', false) - .setType(Attribute.BOOLEAN); - attributeDefinitions - .add('borderEnableTabScrollbar', false) - .setType(Attribute.BOOLEAN); - - return attributeDefinitions; - } + + this.idMap.set(id, node); + } + + /** @internal */ + findDropTargetNode(windowId: string, dragNode: Node & IDraggable, x: number, y: number) { + let node = (this.windows.get(windowId)!.root as RowNode).findDropTargetNode(windowId, dragNode, x, y); + if (node === undefined && windowId === Model.MAIN_WINDOW_ID) { + node = this.borders.findDropTargetNode(dragNode, x, y); + } + return node; + } + + /** @internal */ + tidy() { + // console.log("before _tidy", this.toString()); + for (const [_, window] of this.windows) { + window.root!.tidy(); + } + // console.log("after _tidy", this.toString()); + } + + /** @internal */ + updateAttrs(json: any) { + Model.attributeDefinitions.update(json, this.attributes); + } + + /** @internal */ + nextUniqueId() { + return '#' + randomUUID(); + } + + /** @internal */ + getAttribute(name: string): any { + return this.attributes[name]; + } + + /** @internal */ + getOnAllowDrop() { + return this.onAllowDrop; + } + + /** @internal */ + getOnCreateTabSet() { + return this.onCreateTabSet; + } + + static toTypescriptInterfaces() { + Model.attributeDefinitions.pairAttributes("RowNode", RowNode.getAttributeDefinitions()); + Model.attributeDefinitions.pairAttributes("TabSetNode", TabSetNode.getAttributeDefinitions()); + Model.attributeDefinitions.pairAttributes("TabNode", TabNode.getAttributeDefinitions()); + Model.attributeDefinitions.pairAttributes("BorderNode", BorderNode.getAttributeDefinitions()); + + const sb = []; + sb.push(Model.attributeDefinitions.toTypescriptInterface("Global", undefined)); + sb.push(RowNode.getAttributeDefinitions().toTypescriptInterface("Row", Model.attributeDefinitions)); + sb.push(TabSetNode.getAttributeDefinitions().toTypescriptInterface("TabSet", Model.attributeDefinitions)); + sb.push(TabNode.getAttributeDefinitions().toTypescriptInterface("Tab", Model.attributeDefinitions)); + sb.push(BorderNode.getAttributeDefinitions().toTypescriptInterface("Border", Model.attributeDefinitions)); + console.log(sb.join("\n")); + } + + /** @internal */ + private static createAttributeDefinitions(): AttributeDefinitions { + const attributeDefinitions = new AttributeDefinitions(); + + attributeDefinitions.add("enableEdgeDock", true).setType(Attribute.BOOLEAN).setDescription( + `enable docking to the edges of the layout, this will show the edge indicators` + ); + attributeDefinitions.add("rootOrientationVertical", false).setType(Attribute.BOOLEAN).setDescription( + `the top level 'row' will layout horizontally by default, set this option true to make it layout vertically` + ); + attributeDefinitions.add("enableRotateBorderIcons", true).setType(Attribute.BOOLEAN).setDescription( + `boolean indicating if tab icons should rotate with the text in the left and right borders` + ); + + // splitter + attributeDefinitions.add("splitterSize", 8).setType(Attribute.NUMBER).setDescription( + `width in pixels of all splitters between tabsets/borders` + ); + attributeDefinitions.add("splitterExtra", 0).setType(Attribute.NUMBER).setDescription( + `additional width in pixels of the splitter hit test area` + ); + attributeDefinitions.add("splitterEnableHandle", false).setType(Attribute.BOOLEAN).setDescription( + `enable a small centralized handle on all splitters` + ); + + // tab + attributeDefinitions.add("tabEnableClose", true).setType(Attribute.BOOLEAN); + attributeDefinitions.add("tabCloseType", 1).setType("ICloseType"); + attributeDefinitions.add("tabEnablePopout", false).setType(Attribute.BOOLEAN).setAlias("tabEnableFloat"); + attributeDefinitions.add("tabEnablePopoutIcon", true).setType(Attribute.BOOLEAN); + attributeDefinitions.add("tabEnablePopoutOverlay", false).setType(Attribute.BOOLEAN); + attributeDefinitions.add("tabEnableDrag", true).setType(Attribute.BOOLEAN); + attributeDefinitions.add("tabEnableRename", true).setType(Attribute.BOOLEAN); + attributeDefinitions.add("tabContentClassName", undefined).setType(Attribute.STRING); + attributeDefinitions.add("tabClassName", undefined).setType(Attribute.STRING); + attributeDefinitions.add("tabIcon", undefined).setType(Attribute.STRING); + attributeDefinitions.add("tabEnableRenderOnDemand", true).setType(Attribute.BOOLEAN); + attributeDefinitions.add("tabDragSpeed", 0.3).setType(Attribute.NUMBER); + attributeDefinitions.add("tabBorderWidth", -1).setType(Attribute.NUMBER); + attributeDefinitions.add("tabBorderHeight", -1).setType(Attribute.NUMBER); + + // tabset + attributeDefinitions.add("tabSetEnableDeleteWhenEmpty", true).setType(Attribute.BOOLEAN); + attributeDefinitions.add("tabSetEnableDrop", true).setType(Attribute.BOOLEAN); + attributeDefinitions.add("tabSetEnableDrag", true).setType(Attribute.BOOLEAN); + attributeDefinitions.add("tabSetEnableDivide", true).setType(Attribute.BOOLEAN); + attributeDefinitions.add("tabSetEnableMaximize", true).setType(Attribute.BOOLEAN); + attributeDefinitions.add("tabSetEnableClose", false).setType(Attribute.BOOLEAN); + attributeDefinitions.add("tabSetEnableSingleTabStretch", false).setType(Attribute.BOOLEAN); + attributeDefinitions.add("tabSetAutoSelectTab", true).setType(Attribute.BOOLEAN); + attributeDefinitions.add("tabSetEnableActiveIcon", false).setType(Attribute.BOOLEAN); + attributeDefinitions.add("tabSetClassNameTabStrip", undefined).setType(Attribute.STRING); + attributeDefinitions.add("tabSetEnableTabStrip", true).setType(Attribute.BOOLEAN); + attributeDefinitions.add("tabSetEnableTabWrap", false).setType(Attribute.BOOLEAN); + attributeDefinitions.add("tabSetTabLocation", "top").setType("ITabLocation"); + attributeDefinitions.add("tabMinWidth", DefaultMin).setType(Attribute.NUMBER); + attributeDefinitions.add("tabMinHeight", DefaultMin).setType(Attribute.NUMBER); + attributeDefinitions.add("tabSetMinWidth", DefaultMin).setType(Attribute.NUMBER); + attributeDefinitions.add("tabSetMinHeight", DefaultMin).setType(Attribute.NUMBER); + attributeDefinitions.add("tabMaxWidth", DefaultMax).setType(Attribute.NUMBER); + attributeDefinitions.add("tabMaxHeight", DefaultMax).setType(Attribute.NUMBER); + attributeDefinitions.add("tabSetMaxWidth", DefaultMax).setType(Attribute.NUMBER); + attributeDefinitions.add("tabSetMaxHeight", DefaultMax).setType(Attribute.NUMBER); + attributeDefinitions.add("tabSetEnableTabScrollbar", false).setType(Attribute.BOOLEAN); + + // border + attributeDefinitions.add("borderSize", 200).setType(Attribute.NUMBER); + attributeDefinitions.add("borderMinSize", DefaultMin).setType(Attribute.NUMBER); + attributeDefinitions.add("borderMaxSize", DefaultMax).setType(Attribute.NUMBER); + attributeDefinitions.add("borderEnableDrop", true).setType(Attribute.BOOLEAN); + attributeDefinitions.add("borderAutoSelectTabWhenOpen", true).setType(Attribute.BOOLEAN); + attributeDefinitions.add("borderAutoSelectTabWhenClosed", false).setType(Attribute.BOOLEAN); + attributeDefinitions.add("borderClassName", undefined).setType(Attribute.STRING); + attributeDefinitions.add("borderEnableAutoHide", false).setType(Attribute.BOOLEAN); + attributeDefinitions.add("borderEnableTabScrollbar", false).setType(Attribute.BOOLEAN); + + return attributeDefinitions; + } } + diff --git a/src/model/Node.ts b/src/model/Node.ts index b5e5ca3f..ee12d0bf 100755 --- a/src/model/Node.ts +++ b/src/model/Node.ts @@ -1,315 +1,276 @@ -import { AttributeDefinitions } from '../AttributeDefinitions'; -import { DockLocation } from '../DockLocation'; -import { DropInfo } from '../DropInfo'; -import { Orientation } from '../Orientation'; -import { Rect } from '../Rect'; -import { IDraggable } from './IDraggable'; -import { - IJsonBorderNode, - IJsonRowNode, - IJsonTabNode, - IJsonTabSetNode, -} from './IJsonModel'; -import { Model } from './Model'; +import { AttributeDefinitions } from "../AttributeDefinitions"; +import { DockLocation } from "../DockLocation"; +import { DropInfo } from "../DropInfo"; +import { Orientation } from "../Orientation"; +import { Rect } from "../Rect"; +import { IDraggable } from "./IDraggable"; +import { IJsonBorderNode, IJsonRowNode, IJsonTabNode, IJsonTabSetNode } from "./IJsonModel"; +import { Model } from "./Model"; export abstract class Node { - /** @internal */ - protected model: Model; - /** @internal */ - protected attributes: Record; - /** @internal */ - protected parent?: Node; - /** @internal */ - protected children: Node[]; - /** @internal */ - protected rect: Rect; - /** @internal */ - protected path: string; - /** @internal */ - protected listeners: Map void>; - - /** @internal */ - protected constructor(_model: Model) { - this.model = _model; - this.attributes = {}; - this.children = []; - this.rect = Rect.empty(); - this.listeners = new Map(); - this.path = ''; - } - - getId() { - let id = this.attributes.id; - if (id !== undefined) { - return id as string; + /** @internal */ + protected model: Model; + /** @internal */ + protected attributes: Record; + /** @internal */ + protected parent?: Node; + /** @internal */ + protected children: Node[]; + /** @internal */ + protected rect: Rect; + /** @internal */ + protected path: string; + /** @internal */ + protected listeners: Map void>; + + /** @internal */ + protected constructor(_model: Model) { + this.model = _model; + this.attributes = {}; + this.children = []; + this.rect = Rect.empty(); + this.listeners = new Map(); + this.path = ""; } - id = this.model.nextUniqueId(); - this.setId(id); + getId() { + let id = this.attributes.id; + if (id !== undefined) { + return id as string; + } + + id = this.model.nextUniqueId(); + this.setId(id); + + return id as string; + } + + getModel() { + return this.model; + } + + getType() { + return this.attributes.type as string; + } + + getParent() { + return this.parent; + } + + getChildren() { + return this.children; + } + + getRect() { + return this.rect; + } + + getPath() { + return this.path; + } - return id as string; - } + getOrientation(): Orientation { + if (this.parent === undefined) { + return this.model.isRootOrientationVertical() ? Orientation.VERT : Orientation.HORZ; + } else { + return Orientation.flip(this.parent.getOrientation()); + } + } + + // event can be: resize, visibility, maximize (on tabset), close + setEventListener(event: string, callback: (params: any) => void) { + this.listeners.set(event, callback); + } + + removeEventListener(event: string) { + this.listeners.delete(event); + } + + abstract toJson(): IJsonRowNode | IJsonBorderNode | IJsonTabSetNode | IJsonTabNode | undefined; + + /** @internal */ + setId(id: string) { + this.attributes.id = id; + } + + /** @internal */ + fireEvent(event: string, params: any) { + // console.log(this._type, " fireEvent " + event + " " + JSON.stringify(params)); + if (this.listeners.has(event)) { + this.listeners.get(event)!(params); + } + } + + /** @internal */ + getAttr(name: string) { + let val = this.attributes[name]; + + if (val === undefined) { + const modelName = this.getAttributeDefinitions().getModelName(name); + if (modelName !== undefined) { + val = this.model.getAttribute(modelName); + } + } - getModel() { - return this.model; - } + // console.log(name + "=" + val); + return val; + } - getType() { - return this.attributes.type as string; - } + /** @internal */ + forEachNode(fn: (node: Node, level: number) => void, level: number) { + fn(this, level); + level++; + for (const node of this.children) { + node.forEachNode(fn, level); + } + } - getParent() { - return this.parent; - } + /** @internal */ + setPaths(path: string) { + let i = 0; + + for (const node of this.children) { + let newPath = path; + if (node.getType() === "row") { + newPath += "/r" + i; + } else if (node.getType() === "tabset") { + newPath += "/ts" + i; + } else if (node.getType() === "tab") { + newPath += "/t" + i; + } - getChildren() { - return this.children; - } + node.path = newPath; - getRect() { - return this.rect; - } + node.setPaths(newPath); + i++; + } + } - getPath() { - return this.path; - } + /** @internal */ + setParent(parent: Node) { + this.parent = parent; + } - getOrientation(): Orientation { - if (this.parent === undefined) { - return this.model.isRootOrientationVertical() - ? Orientation.VERT - : Orientation.HORZ; - } else { - return Orientation.flip(this.parent.getOrientation()); + /** @internal */ + setRect(rect: Rect) { + this.rect = rect; } - } - - // event can be: resize, visibility, maximize (on tabset), close - setEventListener(event: string, callback: (params: any) => void) { - this.listeners.set(event, callback); - } - - removeEventListener(event: string) { - this.listeners.delete(event); - } - - abstract toJson(): - | IJsonRowNode - | IJsonBorderNode - | IJsonTabSetNode - | IJsonTabNode - | undefined; - - /** @internal */ - setId(id: string) { - this.attributes.id = id; - } - - /** @internal */ - fireEvent(event: string, params: any) { - // console.log(this._type, " fireEvent " + event + " " + JSON.stringify(params)); - if (this.listeners.has(event)) { - this.listeners.get(event)!(params); + + /** @internal */ + setPath(path: string) { + this.path = path; } - } - /** @internal */ - getAttr(name: string) { - let val = this.attributes[name]; + /** @internal */ + setWeight(weight: number) { + this.attributes.weight = weight; + } - if (val === undefined) { - const modelName = this.getAttributeDefinitions().getModelName(name); - if (modelName !== undefined) { - val = this.model.getAttribute(modelName); - } + /** @internal */ + setSelected(index: number) { + this.attributes.selected = index; } - // console.log(name + "=" + val); - return val; - } + /** @internal */ + findDropTargetNode(windowId: string, dragNode: Node & IDraggable, x: number, y: number): DropInfo | undefined { + let rtn: DropInfo | undefined; + if (this.rect.contains(x, y)) { + if (this.model.getMaximizedTabset(windowId) !== undefined) { + rtn = this.model.getMaximizedTabset(windowId)!.canDrop(dragNode, x, y); + } else { + rtn = this.canDrop(dragNode, x, y); + if (rtn === undefined) { + if (this.children.length !== 0) { + for (const child of this.children) { + rtn = child.findDropTargetNode(windowId, dragNode, x, y); + if (rtn !== undefined) { + break; + } + } + } + } + } + } - /** @internal */ - forEachNode(fn: (node: Node, level: number) => void, level: number) { - fn(this, level); - level++; - for (const node of this.children) { - node.forEachNode(fn, level); + return rtn; } - } - - /** @internal */ - setPaths(path: string) { - let i = 0; - - for (const node of this.children) { - let newPath = path; - if (node.getType() === 'row') { - newPath += '/r' + i; - } else if (node.getType() === 'tabset') { - newPath += '/ts' + i; - } else if (node.getType() === 'tab') { - newPath += '/t' + i; - } - - node.path = newPath; - - node.setPaths(newPath); - i++; + + /** @internal */ + canDrop(dragNode: Node & IDraggable, x: number, y: number): DropInfo | undefined { + return undefined; } - } - - /** @internal */ - setParent(parent: Node) { - this.parent = parent; - } - - /** @internal */ - setRect(rect: Rect) { - this.rect = rect; - } - - /** @internal */ - setPath(path: string) { - this.path = path; - } - - /** @internal */ - setWeight(weight: number) { - this.attributes.weight = weight; - } - - /** @internal */ - setSelected(index: number) { - this.attributes.selected = index; - } - - /** @internal */ - findDropTargetNode( - windowId: string, - dragNode: Node & IDraggable, - x: number, - y: number, - ): DropInfo | undefined { - let rtn: DropInfo | undefined; - if (this.rect.contains(x, y)) { - if (this.model.getMaximizedTabset(windowId) !== undefined) { - rtn = this.model.getMaximizedTabset(windowId)!.canDrop(dragNode, x, y); - } else { - rtn = this.canDrop(dragNode, x, y); - if (rtn === undefined) { - if (this.children.length !== 0) { - for (const child of this.children) { - rtn = child.findDropTargetNode(windowId, dragNode, x, y); - if (rtn !== undefined) { - break; - } + + /** @internal */ + canDockInto(dragNode: Node & IDraggable, dropInfo: DropInfo | undefined): boolean { + if (dropInfo != null) { + if (dropInfo.location === DockLocation.CENTER && dropInfo.node.isEnableDrop() === false) { + return false; + } + + // prevent named tabset docking into another tabset, since this would lose the header + if (dropInfo.location === DockLocation.CENTER && dragNode.getType() === "tabset" && dragNode.getName() !== undefined) { + return false; + } + + if (dropInfo.location !== DockLocation.CENTER && dropInfo.node.isEnableDivide() === false) { + return false; + } + + // finally check model callback to check if drop allowed + if (this.model.getOnAllowDrop()) { + return (this.model.getOnAllowDrop() as (dragNode: Node, dropInfo: DropInfo) => boolean)(dragNode, dropInfo); } - } } - } + return true; } - return rtn; - } - - /** @internal */ - canDrop( - dragNode: Node & IDraggable, - x: number, - y: number, - ): DropInfo | undefined { - return undefined; - } - - /** @internal */ - canDockInto( - dragNode: Node & IDraggable, - dropInfo: DropInfo | undefined, - ): boolean { - if (dropInfo != null) { - if ( - dropInfo.location === DockLocation.CENTER && - dropInfo.node.isEnableDrop() === false - ) { - return false; - } - - // prevent named tabset docking into another tabset, since this would lose the header - if ( - dropInfo.location === DockLocation.CENTER && - dragNode.getType() === 'tabset' && - dragNode.getName() !== undefined - ) { - return false; - } - - if ( - dropInfo.location !== DockLocation.CENTER && - dropInfo.node.isEnableDivide() === false - ) { - return false; - } - - // finally check model callback to check if drop allowed - if (this.model.getOnAllowDrop()) { - return ( - this.model.getOnAllowDrop() as ( - dragNode: Node, - dropInfo: DropInfo, - ) => boolean - )(dragNode, dropInfo); - } + /** @internal */ + removeChild(childNode: Node) { + const pos = this.children.indexOf(childNode); + if (pos !== -1) { + this.children.splice(pos, 1); + } + return pos; } - return true; - } - - /** @internal */ - removeChild(childNode: Node) { - const pos = this.children.indexOf(childNode); - if (pos !== -1) { - this.children.splice(pos, 1); + + /** @internal */ + addChild(childNode: Node, pos?: number) { + if (pos != null) { + this.children.splice(pos, 0, childNode); + } else { + this.children.push(childNode); + pos = this.children.length - 1; + } + childNode.parent = this; + return pos; } - return pos; - } - - /** @internal */ - addChild(childNode: Node, pos?: number) { - if (pos != null) { - this.children.splice(pos, 0, childNode); - } else { - this.children.push(childNode); - pos = this.children.length - 1; + + /** @internal */ + removeAll() { + this.children = []; } - childNode.parent = this; - return pos; - } - - /** @internal */ - removeAll() { - this.children = []; - } - - /** @internal */ - styleWithPosition(style?: Record) { - if (style == null) { - style = {}; + + /** @internal */ + styleWithPosition(style?: Record) { + if (style == null) { + style = {}; + } + return this.rect.styleWithPosition(style); + } + + /** @internal */ + isEnableDivide() { + return true; } - return this.rect.styleWithPosition(style); - } - - /** @internal */ - isEnableDivide() { - return true; - } - - /** @internal */ - toAttributeString() { - return JSON.stringify(this.attributes, undefined, '\t'); - } - - // implemented by subclasses - /** @internal */ - abstract updateAttrs(json: any): void; - /** @internal */ - abstract getAttributeDefinitions(): AttributeDefinitions; + + /** @internal */ + toAttributeString() { + return JSON.stringify(this.attributes, undefined, "\t"); + } + + // implemented by subclasses + /** @internal */ + abstract updateAttrs(json: any): void; + /** @internal */ + abstract getAttributeDefinitions(): AttributeDefinitions; } diff --git a/src/model/RowNode.ts b/src/model/RowNode.ts index e27a14f5..d3fdae82 100755 --- a/src/model/RowNode.ts +++ b/src/model/RowNode.ts @@ -1,639 +1,559 @@ -import { TabNode } from './TabNode'; -import { Attribute } from '../Attribute'; -import { AttributeDefinitions } from '../AttributeDefinitions'; -import { DockLocation } from '../DockLocation'; -import { DropInfo } from '../DropInfo'; -import { Orientation } from '../Orientation'; -import { CLASSES } from '../Types'; -import { BorderNode } from './BorderNode'; -import { IDraggable } from './IDraggable'; -import { IDropTarget } from './IDropTarget'; -import { IJsonRowNode } from './IJsonModel'; -import { DefaultMax, DefaultMin, Model } from './Model'; -import { Node } from './Node'; -import { TabSetNode } from './TabSetNode'; -import { canDockToWindow } from '../view/Utils'; -import { LayoutWindow } from './LayoutWindow'; +import { TabNode } from "./TabNode"; +import { Attribute } from "../Attribute"; +import { AttributeDefinitions } from "../AttributeDefinitions"; +import { DockLocation } from "../DockLocation"; +import { DropInfo } from "../DropInfo"; +import { Orientation } from "../Orientation"; +import { CLASSES } from "../Types"; +import { BorderNode } from "./BorderNode"; +import { IDraggable } from "./IDraggable"; +import { IDropTarget } from "./IDropTarget"; +import { IJsonRowNode } from "./IJsonModel"; +import { DefaultMax, DefaultMin, Model } from "./Model"; +import { Node } from "./Node"; +import { TabSetNode } from "./TabSetNode"; +import { canDockToWindow } from "../view/Utils"; +import { LayoutWindow } from "./LayoutWindow"; export class RowNode extends Node implements IDropTarget { - static readonly TYPE = 'row'; + static readonly TYPE = "row"; + + /** @internal */ + static fromJson(json: any, model: Model, layoutWindow: LayoutWindow) { + const newLayoutNode = new RowNode(model, layoutWindow.windowId, json); + + if (json.children != null) { + for (const jsonChild of json.children) { + if (jsonChild.type === TabSetNode.TYPE) { + const child = TabSetNode.fromJson(jsonChild, model, layoutWindow); + newLayoutNode.addChild(child); + } else { + const child = RowNode.fromJson(jsonChild, model, layoutWindow); + newLayoutNode.addChild(child); + } + } + } - /** @internal */ - static fromJson(json: any, model: Model, layoutWindow: LayoutWindow) { - const newLayoutNode = new RowNode(model, layoutWindow.windowId, json); + return newLayoutNode; + } - if (json.children != null) { - for (const jsonChild of json.children) { - if (jsonChild.type === TabSetNode.TYPE) { - const child = TabSetNode.fromJson(jsonChild, model, layoutWindow); - newLayoutNode.addChild(child); - } else { - const child = RowNode.fromJson(jsonChild, model, layoutWindow); - newLayoutNode.addChild(child); - } - } + /** @internal */ + private static attributeDefinitions: AttributeDefinitions = RowNode.createAttributeDefinitions(); + + /** @internal */ + private windowId: string; + /** @internal */ + private minHeight: number; + /** @internal */ + private minWidth: number; + /** @internal */ + private maxHeight: number; + /** @internal */ + private maxWidth: number; + + /** @internal */ + constructor(model: Model, windowId: string, json: any) { + super(model); + + this.windowId = windowId; + this.minHeight = DefaultMin; + this.minWidth = DefaultMin; + this.maxHeight = DefaultMax; + this.maxWidth = DefaultMax; + RowNode.attributeDefinitions.fromJson(json, this.attributes); + this.normalizeWeights(); + model.addNode(this); } - return newLayoutNode; - } - - /** @internal */ - private static attributeDefinitions: AttributeDefinitions = - RowNode.createAttributeDefinitions(); - - /** @internal */ - private windowId: string; - /** @internal */ - private minHeight: number; - /** @internal */ - private minWidth: number; - /** @internal */ - private maxHeight: number; - /** @internal */ - private maxWidth: number; - - /** @internal */ - constructor(model: Model, windowId: string, json: any) { - super(model); - - this.windowId = windowId; - this.minHeight = DefaultMin; - this.minWidth = DefaultMin; - this.maxHeight = DefaultMax; - this.maxWidth = DefaultMax; - RowNode.attributeDefinitions.fromJson(json, this.attributes); - this.normalizeWeights(); - model.addNode(this); - } - - getWeight() { - return this.attributes.weight as number; - } - - toJson(): IJsonRowNode { - const json: any = {}; - RowNode.attributeDefinitions.toJson(json, this.attributes); - - json.children = []; - for (const child of this.children) { - json.children.push(child.toJson()); + getWeight() { + return this.attributes.weight as number; } - return json; - } - - /** @internal */ - getWindowId() { - return this.windowId; - } - - setWindowId(windowId: string) { - this.windowId = windowId; - } - - /** @internal */ - setWeight(weight: number) { - this.attributes.weight = weight; - } - - /** @internal */ - getSplitterBounds(index: number) { - const h = this.getOrientation() === Orientation.HORZ; - const c = this.getChildren(); - const ss = this.model.getSplitterSize(); - const fr = c[0].getRect(); - const lr = c[c.length - 1].getRect(); - let p = h ? [fr.x, lr.getRight()] : [fr.y, lr.getBottom()]; - const q = h ? [fr.x, lr.getRight()] : [fr.y, lr.getBottom()]; - - for (let i = 0; i < index; i++) { - const n = c[i] as TabSetNode | RowNode; - p[0] += h ? n.getMinWidth() : n.getMinHeight(); - q[0] += h ? n.getMaxWidth() : n.getMaxHeight(); - if (i > 0) { - p[0] += ss; - q[0] += ss; - } + toJson(): IJsonRowNode { + const json: any = {}; + RowNode.attributeDefinitions.toJson(json, this.attributes); + + json.children = []; + for (const child of this.children) { + json.children.push(child.toJson()); + } + + return json; } - for (let i = c.length - 1; i >= index; i--) { - const n = c[i] as TabSetNode | RowNode; - p[1] -= (h ? n.getMinWidth() : n.getMinHeight()) + ss; - q[1] -= (h ? n.getMaxWidth() : n.getMaxHeight()) + ss; + /** @internal */ + getWindowId() { + return this.windowId; } - p = [Math.max(q[1], p[0]), Math.min(q[0], p[1])]; + setWindowId(windowId: string) { + this.windowId = windowId; + } - return p; - } + /** @internal */ + setWeight(weight: number) { + this.attributes.weight = weight; + } - /** @internal */ - getSplitterInitials(index: number) { - const h = this.getOrientation() === Orientation.HORZ; - const c = this.getChildren(); - const ss = this.model.getSplitterSize(); - const initialSizes = []; + /** @internal */ + getSplitterBounds(index: number) { + const h = this.getOrientation() === Orientation.HORZ; + const c = this.getChildren(); + const ss = this.model.getSplitterSize(); + const fr = c[0].getRect(); + const lr = c[c.length - 1].getRect(); + let p = h ? [fr.x, lr.getRight()] : [fr.y, lr.getBottom()]; + const q = h ? [fr.x, lr.getRight()] : [fr.y, lr.getBottom()]; + + for (let i = 0; i < index; i++) { + const n = c[i] as TabSetNode | RowNode; + p[0] += h ? n.getMinWidth() : n.getMinHeight(); + q[0] += h ? n.getMaxWidth() : n.getMaxHeight(); + if (i > 0) { + p[0] += ss; + q[0] += ss; + } + } - let sum = 0; + for (let i = c.length - 1; i >= index; i--) { + const n = c[i] as TabSetNode | RowNode; + p[1] -= (h ? n.getMinWidth() : n.getMinHeight()) + ss; + q[1] -= (h ? n.getMaxWidth() : n.getMaxHeight()) + ss; + } + + p = [Math.max(q[1], p[0]), Math.min(q[0], p[1])]; - for (let i = 0; i < c.length; i++) { - const n = c[i] as TabSetNode | RowNode; - const r = n.getRect(); - const s = h ? r.width : r.height; - initialSizes.push(s); - sum += s; + return p; } - const startRect = c[index].getRect(); - const startPosition = (h ? startRect.x : startRect.y) - ss; - - return { initialSizes, sum, startPosition }; - } - - /** @internal */ - calculateSplit( - index: number, - splitterPos: number, - initialSizes: number[], - sum: number, - startPosition: number, - ) { - const h = this.getOrientation() === Orientation.HORZ; - const c = this.getChildren(); - const sn = c[index] as TabSetNode | RowNode; - const smax = h ? sn.getMaxWidth() : sn.getMaxHeight(); - - const sizes = [...initialSizes]; - - if (splitterPos < startPosition) { - // moved left - let shift = startPosition - splitterPos; - let altShift = 0; - if (sizes[index] + shift > smax) { - altShift = sizes[index] + shift - smax; - sizes[index] = smax; - } else { - sizes[index] += shift; - } - - for (let i = index - 1; i >= 0; i--) { - const n = c[i] as TabSetNode | RowNode; - const m = h ? n.getMinWidth() : n.getMinHeight(); - if (sizes[i] - shift > m) { - sizes[i] -= shift; - break; - } else { - shift -= sizes[i] - m; - sizes[i] = m; + /** @internal */ + getSplitterInitials(index: number) { + const h = this.getOrientation() === Orientation.HORZ; + const c = this.getChildren(); + const ss = this.model.getSplitterSize(); + const initialSizes = []; + + let sum = 0; + + for (let i = 0; i < c.length; i++) { + const n = c[i] as TabSetNode | RowNode; + const r = n.getRect(); + const s = h ? r.width : r.height; + initialSizes.push(s); + sum += s; } - } - - for (let i = index + 1; i < c.length; i++) { - const n = c[i] as TabSetNode | RowNode; - const m = h ? n.getMaxWidth() : n.getMaxHeight(); - if (sizes[i] + altShift < m) { - sizes[i] += altShift; - break; + + const startRect = c[index].getRect() + const startPosition = (h ? startRect.x : startRect.y) - ss; + + return { initialSizes, sum, startPosition }; + } + + /** @internal */ + calculateSplit(index: number, splitterPos: number, initialSizes: number[], sum: number, startPosition: number) { + const h = this.getOrientation() === Orientation.HORZ; + const c = this.getChildren(); + const sn = c[index] as TabSetNode | RowNode; + const smax = h ? sn.getMaxWidth() : sn.getMaxHeight(); + + const sizes = [...initialSizes]; + + if (splitterPos < startPosition) { // moved left + let shift = startPosition - splitterPos; + let altShift = 0; + if (sizes[index] + shift > smax) { + altShift = sizes[index] + shift - smax; + sizes[index] = smax; + } else { + sizes[index] += shift; + } + + for (let i = index - 1; i >= 0; i--) { + const n = c[i] as TabSetNode | RowNode; + const m = h ? n.getMinWidth() : n.getMinHeight(); + if (sizes[i] - shift > m) { + sizes[i] -= shift; + break; + } else { + shift -= sizes[i] - m; + sizes[i] = m; + } + } + + for (let i = index+1; i < c.length; i++) { + const n = c[i] as TabSetNode | RowNode; + const m = h ? n.getMaxWidth() : n.getMaxHeight(); + if (sizes[i] + altShift < m) { + sizes[i] += altShift; + break; + } else { + altShift -= m - sizes[i]; + sizes[i] = m; + } + } + + } else { - altShift -= m - sizes[i]; - sizes[i] = m; + let shift = splitterPos - startPosition; + let altShift = 0; + if (sizes[index-1] + shift > smax) { + altShift = sizes[index-1] + shift - smax; + sizes[index-1] = smax; + } else { + sizes[index-1] += shift; + } + + for (let i = index; i < c.length; i++) { + const n = c[i] as TabSetNode | RowNode; + const m = h ? n.getMinWidth() : n.getMinHeight(); + if (sizes[i] - shift > m) { + sizes[i] -= shift; + break; + } else { + shift -= sizes[i] - m; + sizes[i] = m; + } + } + + for (let i = index - 1; i >= 0; i--) { + const n = c[i] as TabSetNode | RowNode; + const m = h ? n.getMaxWidth() : n.getMaxHeight(); + if (sizes[i] + altShift < m) { + sizes[i] += altShift; + break; + } else { + altShift -= m - sizes[i]; + sizes[i] = m; + } + } } - } - } else { - let shift = splitterPos - startPosition; - let altShift = 0; - if (sizes[index - 1] + shift > smax) { - altShift = sizes[index - 1] + shift - smax; - sizes[index - 1] = smax; - } else { - sizes[index - 1] += shift; - } - - for (let i = index; i < c.length; i++) { - const n = c[i] as TabSetNode | RowNode; - const m = h ? n.getMinWidth() : n.getMinHeight(); - if (sizes[i] - shift > m) { - sizes[i] -= shift; - break; + + // 0.1 is to prevent weight ever going to zero + const weights = sizes.map(s => Math.max(0.1, s) * 100 / sum); + + // console.log(splitterPos, startPosition, "sizes", sizes); + // console.log("weights",weights); + return weights; + } + + /** @internal */ + getMinSize(orientation: Orientation) { + if (orientation === Orientation.HORZ) { + return this.getMinWidth(); } else { - shift -= sizes[i] - m; - sizes[i] = m; + return this.getMinHeight(); } - } - - for (let i = index - 1; i >= 0; i--) { - const n = c[i] as TabSetNode | RowNode; - const m = h ? n.getMaxWidth() : n.getMaxHeight(); - if (sizes[i] + altShift < m) { - sizes[i] += altShift; - break; + } + + /** @internal */ + getMinWidth() { + return this.minWidth; + } + + /** @internal */ + getMinHeight() { + return this.minHeight; + } + + /** @internal */ + getMaxSize(orientation: Orientation) { + if (orientation === Orientation.HORZ) { + return this.getMaxWidth(); } else { - altShift -= m - sizes[i]; - sizes[i] = m; + return this.getMaxHeight(); } - } } - // 0.1 is to prevent weight ever going to zero - const weights = sizes.map((s) => (Math.max(0.1, s) * 100) / sum); - - // console.log(splitterPos, startPosition, "sizes", sizes); - // console.log("weights",weights); - return weights; - } + /** @internal */ + getMaxWidth() { + return this.maxWidth; + } - /** @internal */ - getMinSize(orientation: Orientation) { - if (orientation === Orientation.HORZ) { - return this.getMinWidth(); - } else { - return this.getMinHeight(); + /** @internal */ + getMaxHeight() { + return this.maxHeight; } - } - - /** @internal */ - getMinWidth() { - return this.minWidth; - } - - /** @internal */ - getMinHeight() { - return this.minHeight; - } - - /** @internal */ - getMaxSize(orientation: Orientation) { - if (orientation === Orientation.HORZ) { - return this.getMaxWidth(); - } else { - return this.getMaxHeight(); + + /** @internal */ + calcMinMaxSize() { + this.minHeight = DefaultMin; + this.minWidth = DefaultMin; + this.maxHeight = DefaultMax; + this.maxWidth = DefaultMax; + let first = true; + for (const child of this.children) { + const c = child as RowNode | TabSetNode; + c.calcMinMaxSize(); + if (this.getOrientation() === Orientation.VERT) { + this.minHeight += c.getMinHeight(); + this.maxHeight += c.getMaxHeight(); + if (!first) { + this.minHeight += this.model.getSplitterSize(); + this.maxHeight += this.model.getSplitterSize(); + } + this.minWidth = Math.max(this.minWidth, c.getMinWidth()); + this.maxWidth = Math.min(this.maxWidth, c.getMaxWidth()); + } else { + this.minWidth += c.getMinWidth(); + this.maxWidth += c.getMaxWidth(); + if (!first) { + this.minWidth += this.model.getSplitterSize(); + this.maxWidth += this.model.getSplitterSize(); + } + this.minHeight = Math.max(this.minHeight, c.getMinHeight()); + this.maxHeight = Math.min(this.maxHeight, c.getMaxHeight()); + } + first = false; + } } - } - - /** @internal */ - getMaxWidth() { - return this.maxWidth; - } - - /** @internal */ - getMaxHeight() { - return this.maxHeight; - } - - /** @internal */ - calcMinMaxSize() { - this.minHeight = DefaultMin; - this.minWidth = DefaultMin; - this.maxHeight = DefaultMax; - this.maxWidth = DefaultMax; - let first = true; - for (const child of this.children) { - const c = child as RowNode | TabSetNode; - c.calcMinMaxSize(); - if (this.getOrientation() === Orientation.VERT) { - this.minHeight += c.getMinHeight(); - this.maxHeight += c.getMaxHeight(); - if (!first) { - this.minHeight += this.model.getSplitterSize(); - this.maxHeight += this.model.getSplitterSize(); + + /** @internal */ + tidy() { + let i = 0; + while (i < this.children.length) { + const child = this.children[i]; + if (child instanceof RowNode) { + child.tidy(); + + const childChildren = child.getChildren(); + if (childChildren.length === 0) { + this.removeChild(child); + } else if (childChildren.length === 1) { + // hoist child/children up to this level + const subchild = childChildren[0]; + this.removeChild(child); + if (subchild instanceof RowNode) { + let subChildrenTotal = 0; + const subChildChildren = subchild.getChildren(); + for (const ssc of subChildChildren) { + const subsubChild = ssc as RowNode | TabSetNode; + subChildrenTotal += subsubChild.getWeight(); + } + for (let j = 0; j < subChildChildren.length; j++) { + const subsubChild = subChildChildren[j] as RowNode | TabSetNode; + subsubChild.setWeight((child.getWeight() * subsubChild.getWeight()) / subChildrenTotal); + this.addChild(subsubChild, i + j); + } + } else { + subchild.setWeight(child.getWeight()); + this.addChild(subchild, i); + } + } else { + i++; + } + } else if (child instanceof TabSetNode && child.getChildren().length === 0) { + if (child.isEnableDeleteWhenEmpty()) { + this.removeChild(child); + if (child === this.model.getMaximizedTabset(this.windowId)) { + this.model.setMaximizedTabset(undefined, this.windowId); + } + } else { + i++; + } + } else { + i++; + } } - this.minWidth = Math.max(this.minWidth, c.getMinWidth()); - this.maxWidth = Math.min(this.maxWidth, c.getMaxWidth()); - } else { - this.minWidth += c.getMinWidth(); - this.maxWidth += c.getMaxWidth(); - if (!first) { - this.minWidth += this.model.getSplitterSize(); - this.maxWidth += this.model.getSplitterSize(); + + // add tabset into empty root + if (this === this.model.getRoot(this.windowId) && this.children.length === 0) { + const callback = this.model.getOnCreateTabSet(); + let attrs = callback ? callback() : {}; + attrs = { ...attrs, selected: -1 }; + const child = new TabSetNode(this.model, attrs); + this.model.setActiveTabset(child, this.windowId); + this.addChild(child); } - this.minHeight = Math.max(this.minHeight, c.getMinHeight()); - this.maxHeight = Math.min(this.maxHeight, c.getMaxHeight()); - } - first = false; + } - } - - /** @internal */ - tidy() { - let i = 0; - while (i < this.children.length) { - const child = this.children[i]; - if (child instanceof RowNode) { - child.tidy(); - - const childChildren = child.getChildren(); - if (childChildren.length === 0) { - this.removeChild(child); - } else if (childChildren.length === 1) { - // hoist child/children up to this level - const subchild = childChildren[0]; - this.removeChild(child); - if (subchild instanceof RowNode) { - let subChildrenTotal = 0; - const subChildChildren = subchild.getChildren(); - for (const ssc of subChildChildren) { - const subsubChild = ssc as RowNode | TabSetNode; - subChildrenTotal += subsubChild.getWeight(); + + /** @internal */ + canDrop(dragNode: Node & IDraggable, x: number, y: number): DropInfo | undefined { + const yy = y - this.rect.y; + const xx = x - this.rect.x; + const w = this.rect.width; + const h = this.rect.height; + const margin = 10; // height of edge rect + const half = 50; // half width of edge rect + let dropInfo; + + if (this.getWindowId() !== Model.MAIN_WINDOW_ID && !canDockToWindow(dragNode)) { + return undefined; + } + + if (this.model.isEnableEdgeDock() && this.parent === undefined) { + if (x < this.rect.x + margin && yy > h / 2 - half && yy < h / 2 + half) { + const dockLocation = DockLocation.LEFT; + const outlineRect = dockLocation.getDockRect(this.rect); + outlineRect.width = outlineRect.width / 2; + dropInfo = new DropInfo(this, outlineRect, dockLocation, -1, CLASSES.FLEXLAYOUT__OUTLINE_RECT_EDGE); + } else if (x > this.rect.getRight() - margin && yy > h / 2 - half && yy < h / 2 + half) { + const dockLocation = DockLocation.RIGHT; + const outlineRect = dockLocation.getDockRect(this.rect); + outlineRect.width = outlineRect.width / 2; + outlineRect.x += outlineRect.width; + dropInfo = new DropInfo(this, outlineRect, dockLocation, -1, CLASSES.FLEXLAYOUT__OUTLINE_RECT_EDGE); + } else if (y < this.rect.y + margin && xx > w / 2 - half && xx < w / 2 + half) { + const dockLocation = DockLocation.TOP; + const outlineRect = dockLocation.getDockRect(this.rect); + outlineRect.height = outlineRect.height / 2; + dropInfo = new DropInfo(this, outlineRect, dockLocation, -1, CLASSES.FLEXLAYOUT__OUTLINE_RECT_EDGE); + } else if (y > this.rect.getBottom() - margin && xx > w / 2 - half && xx < w / 2 + half) { + const dockLocation = DockLocation.BOTTOM; + const outlineRect = dockLocation.getDockRect(this.rect); + outlineRect.height = outlineRect.height / 2; + outlineRect.y += outlineRect.height; + dropInfo = new DropInfo(this, outlineRect, dockLocation, -1, CLASSES.FLEXLAYOUT__OUTLINE_RECT_EDGE); } - for (let j = 0; j < subChildChildren.length; j++) { - const subsubChild = subChildChildren[j] as RowNode | TabSetNode; - subsubChild.setWeight( - (child.getWeight() * subsubChild.getWeight()) / - subChildrenTotal, - ); - this.addChild(subsubChild, i + j); + + if (dropInfo !== undefined) { + if (!dragNode.canDockInto(dragNode, dropInfo)) { + return undefined; + } } - } else { - subchild.setWeight(child.getWeight()); - this.addChild(subchild, i); - } - } else { - i++; } - } else if ( - child instanceof TabSetNode && - child.getChildren().length === 0 - ) { - if (child.isEnableDeleteWhenEmpty()) { - this.removeChild(child); - if (child === this.model.getMaximizedTabset(this.windowId)) { - this.model.setMaximizedTabset(undefined, this.windowId); - } - } else { - i++; - } - } else { - i++; - } - } - // add tabset into empty root - if ( - this === this.model.getRoot(this.windowId) && - this.children.length === 0 - ) { - const callback = this.model.getOnCreateTabSet(); - let attrs = callback ? callback() : {}; - attrs = { ...attrs, selected: -1 }; - const child = new TabSetNode(this.model, attrs); - this.model.setActiveTabset(child, this.windowId); - this.addChild(child); - } - } - - /** @internal */ - canDrop( - dragNode: Node & IDraggable, - x: number, - y: number, - ): DropInfo | undefined { - const yy = y - this.rect.y; - const xx = x - this.rect.x; - const w = this.rect.width; - const h = this.rect.height; - const margin = 10; // height of edge rect - const half = 50; // half width of edge rect - let dropInfo; - - if ( - this.getWindowId() !== Model.MAIN_WINDOW_ID && - !canDockToWindow(dragNode) - ) { - return undefined; + return dropInfo; } - if (this.model.isEnableEdgeDock() && this.parent === undefined) { - if (x < this.rect.x + margin && yy > h / 2 - half && yy < h / 2 + half) { - const dockLocation = DockLocation.LEFT; - const outlineRect = dockLocation.getDockRect(this.rect); - outlineRect.width = outlineRect.width / 2; - dropInfo = new DropInfo( - this, - outlineRect, - dockLocation, - -1, - CLASSES.FLEXLAYOUT__OUTLINE_RECT_EDGE, - ); - } else if ( - x > this.rect.getRight() - margin && - yy > h / 2 - half && - yy < h / 2 + half - ) { - const dockLocation = DockLocation.RIGHT; - const outlineRect = dockLocation.getDockRect(this.rect); - outlineRect.width = outlineRect.width / 2; - outlineRect.x += outlineRect.width; - dropInfo = new DropInfo( - this, - outlineRect, - dockLocation, - -1, - CLASSES.FLEXLAYOUT__OUTLINE_RECT_EDGE, - ); - } else if ( - y < this.rect.y + margin && - xx > w / 2 - half && - xx < w / 2 + half - ) { - const dockLocation = DockLocation.TOP; - const outlineRect = dockLocation.getDockRect(this.rect); - outlineRect.height = outlineRect.height / 2; - dropInfo = new DropInfo( - this, - outlineRect, - dockLocation, - -1, - CLASSES.FLEXLAYOUT__OUTLINE_RECT_EDGE, - ); - } else if ( - y > this.rect.getBottom() - margin && - xx > w / 2 - half && - xx < w / 2 + half - ) { - const dockLocation = DockLocation.BOTTOM; - const outlineRect = dockLocation.getDockRect(this.rect); - outlineRect.height = outlineRect.height / 2; - outlineRect.y += outlineRect.height; - dropInfo = new DropInfo( - this, - outlineRect, - dockLocation, - -1, - CLASSES.FLEXLAYOUT__OUTLINE_RECT_EDGE, - ); - } + /** @internal */ + drop(dragNode: Node, location: DockLocation, index: number): void { + const dockLocation = location; + + const parent = dragNode.getParent(); - if (dropInfo !== undefined) { - if (!dragNode.canDockInto(dragNode, dropInfo)) { - return undefined; + if (parent) { + parent.removeChild(dragNode); } - } - } - return dropInfo; - } + if (parent !== undefined && parent! instanceof TabSetNode) { + parent.setSelected(0); + } - /** @internal */ - drop(dragNode: Node, location: DockLocation, index: number): void { - const dockLocation = location; + if (parent !== undefined && parent! instanceof BorderNode) { + parent.setSelected(-1); + } - const parent = dragNode.getParent(); + let node: TabSetNode | RowNode | undefined; + if (dragNode instanceof TabSetNode || dragNode instanceof RowNode) { + node = dragNode; + // need to turn round if same orientation unless docking oposite direction + if (node instanceof RowNode && node.getOrientation() === this.getOrientation() && + (location.getOrientation() === this.getOrientation() || location === DockLocation.CENTER)) { + node = new RowNode(this.model, this.windowId, {}); + node.addChild(dragNode); + } + } else { + const callback = this.model.getOnCreateTabSet(); + node = new TabSetNode(this.model, callback ? callback(dragNode as TabNode) : {}); + node.addChild(dragNode); + } + let size = this.children.reduce((sum, child) => { + return sum + (child as RowNode | TabSetNode).getWeight(); + }, 0); - if (parent) { - parent.removeChild(dragNode); - } + if (size === 0) { + size = 100; + } - if (parent !== undefined && parent! instanceof TabSetNode) { - parent.setSelected(0); - } + node.setWeight(size / 3); - if (parent !== undefined && parent! instanceof BorderNode) { - parent.setSelected(-1); - } + const horz = !this.model.isRootOrientationVertical(); + if (dockLocation === DockLocation.CENTER) { + if (index === -1) { + this.addChild(node, this.children.length); + } else { + this.addChild(node, index); + } + } else if (horz && dockLocation === DockLocation.LEFT || !horz && dockLocation === DockLocation.TOP) { + this.addChild(node, 0); + } else if (horz && dockLocation === DockLocation.RIGHT || !horz && dockLocation === DockLocation.BOTTOM) { + this.addChild(node); + } else if (horz && dockLocation === DockLocation.TOP || !horz && dockLocation === DockLocation.LEFT) { + const vrow = new RowNode(this.model, this.windowId, {}); + const hrow = new RowNode(this.model, this.windowId, {}); + hrow.setWeight(75); + node.setWeight(25); + for (const child of this.children) { + hrow.addChild(child); + } + this.removeAll(); + vrow.addChild(node); + vrow.addChild(hrow); + this.addChild(vrow); + } else if (horz && dockLocation === DockLocation.BOTTOM || !horz && dockLocation === DockLocation.RIGHT) { + const vrow = new RowNode(this.model, this.windowId, {}); + const hrow = new RowNode(this.model, this.windowId, {}); + hrow.setWeight(75); + node.setWeight(25); + for (const child of this.children) { + hrow.addChild(child); + } + this.removeAll(); + vrow.addChild(hrow); + vrow.addChild(node); + this.addChild(vrow); + } + + if (node instanceof TabSetNode) { + this.model.setActiveTabset(node, this.windowId); + } - let node: TabSetNode | RowNode | undefined; - if (dragNode instanceof TabSetNode || dragNode instanceof RowNode) { - node = dragNode; - // need to turn round if same orientation unless docking oposite direction - if ( - node instanceof RowNode && - node.getOrientation() === this.getOrientation() && - (location.getOrientation() === this.getOrientation() || - location === DockLocation.CENTER) - ) { - node = new RowNode(this.model, this.windowId, {}); - node.addChild(dragNode); - } - } else { - const callback = this.model.getOnCreateTabSet(); - node = new TabSetNode( - this.model, - callback ? callback(dragNode as TabNode) : {}, - ); - node.addChild(dragNode); + this.model.tidy(); } - let size = this.children.reduce((sum, child) => { - return sum + (child as RowNode | TabSetNode).getWeight(); - }, 0); - if (size === 0) { - size = 100; + + + /** @internal */ + isEnableDrop() { + return true; } - node.setWeight(size / 3); - - const horz = !this.model.isRootOrientationVertical(); - if (dockLocation === DockLocation.CENTER) { - if (index === -1) { - this.addChild(node, this.children.length); - } else { - this.addChild(node, index); - } - } else if ( - (horz && dockLocation === DockLocation.LEFT) || - (!horz && dockLocation === DockLocation.TOP) - ) { - this.addChild(node, 0); - } else if ( - (horz && dockLocation === DockLocation.RIGHT) || - (!horz && dockLocation === DockLocation.BOTTOM) - ) { - this.addChild(node); - } else if ( - (horz && dockLocation === DockLocation.TOP) || - (!horz && dockLocation === DockLocation.LEFT) - ) { - const vrow = new RowNode(this.model, this.windowId, {}); - const hrow = new RowNode(this.model, this.windowId, {}); - hrow.setWeight(75); - node.setWeight(25); - for (const child of this.children) { - hrow.addChild(child); - } - this.removeAll(); - vrow.addChild(node); - vrow.addChild(hrow); - this.addChild(vrow); - } else if ( - (horz && dockLocation === DockLocation.BOTTOM) || - (!horz && dockLocation === DockLocation.RIGHT) - ) { - const vrow = new RowNode(this.model, this.windowId, {}); - const hrow = new RowNode(this.model, this.windowId, {}); - hrow.setWeight(75); - node.setWeight(25); - for (const child of this.children) { - hrow.addChild(child); - } - this.removeAll(); - vrow.addChild(hrow); - vrow.addChild(node); - this.addChild(vrow); + /** @internal */ + getAttributeDefinitions() { + return RowNode.attributeDefinitions; } - if (node instanceof TabSetNode) { - this.model.setActiveTabset(node, this.windowId); + /** @internal */ + updateAttrs(json: any) { + RowNode.attributeDefinitions.update(json, this.attributes); } - this.model.tidy(); - } - - /** @internal */ - isEnableDrop() { - return true; - } - - /** @internal */ - getAttributeDefinitions() { - return RowNode.attributeDefinitions; - } - - /** @internal */ - updateAttrs(json: any) { - RowNode.attributeDefinitions.update(json, this.attributes); - } - - /** @internal */ - static getAttributeDefinitions() { - return RowNode.attributeDefinitions; - } - - // NOTE: flex-grow cannot have values < 1 otherwise will not fill parent, need to normalize - normalizeWeights() { - let sum = 0; - for (const n of this.children) { - const node = n as TabSetNode | RowNode; - sum += node.getWeight(); + /** @internal */ + static getAttributeDefinitions() { + return RowNode.attributeDefinitions; } - if (sum === 0) { - sum = 1; + + // NOTE: flex-grow cannot have values < 1 otherwise will not fill parent, need to normalize + normalizeWeights() { + let sum = 0; + for (const n of this.children) { + const node = (n as TabSetNode | RowNode); + sum += node.getWeight(); + } + + if (sum === 0) { + sum = 1; + } + + for (const n of this.children) { + const node = (n as TabSetNode | RowNode); + node.setWeight(Math.max(0.001, 100 * node.getWeight() / sum)); + } } - for (const n of this.children) { - const node = n as TabSetNode | RowNode; - node.setWeight(Math.max(0.001, (100 * node.getWeight()) / sum)); + /** @internal */ + private static createAttributeDefinitions(): AttributeDefinitions { + const attributeDefinitions = new AttributeDefinitions(); + attributeDefinitions.add("type", RowNode.TYPE, true).setType(Attribute.STRING).setFixed(); + attributeDefinitions.add("id", undefined).setType(Attribute.STRING).setDescription( + `the unique id of the row, if left undefined a uuid will be assigned` + ); + attributeDefinitions.add("weight", 100).setType(Attribute.NUMBER).setDescription( + `relative weight for sizing of this row in parent row` + ); + + return attributeDefinitions; } - } - - /** @internal */ - private static createAttributeDefinitions(): AttributeDefinitions { - const attributeDefinitions = new AttributeDefinitions(); - attributeDefinitions - .add('type', RowNode.TYPE, true) - .setType(Attribute.STRING) - .setFixed(); - attributeDefinitions - .add('id', undefined) - .setType(Attribute.STRING) - .setDescription( - `the unique id of the row, if left undefined a uuid will be assigned`, - ); - attributeDefinitions - .add('weight', 100) - .setType(Attribute.NUMBER) - .setDescription(`relative weight for sizing of this row in parent row`); - - return attributeDefinitions; - } } diff --git a/src/model/TabNode.ts b/src/model/TabNode.ts index 9a938d9c..fe9fe48a 100755 --- a/src/model/TabNode.ts +++ b/src/model/TabNode.ts @@ -1,479 +1,430 @@ -import { Attribute } from '../Attribute'; -import { AttributeDefinitions } from '../AttributeDefinitions'; -import { Rect } from '../Rect'; -import { BorderNode } from './BorderNode'; -import { IDraggable } from './IDraggable'; -import { IJsonTabNode } from './IJsonModel'; -import { Model } from './Model'; -import { Node } from './Node'; -import { TabSetNode } from './TabSetNode'; +import { Attribute } from "../Attribute"; +import { AttributeDefinitions } from "../AttributeDefinitions"; +import { Rect } from "../Rect"; +import { BorderNode } from "./BorderNode"; +import { IDraggable } from "./IDraggable"; +import { IJsonTabNode } from "./IJsonModel"; +import { Model } from "./Model"; +import { Node } from "./Node"; +import { TabSetNode } from "./TabSetNode"; export class TabNode extends Node implements IDraggable { - static readonly TYPE = 'tab'; - - /** @internal */ - static fromJson(json: any, model: Model, addToModel: boolean = true) { - const newLayoutNode = new TabNode(model, json, addToModel); - return newLayoutNode; - } - - /** @internal */ - private tabRect: Rect = Rect.empty(); - /** @internal */ - private moveableElement: HTMLElement | null; - /** @internal */ - private tabStamp: HTMLElement | null; - /** @internal */ - private renderedName?: string; - /** @internal */ - private extra: Record; - /** @internal */ - private visible: boolean; - /** @internal */ - private rendered: boolean; - /** @internal */ - private scrollTop?: number; - /** @internal */ - private scrollLeft?: number; - - /** @internal */ - constructor(model: Model, json: any, addToModel: boolean = true) { - super(model); - - this.extra = {}; // extra data added to node not saved in json - this.moveableElement = null; - this.tabStamp = null; - this.rendered = false; - this.visible = false; - - TabNode.attributeDefinitions.fromJson(json, this.attributes); - if (addToModel === true) { - model.addNode(this); - } - } - - getName() { - return this.getAttr('name') as string; - } - - getHelpText() { - return this.getAttr('helpText') as string | undefined; - } - - getComponent() { - return this.getAttr('component') as string | undefined; - } - - getWindowId() { - if (this.parent instanceof TabSetNode) { - return this.parent.getWindowId(); - } - return Model.MAIN_WINDOW_ID; - } - - getWindow(): Window | undefined { - const layoutWindow = this.model.getwindowsMap().get(this.getWindowId()); - if (layoutWindow) { - return layoutWindow.window; - } - return undefined; - } - - /** - * Returns the config attribute that can be used to store node specific data that - * WILL be saved to the json. The config attribute should be changed via the action Actions.updateNodeAttributes rather - * than directly, for example: - * this.state.model.doAction( - * FlexLayout.Actions.updateNodeAttributes(node.getId(), {config:myConfigObject})); - */ - getConfig() { - return this.attributes.config; - } - - /** - * Returns an object that can be used to store transient node specific data that will - * NOT be saved in the json. - */ - getExtraData() { - return this.extra; - } - - isPoppedOut() { - return this.getWindowId() !== Model.MAIN_WINDOW_ID; - } - - isSelected() { - return ( - (this.getParent() as TabSetNode | BorderNode).getSelectedNode() === this - ); - } - - getIcon() { - return this.getAttr('icon') as string | undefined; - } - - isEnableClose() { - return this.getAttr('enableClose') as boolean; - } - - getCloseType() { - return this.getAttr('closeType') as number; - } - - isEnablePopout() { - return this.getAttr('enablePopout') as boolean; - } - - isEnablePopoutIcon() { - return this.getAttr('enablePopoutIcon') as boolean; - } - - isEnablePopoutOverlay() { - return this.getAttr('enablePopoutOverlay') as boolean; - } - - isEnableDrag() { - return this.getAttr('enableDrag') as boolean; - } - - isEnableRename() { - return this.getAttr('enableRename') as boolean; - } - - isEnableWindowReMount() { - return this.getAttr('enableWindowReMount') as boolean; - } - - getClassName() { - return this.getAttr('className') as string | undefined; - } - - getContentClassName() { - return this.getAttr('contentClassName') as string | undefined; - } - - getTabSetClassName() { - return this.getAttr('tabsetClassName') as string | undefined; - } - - isEnableRenderOnDemand() { - return this.getAttr('enableRenderOnDemand') as boolean; - } - - isPinned() { - return this.getAttr('pinned') as boolean; - } - - getMinWidth() { - return this.getAttr('minWidth') as number; - } - - getMinHeight() { - return this.getAttr('minHeight') as number; - } - - getMaxWidth() { - return this.getAttr('maxWidth') as number; - } - - getMaxHeight() { - return this.getAttr('maxHeight') as number; - } - - isVisible() { - return this.visible; - } - - toJson(): IJsonTabNode { - const json = {}; - TabNode.attributeDefinitions.toJson(json, this.attributes); - return json; - } - - /** @internal */ - saveScrollPosition() { - if (this.moveableElement) { - this.scrollLeft = this.moveableElement.scrollLeft; - this.scrollTop = this.moveableElement.scrollTop; - // console.log("save", this.getName(), this.scrollTop); - } - } - - /** @internal */ - restoreScrollPosition() { - if (this.scrollTop) { - requestAnimationFrame(() => { + static readonly TYPE = "tab"; + + /** @internal */ + static fromJson(json: any, model: Model, addToModel: boolean = true) { + const newLayoutNode = new TabNode(model, json, addToModel); + return newLayoutNode; + } + + /** @internal */ + private tabRect: Rect = Rect.empty(); + /** @internal */ + private moveableElement: HTMLElement | null; + /** @internal */ + private tabStamp: HTMLElement | null; + /** @internal */ + private renderedName?: string; + /** @internal */ + private extra: Record; + /** @internal */ + private visible: boolean; + /** @internal */ + private rendered: boolean; + /** @internal */ + private scrollTop?: number; + /** @internal */ + private scrollLeft?: number; + + /** @internal */ + constructor(model: Model, json: any, addToModel: boolean = true) { + super(model); + + this.extra = {}; // extra data added to node not saved in json + this.moveableElement = null; + this.tabStamp = null; + this.rendered = false; + this.visible = false; + + TabNode.attributeDefinitions.fromJson(json, this.attributes); + if (addToModel === true) { + model.addNode(this); + } + } + + getName() { + return this.getAttr("name") as string; + } + + getHelpText() { + return this.getAttr("helpText") as string | undefined; + } + + getComponent() { + return this.getAttr("component") as string | undefined; + } + + getWindowId() { + if (this.parent instanceof TabSetNode) { + return this.parent.getWindowId(); + } + return Model.MAIN_WINDOW_ID; + } + + getWindow() : Window | undefined { + const layoutWindow = this.model.getwindowsMap().get(this.getWindowId()); + if (layoutWindow) { + return layoutWindow.window; + } + return undefined; + } + + /** + * Returns the config attribute that can be used to store node specific data that + * WILL be saved to the json. The config attribute should be changed via the action Actions.updateNodeAttributes rather + * than directly, for example: + * this.state.model.doAction( + * FlexLayout.Actions.updateNodeAttributes(node.getId(), {config:myConfigObject})); + */ + getConfig() { + return this.attributes.config; + } + + /** + * Returns an object that can be used to store transient node specific data that will + * NOT be saved in the json. + */ + getExtraData() { + return this.extra; + } + + isPoppedOut() { + return this.getWindowId() !== Model.MAIN_WINDOW_ID; + } + + isSelected() { + return (this.getParent() as TabSetNode | BorderNode).getSelectedNode() === this; + } + + getIcon() { + return this.getAttr("icon") as string | undefined; + } + + isEnableClose() { + return this.getAttr("enableClose") as boolean; + } + + getCloseType() { + return this.getAttr("closeType") as number; + } + + isEnablePopout() { + return this.getAttr("enablePopout") as boolean; + } + + isEnablePopoutIcon() { + return this.getAttr("enablePopoutIcon") as boolean; + } + + isEnablePopoutOverlay() { + return this.getAttr("enablePopoutOverlay") as boolean; + } + + isEnableDrag() { + return this.getAttr("enableDrag") as boolean; + } + + isEnableRename() { + return this.getAttr("enableRename") as boolean; + } + + isEnableWindowReMount() { + return this.getAttr("enableWindowReMount") as boolean; + } + + getClassName() { + return this.getAttr("className") as string | undefined; + } + + getContentClassName() { + return this.getAttr("contentClassName") as string | undefined; + } + + getTabSetClassName() { + return this.getAttr("tabsetClassName") as string | undefined; + } + + isEnableRenderOnDemand() { + return this.getAttr("enableRenderOnDemand") as boolean; + } + + isPinned() { + return this.getAttr("pinned") as boolean; + } + + getMinWidth() { + return this.getAttr("minWidth") as number; + } + + getMinHeight() { + return this.getAttr("minHeight") as number; + } + + getMaxWidth() { + return this.getAttr("maxWidth") as number; + } + + getMaxHeight() { + return this.getAttr("maxHeight") as number; + } + + isVisible() { + return this.visible; + } + + toJson(): IJsonTabNode { + const json = {}; + TabNode.attributeDefinitions.toJson(json, this.attributes); + return json; + } + + /** @internal */ + saveScrollPosition() { if (this.moveableElement) { - if (this.scrollTop) { - // console.log("restore", this.getName(), this.scrollTop); - this.moveableElement.scrollTop = this.scrollTop; - this.moveableElement.scrollLeft = this.scrollLeft!; - } + this.scrollLeft = this.moveableElement.scrollLeft; + this.scrollTop = this.moveableElement.scrollTop; + // console.log("save", this.getName(), this.scrollTop); + } + } + + /** @internal */ + restoreScrollPosition() { + if (this.scrollTop) { + requestAnimationFrame(() => { + if (this.moveableElement) { + if (this.scrollTop) { + // console.log("restore", this.getName(), this.scrollTop); + this.moveableElement.scrollTop = this.scrollTop; + this.moveableElement.scrollLeft = this.scrollLeft!; + } + } + }); + } + } + + /** @internal */ + setRect(rect: Rect) { + if (!rect.equals(this.rect)) { + this.fireEvent("resize", {rect}); + this.rect = rect; + } + } + + /** @internal */ + setVisible(visible: boolean) { + if (visible !== this.visible) { + this.visible = visible; + this.fireEvent("visibility", { visible }); } - }); - } - } - - /** @internal */ - setRect(rect: Rect) { - if (!rect.equals(this.rect)) { - this.fireEvent('resize', { rect }); - this.rect = rect; - } - } - - /** @internal */ - setVisible(visible: boolean) { - if (visible !== this.visible) { - this.visible = visible; - this.fireEvent('visibility', { visible }); - } - } - - /** @internal */ - getScrollTop() { - return this.scrollTop; - } - - /** @internal */ - setScrollTop(scrollTop: number | undefined) { - this.scrollTop = scrollTop; - } - /** @internal */ - getScrollLeft() { - return this.scrollLeft; - } - - /** @internal */ - setScrollLeft(scrollLeft: number | undefined) { - this.scrollLeft = scrollLeft; - } - /** @internal */ - isRendered() { - return this.rendered; - } - - /** @internal */ - setRendered(rendered: boolean) { - this.rendered = rendered; - } - - /** @internal */ - getTabRect() { - return this.tabRect; - } - - /** @internal */ - setTabRect(rect: Rect) { - this.tabRect = rect; - } - - /** @internal */ - getTabStamp() { - return this.tabStamp; - } - - /** @internal */ - setTabStamp(stamp: HTMLElement | null) { - this.tabStamp = stamp; - } - - /** @internal */ - getMoveableElement() { - return this.moveableElement; - } - - /** @internal */ - setMoveableElement(element: HTMLElement | null) { - this.moveableElement = element; - } - - /** @internal */ - setRenderedName(name: string) { - this.renderedName = name; - } - - /** @internal */ - getNameForOverflowMenu() { - const altName = this.getAttr('altName') as string; - if (altName !== undefined) { - return altName; - } - return this.renderedName; - } - - /** @internal */ - setName(name: string) { - this.attributes.name = name; - } - - /** @internal */ - delete() { - (this.parent as TabSetNode | BorderNode).remove(this); - this.fireEvent('close', {}); - } - - /** @internal */ - updateAttrs(json: any) { - TabNode.attributeDefinitions.update(json, this.attributes); - } - - /** @internal */ - getAttributeDefinitions() { - return TabNode.attributeDefinitions; - } - - /** @internal */ - setBorderWidth(width: number) { - this.attributes.borderWidth = width; - } - - /** @internal */ - setBorderHeight(height: number) { - this.attributes.borderHeight = height; - } - - /** @internal */ - static getAttributeDefinitions() { - return TabNode.attributeDefinitions; - } - - /** @internal */ - private static attributeDefinitions: AttributeDefinitions = - TabNode.createAttributeDefinitions(); - - /** @internal */ - private static createAttributeDefinitions(): AttributeDefinitions { - const attributeDefinitions = new AttributeDefinitions(); - attributeDefinitions - .add('type', TabNode.TYPE, true) - .setType(Attribute.STRING) - .setFixed(); - attributeDefinitions - .add('id', undefined) - .setType(Attribute.STRING) - .setDescription( - `the unique id of the tab, if left undefined a uuid will be assigned`, - ); - - attributeDefinitions - .add('name', '[Unnamed Tab]') - .setType(Attribute.STRING) - .setDescription(`name of tab to be displayed in the tab button`); - attributeDefinitions - .add('altName', undefined) - .setType(Attribute.STRING) - .setDescription( - `if there is no name specifed then this value will be used in the overflow menu`, - ); - attributeDefinitions - .add('helpText', undefined) - .setType(Attribute.STRING) - .setDescription( - `An optional help text for the tab to be displayed upon tab hover.`, - ); - attributeDefinitions - .add('component', undefined) - .setType(Attribute.STRING) - .setDescription( - `string identifying which component to run (for factory)`, - ); - attributeDefinitions - .add('config', undefined) - .setType('any') - .setDescription(`a place to hold json config for the hosted component`); - attributeDefinitions - .add('tabsetClassName', undefined) - .setType(Attribute.STRING) - .setDescription( - `class applied to parent tabset when this is the only tab and it is stretched to fill the tabset`, - ); - attributeDefinitions - .add('enableWindowReMount', false) - .setType(Attribute.BOOLEAN) - .setDescription(`if enabled the tab will re-mount when popped out/in`); - attributeDefinitions - .add('pinned', true) - .setType(Attribute.BOOLEAN) - .setDescription(`whether the tab remains open when clicking elsewhere`); - attributeDefinitions - .addInherited('enableClose', 'tabEnableClose') - .setType(Attribute.BOOLEAN) - .setDescription(`allow user to close tab via close button`); - attributeDefinitions - .addInherited('closeType', 'tabCloseType') - .setType('ICloseType') - .setDescription(`see values in ICloseType`); - attributeDefinitions - .addInherited('enableDrag', 'tabEnableDrag') - .setType(Attribute.BOOLEAN) - .setDescription(`allow user to drag tab to new location`); - attributeDefinitions - .addInherited('enableRename', 'tabEnableRename') - .setType(Attribute.BOOLEAN) - .setDescription(`allow user to rename tabs by double clicking`); - attributeDefinitions - .addInherited('className', 'tabClassName') - .setType(Attribute.STRING) - .setDescription(`class applied to tab button`); - attributeDefinitions - .addInherited('contentClassName', 'tabContentClassName') - .setType(Attribute.STRING) - .setDescription(`class applied to tab content`); - attributeDefinitions - .addInherited('icon', 'tabIcon') - .setType(Attribute.STRING) - .setDescription(`the tab icon`); - attributeDefinitions - .addInherited('enableRenderOnDemand', 'tabEnableRenderOnDemand') - .setType(Attribute.BOOLEAN) - .setDescription( - `whether to avoid rendering component until tab is visible`, - ); - attributeDefinitions - .addInherited('enablePopout', 'tabEnablePopout') - .setType(Attribute.BOOLEAN) - .setAlias('enableFloat') - .setDescription(`enable popout (in popout capable browser)`); - attributeDefinitions - .addInherited('enablePopoutIcon', 'tabEnablePopoutIcon') - .setType(Attribute.BOOLEAN) - .setDescription( - `whether to show the popout icon in the tabset header if this tab enables popouts`, - ); - attributeDefinitions - .addInherited('enablePopoutOverlay', 'tabEnablePopoutOverlay') - .setType(Attribute.BOOLEAN) - .setDescription( - `if this tab will not work correctly in a popout window when the main window is backgrounded (inactive) - then enabling this option will gray out this tab`, - ); - - attributeDefinitions - .addInherited('borderWidth', 'tabBorderWidth') - .setType(Attribute.NUMBER) - .setDescription(`width when added to border, -1 will use border size`); - attributeDefinitions - .addInherited('borderHeight', 'tabBorderHeight') - .setType(Attribute.NUMBER) - .setDescription(`height when added to border, -1 will use border size`); - attributeDefinitions - .addInherited('minWidth', 'tabMinWidth') - .setType(Attribute.NUMBER) - .setDescription(`the min width of this tab`); - attributeDefinitions - .addInherited('minHeight', 'tabMinHeight') - .setType(Attribute.NUMBER) - .setDescription(`the min height of this tab`); - attributeDefinitions - .addInherited('maxWidth', 'tabMaxWidth') - .setType(Attribute.NUMBER) - .setDescription(`the max width of this tab`); - attributeDefinitions - .addInherited('maxHeight', 'tabMaxHeight') - .setType(Attribute.NUMBER) - .setDescription(`the max height of this tab`); - - return attributeDefinitions; - } + } + + /** @internal */ + getScrollTop() { + return this.scrollTop; + } + + /** @internal */ + setScrollTop(scrollTop: number | undefined) { + this.scrollTop = scrollTop; + } + /** @internal */ + getScrollLeft() { + return this.scrollLeft; + } + + /** @internal */ + setScrollLeft(scrollLeft: number | undefined) { + this.scrollLeft = scrollLeft; + } + /** @internal */ + isRendered() { + return this.rendered; + } + + /** @internal */ + setRendered(rendered: boolean) { + this.rendered = rendered; + } + + /** @internal */ + getTabRect() { + return this.tabRect; + } + + /** @internal */ + setTabRect(rect: Rect) { + this.tabRect = rect; + } + + /** @internal */ + getTabStamp() { + return this.tabStamp; + } + + /** @internal */ + setTabStamp(stamp: HTMLElement | null) { + this.tabStamp = stamp; + } + + /** @internal */ + getMoveableElement() { + return this.moveableElement; + } + + /** @internal */ + setMoveableElement(element: HTMLElement | null) { + this.moveableElement = element; + } + + /** @internal */ + setRenderedName(name: string) { + this.renderedName = name; + } + + /** @internal */ + getNameForOverflowMenu() { + const altName = this.getAttr("altName") as string; + if (altName !== undefined) { + return altName; + } + return this.renderedName; + } + + /** @internal */ + setName(name: string) { + this.attributes.name = name; + } + + /** @internal */ + delete() { + (this.parent as TabSetNode | BorderNode).remove(this); + this.fireEvent("close", {}); + } + + /** @internal */ + updateAttrs(json: any) { + TabNode.attributeDefinitions.update(json, this.attributes); + } + + /** @internal */ + getAttributeDefinitions() { + return TabNode.attributeDefinitions; + } + + /** @internal */ + setBorderWidth(width: number) { + this.attributes.borderWidth = width; + } + + /** @internal */ + setBorderHeight(height: number) { + this.attributes.borderHeight = height; + } + + /** @internal */ + static getAttributeDefinitions() { + return TabNode.attributeDefinitions; + } + + /** @internal */ + private static attributeDefinitions: AttributeDefinitions = TabNode.createAttributeDefinitions(); + + /** @internal */ + private static createAttributeDefinitions(): AttributeDefinitions { + const attributeDefinitions = new AttributeDefinitions(); + attributeDefinitions.add("type", TabNode.TYPE, true).setType(Attribute.STRING).setFixed(); + attributeDefinitions.add("id", undefined).setType(Attribute.STRING).setDescription( + `the unique id of the tab, if left undefined a uuid will be assigned` + ); + + attributeDefinitions.add("name", "[Unnamed Tab]").setType(Attribute.STRING).setDescription( + `name of tab to be displayed in the tab button` + ); + attributeDefinitions.add("altName", undefined).setType(Attribute.STRING).setDescription( + `if there is no name specifed then this value will be used in the overflow menu` + ); + attributeDefinitions.add("helpText", undefined).setType(Attribute.STRING).setDescription( + `An optional help text for the tab to be displayed upon tab hover.` + ); + attributeDefinitions.add("component", undefined).setType(Attribute.STRING).setDescription( + `string identifying which component to run (for factory)` + ); + attributeDefinitions.add("config", undefined).setType("any").setDescription( + `a place to hold json config for the hosted component` + ); + attributeDefinitions.add("tabsetClassName", undefined).setType(Attribute.STRING).setDescription( + `class applied to parent tabset when this is the only tab and it is stretched to fill the tabset` + ); + attributeDefinitions.add("enableWindowReMount", false).setType(Attribute.BOOLEAN).setDescription( + `if enabled the tab will re-mount when popped out/in` + ); + attributeDefinitions.add("pinned", true).setType(Attribute.BOOLEAN).setDescription( + `whether the tab remains open when clicking elsewhere` + ); + attributeDefinitions.addInherited("enableClose", "tabEnableClose").setType(Attribute.BOOLEAN).setDescription( + `allow user to close tab via close button` + ); + attributeDefinitions.addInherited("closeType", "tabCloseType").setType("ICloseType").setDescription( + `see values in ICloseType` + ); + attributeDefinitions.addInherited("enableDrag", "tabEnableDrag").setType(Attribute.BOOLEAN).setDescription( + `allow user to drag tab to new location` + ); + attributeDefinitions.addInherited("enableRename", "tabEnableRename").setType(Attribute.BOOLEAN).setDescription( + `allow user to rename tabs by double clicking` + ); + attributeDefinitions.addInherited("className", "tabClassName").setType(Attribute.STRING).setDescription( + `class applied to tab button` + ); + attributeDefinitions.addInherited("contentClassName", "tabContentClassName").setType(Attribute.STRING).setDescription( + `class applied to tab content` + ); + attributeDefinitions.addInherited("icon", "tabIcon").setType(Attribute.STRING).setDescription( + `the tab icon` + ); + attributeDefinitions.addInherited("enableRenderOnDemand", "tabEnableRenderOnDemand").setType(Attribute.BOOLEAN).setDescription( + `whether to avoid rendering component until tab is visible` + ); + attributeDefinitions.addInherited("enablePopout", "tabEnablePopout").setType(Attribute.BOOLEAN).setAlias("enableFloat").setDescription( + `enable popout (in popout capable browser)` + ); + attributeDefinitions.addInherited("enablePopoutIcon", "tabEnablePopoutIcon").setType(Attribute.BOOLEAN).setDescription( + `whether to show the popout icon in the tabset header if this tab enables popouts` + ); + attributeDefinitions.addInherited("enablePopoutOverlay", "tabEnablePopoutOverlay").setType(Attribute.BOOLEAN).setDescription( + `if this tab will not work correctly in a popout window when the main window is backgrounded (inactive) + then enabling this option will gray out this tab` + ); + + attributeDefinitions.addInherited("borderWidth", "tabBorderWidth").setType(Attribute.NUMBER).setDescription( + `width when added to border, -1 will use border size` + ); + attributeDefinitions.addInherited("borderHeight", "tabBorderHeight").setType(Attribute.NUMBER).setDescription( + `height when added to border, -1 will use border size` + ); + attributeDefinitions.addInherited("minWidth", "tabMinWidth").setType(Attribute.NUMBER).setDescription( + `the min width of this tab` + ); + attributeDefinitions.addInherited("minHeight", "tabMinHeight").setType(Attribute.NUMBER).setDescription( + `the min height of this tab` + ); + attributeDefinitions.addInherited("maxWidth", "tabMaxWidth").setType(Attribute.NUMBER).setDescription( + `the max width of this tab` + ); + attributeDefinitions.addInherited("maxHeight", "tabMaxHeight").setType(Attribute.NUMBER).setDescription( + `the max height of this tab` + ); + + return attributeDefinitions; + } } diff --git a/src/model/TabSetNode.ts b/src/model/TabSetNode.ts index 62055ad5..0c11c27f 100755 --- a/src/model/TabSetNode.ts +++ b/src/model/TabSetNode.ts @@ -1,680 +1,592 @@ -import { Attribute } from '../Attribute'; -import { AttributeDefinitions } from '../AttributeDefinitions'; -import { DockLocation } from '../DockLocation'; -import { DropInfo } from '../DropInfo'; -import { Orientation } from '../Orientation'; -import { Rect } from '../Rect'; -import { CLASSES } from '../Types'; -import { canDockToWindow } from '../view/Utils'; -import { BorderNode } from './BorderNode'; -import { IDraggable } from './IDraggable'; -import { IDropTarget } from './IDropTarget'; -import { IJsonTabSetNode } from './IJsonModel'; -import { LayoutWindow } from './LayoutWindow'; -import { Model } from './Model'; -import { Node } from './Node'; -import { RowNode } from './RowNode'; -import { TabNode } from './TabNode'; -import { adjustSelectedIndex } from './Utils'; +import { Attribute } from "../Attribute"; +import { AttributeDefinitions } from "../AttributeDefinitions"; +import { DockLocation } from "../DockLocation"; +import { DropInfo } from "../DropInfo"; +import { Orientation } from "../Orientation"; +import { Rect } from "../Rect"; +import { CLASSES } from "../Types"; +import { canDockToWindow } from "../view/Utils"; +import { BorderNode } from "./BorderNode"; +import { IDraggable } from "./IDraggable"; +import { IDropTarget } from "./IDropTarget"; +import { IJsonTabSetNode } from "./IJsonModel"; +import { LayoutWindow } from "./LayoutWindow"; +import { Model } from "./Model"; +import { Node } from "./Node"; +import { RowNode } from "./RowNode"; +import { TabNode } from "./TabNode"; +import { adjustSelectedIndex } from "./Utils"; export class TabSetNode extends Node implements IDraggable, IDropTarget { - static readonly TYPE = 'tabset'; - - /** @internal */ - static fromJson(json: any, model: Model, layoutWindow: LayoutWindow) { - const newLayoutNode = new TabSetNode(model, json); - - if (json.children != null) { - for (const jsonChild of json.children) { - const child = TabNode.fromJson(jsonChild, model); - newLayoutNode.addChild(child); - } - } - if (newLayoutNode.children.length === 0) { - newLayoutNode.setSelected(-1); - } - - if (json.maximized && json.maximized === true) { - layoutWindow.maximizedTabSet = newLayoutNode; - } - - if (json.active && json.active === true) { - layoutWindow.activeTabSet = newLayoutNode; - } - - return newLayoutNode; - } - /** @internal */ - private static attributeDefinitions: AttributeDefinitions = - TabSetNode.createAttributeDefinitions(); - - /** @internal */ - private tabStripRect: Rect = Rect.empty(); - /** @internal */ - private contentRect: Rect = Rect.empty(); - /** @internal */ - private calculatedMinHeight: number; - /** @internal */ - private calculatedMinWidth: number; - /** @internal */ - private calculatedMaxHeight: number; - /** @internal */ - private calculatedMaxWidth: number; - - /** @internal */ - constructor(model: Model, json: any) { - super(model); - this.calculatedMinHeight = 0; - this.calculatedMinWidth = 0; - this.calculatedMaxHeight = 0; - this.calculatedMaxWidth = 0; - - TabSetNode.attributeDefinitions.fromJson(json, this.attributes); - model.addNode(this); - } - - getName() { - return this.getAttr('name') as string | undefined; - } - - isEnableActiveIcon() { - return this.getAttr('enableActiveIcon') as boolean; - } - - getSelected() { - const selected = this.attributes.selected; - if (selected !== undefined) { - return selected as number; - } - return -1; - } - - getSelectedNode() { - const selected = this.getSelected(); - if (selected !== -1) { - return this.children[selected]; - } - return undefined; - } - - getWeight(): number { - return this.getAttr('weight') as number; - } - - getAttrMinWidth() { - return this.getAttr('minWidth') as number; - } - - getAttrMinHeight() { - return this.getAttr('minHeight') as number; - } - - getMinWidth() { - return this.calculatedMinWidth; - } - - getMinHeight() { - return this.calculatedMinHeight; - } - - /** @internal */ - getMinSize(orientation: Orientation) { - if (orientation === Orientation.HORZ) { - return this.getMinWidth(); - } else { - return this.getMinHeight(); - } - } - getAttrMaxWidth() { - return this.getAttr('maxWidth') as number; - } - - getAttrMaxHeight() { - return this.getAttr('maxHeight') as number; - } - - getMaxWidth() { - return this.calculatedMaxWidth; - } - - getMaxHeight() { - return this.calculatedMaxHeight; - } - - /** @internal */ - getMaxSize(orientation: Orientation) { - if (orientation === Orientation.HORZ) { - return this.getMaxWidth(); - } else { - return this.getMaxHeight(); - } - } - - /** - * Returns the config attribute that can be used to store node specific data that - * WILL be saved to the json. The config attribute should be changed via the action Actions.updateNodeAttributes rather - * than directly, for example: - * this.state.model.doAction( - * FlexLayout.Actions.updateNodeAttributes(node.getId(), {config:myConfigObject})); - */ - getConfig() { - return this.attributes.config; - } - - isMaximized() { - return this.model.getMaximizedTabset(this.getWindowId()) === this; - } - - isActive() { - return this.model.getActiveTabset(this.getWindowId()) === this; - } - - isEnableDeleteWhenEmpty() { - return this.getAttr('enableDeleteWhenEmpty') as boolean; - } - - isEnableDrop() { - return this.getAttr('enableDrop') as boolean; - } - - isEnableTabWrap() { - return this.getAttr('enableTabWrap') as boolean; - } - - isEnableDrag() { - return this.getAttr('enableDrag') as boolean; - } - - isEnableDivide() { - return this.getAttr('enableDivide') as boolean; - } - - isEnableMaximize() { - return this.getAttr('enableMaximize') as boolean; - } - - isEnableClose() { - return this.getAttr('enableClose') as boolean; - } - - isEnableSingleTabStretch() { - return this.getAttr('enableSingleTabStretch') as boolean; - } - - isEnableTabStrip() { - return this.getAttr('enableTabStrip') as boolean; - } - - isAutoSelectTab() { - return this.getAttr('autoSelectTab') as boolean; - } - - isEnableTabScrollbar() { - return this.getAttr('enableTabScrollbar') as boolean; - } - - getClassNameTabStrip() { - return this.getAttr('classNameTabStrip') as string | undefined; - } - - getTabLocation() { - return this.getAttr('tabLocation') as string; - } - - toJson(): IJsonTabSetNode { - const json: any = {}; - TabSetNode.attributeDefinitions.toJson(json, this.attributes); - json.children = this.children.map((child) => child.toJson()); - - if (this.isActive()) { - json.active = true; - } - - if (this.isMaximized()) { - json.maximized = true; - } - - return json; - } - - /** @internal */ - calcMinMaxSize() { - this.calculatedMinHeight = this.getAttrMinHeight(); - this.calculatedMinWidth = this.getAttrMinWidth(); - this.calculatedMaxHeight = this.getAttrMaxHeight(); - this.calculatedMaxWidth = this.getAttrMaxWidth(); - for (const child of this.children) { - const c = child as TabNode; - this.calculatedMinWidth = Math.max( - this.calculatedMinWidth, - c.getMinWidth(), - ); - this.calculatedMinHeight = Math.max( - this.calculatedMinHeight, - c.getMinHeight(), - ); - this.calculatedMaxWidth = Math.min( - this.calculatedMaxWidth, - c.getMaxWidth(), - ); - this.calculatedMaxHeight = Math.min( - this.calculatedMaxHeight, - c.getMaxHeight(), - ); - } - - this.calculatedMinHeight += this.tabStripRect.height; - this.calculatedMaxHeight += this.tabStripRect.height; - } - - /** @internal */ - canMaximize() { - if (this.isEnableMaximize()) { - // always allow maximize toggle if already maximized - if (this.getModel().getMaximizedTabset(this.getWindowId()) === this) { - return true; - } - // only one tabset, so disable - if ( - this.getParent() === this.getModel().getRoot(this.getWindowId()) && - this.getModel().getRoot(this.getWindowId()).getChildren().length === 1 - ) { + static readonly TYPE = "tabset"; + + /** @internal */ + static fromJson(json: any, model: Model, layoutWindow: LayoutWindow) { + const newLayoutNode = new TabSetNode(model, json); + + if (json.children != null) { + for (const jsonChild of json.children) { + const child = TabNode.fromJson(jsonChild, model); + newLayoutNode.addChild(child); + } + } + if (newLayoutNode.children.length === 0) { + newLayoutNode.setSelected(-1); + } + + if (json.maximized && json.maximized === true) { + layoutWindow.maximizedTabSet = newLayoutNode; + } + + if (json.active && json.active === true) { + layoutWindow.activeTabSet = newLayoutNode; + } + + return newLayoutNode; + } + /** @internal */ + private static attributeDefinitions: AttributeDefinitions = TabSetNode.createAttributeDefinitions(); + + /** @internal */ + private tabStripRect: Rect = Rect.empty(); + /** @internal */ + private contentRect: Rect = Rect.empty(); + /** @internal */ + private calculatedMinHeight: number; + /** @internal */ + private calculatedMinWidth: number; + /** @internal */ + private calculatedMaxHeight: number; + /** @internal */ + private calculatedMaxWidth: number; + + /** @internal */ + constructor(model: Model, json: any) { + super(model); + this.calculatedMinHeight = 0; + this.calculatedMinWidth = 0; + this.calculatedMaxHeight = 0; + this.calculatedMaxWidth = 0; + + TabSetNode.attributeDefinitions.fromJson(json, this.attributes); + model.addNode(this); + } + + getName() { + return this.getAttr("name") as string | undefined; + } + + isEnableActiveIcon() { + return this.getAttr("enableActiveIcon") as boolean; + } + + getSelected() { + const selected = this.attributes.selected; + if (selected !== undefined) { + return selected as number; + } + return -1; + } + + getSelectedNode() { + const selected = this.getSelected(); + if (selected !== -1) { + return this.children[selected]; + } + return undefined; + } + + getWeight(): number { + return this.getAttr("weight") as number; + } + + getAttrMinWidth() { + return (this.getAttr("minWidth") as number); + } + + getAttrMinHeight() { + return this.getAttr("minHeight") as number; + } + + getMinWidth() { + return this.calculatedMinWidth; + } + + getMinHeight() { + return this.calculatedMinHeight; + } + + /** @internal */ + getMinSize(orientation: Orientation) { + if (orientation === Orientation.HORZ) { + return this.getMinWidth(); + } else { + return this.getMinHeight(); + } + } + getAttrMaxWidth() { + return (this.getAttr("maxWidth") as number); + } + + getAttrMaxHeight() { + return this.getAttr("maxHeight") as number; + } + + getMaxWidth() { + return this.calculatedMaxWidth; + } + + getMaxHeight() { + return this.calculatedMaxHeight; + } + + /** @internal */ + getMaxSize(orientation: Orientation) { + if (orientation === Orientation.HORZ) { + return this.getMaxWidth(); + } else { + return this.getMaxHeight(); + } + } + + + /** + * Returns the config attribute that can be used to store node specific data that + * WILL be saved to the json. The config attribute should be changed via the action Actions.updateNodeAttributes rather + * than directly, for example: + * this.state.model.doAction( + * FlexLayout.Actions.updateNodeAttributes(node.getId(), {config:myConfigObject})); + */ + getConfig() { + return this.attributes.config; + } + + isMaximized() { + return this.model.getMaximizedTabset(this.getWindowId()) === this; + } + + isActive() { + return this.model.getActiveTabset(this.getWindowId()) === this; + } + + isEnableDeleteWhenEmpty() { + return this.getAttr("enableDeleteWhenEmpty") as boolean; + } + + isEnableDrop() { + return this.getAttr("enableDrop") as boolean; + } + + isEnableTabWrap() { + return this.getAttr("enableTabWrap") as boolean; + } + + isEnableDrag() { + return this.getAttr("enableDrag") as boolean; + } + + isEnableDivide() { + return this.getAttr("enableDivide") as boolean; + } + + isEnableMaximize() { + return this.getAttr("enableMaximize") as boolean; + } + + isEnableClose() { + return this.getAttr("enableClose") as boolean; + } + + isEnableSingleTabStretch() { + return this.getAttr("enableSingleTabStretch") as boolean; + } + + isEnableTabStrip() { + return this.getAttr("enableTabStrip") as boolean; + } + + isAutoSelectTab() { + return this.getAttr("autoSelectTab") as boolean; + } + + isEnableTabScrollbar() { + return this.getAttr("enableTabScrollbar") as boolean; + } + + getClassNameTabStrip() { + return this.getAttr("classNameTabStrip") as string | undefined; + } + + getTabLocation() { + return this.getAttr("tabLocation") as string; + } + + + + toJson(): IJsonTabSetNode { + const json: any = {}; + TabSetNode.attributeDefinitions.toJson(json, this.attributes); + json.children = this.children.map((child) => child.toJson()); + + if (this.isActive()) { + json.active = true; + } + + if (this.isMaximized()) { + json.maximized = true; + } + + return json; + } + + /** @internal */ + calcMinMaxSize() { + this.calculatedMinHeight = this.getAttrMinHeight(); + this.calculatedMinWidth = this.getAttrMinWidth(); + this.calculatedMaxHeight = this.getAttrMaxHeight(); + this.calculatedMaxWidth = this.getAttrMaxWidth(); + for (const child of this.children) { + const c = child as TabNode; + this.calculatedMinWidth = Math.max(this.calculatedMinWidth, c.getMinWidth()); + this.calculatedMinHeight = Math.max(this.calculatedMinHeight, c.getMinHeight()); + this.calculatedMaxWidth = Math.min(this.calculatedMaxWidth, c.getMaxWidth()); + this.calculatedMaxHeight = Math.min(this.calculatedMaxHeight, c.getMaxHeight()); + } + + this.calculatedMinHeight += this.tabStripRect.height; + this.calculatedMaxHeight += this.tabStripRect.height; + } + + /** @internal */ + canMaximize() { + if (this.isEnableMaximize()) { + // always allow maximize toggle if already maximized + if (this.getModel().getMaximizedTabset(this.getWindowId()) === this) { + return true; + } + // only one tabset, so disable + if (this.getParent() === this.getModel().getRoot(this.getWindowId()) && this.getModel().getRoot(this.getWindowId()).getChildren().length === 1) { + return false; + } + return true; + } return false; - } - return true; - } - return false; - } - - /** @internal */ - setContentRect(rect: Rect) { - this.contentRect = rect; - } - - /** @internal */ - getContentRect() { - return this.contentRect; - } - - /** @internal */ - setTabStripRect(rect: Rect) { - this.tabStripRect = rect; - } - /** @internal */ - setWeight(weight: number) { - this.attributes.weight = weight; - } - - /** @internal */ - setSelected(index: number) { - this.attributes.selected = index; - } - - getWindowId() { - return (this.parent as RowNode).getWindowId(); - } - - /** @internal */ - canDrop( - dragNode: Node & IDraggable, - x: number, - y: number, - ): DropInfo | undefined { - let dropInfo; - - if (dragNode === this) { - const dockLocation = DockLocation.CENTER; - const outlineRect = this.tabStripRect; - dropInfo = new DropInfo( - this, - outlineRect!, - dockLocation, - -1, - CLASSES.FLEXLAYOUT__OUTLINE_RECT, - ); - } else if ( - this.getWindowId() !== Model.MAIN_WINDOW_ID && - !canDockToWindow(dragNode) - ) { - return undefined; - } else if (this.contentRect!.contains(x, y)) { - let dockLocation = DockLocation.CENTER; - if ( - this.model.getMaximizedTabset( - (this.parent as RowNode).getWindowId(), - ) === undefined - ) { - dockLocation = DockLocation.getLocation(this.contentRect!, x, y); - } - const outlineRect = dockLocation.getDockRect(this.rect); - dropInfo = new DropInfo( - this, - outlineRect, - dockLocation, - -1, - CLASSES.FLEXLAYOUT__OUTLINE_RECT, - ); - } else if (this.tabStripRect != null && this.tabStripRect.contains(x, y)) { - let r: Rect; - let yy: number; - let h: number; - if (this.children.length === 0) { - r = this.tabStripRect.clone(); - yy = r.y + 3; - h = r.height - 4; - r.width = 2; - } else { - let child = this.children[0] as TabNode; - r = child.getTabRect()!; - yy = r.y; - h = r.height; - let p = this.tabStripRect.x; - let childCenter = 0; - for (let i = 0; i < this.children.length; i++) { - child = this.children[i] as TabNode; - r = child.getTabRect()!; - if (r.y !== yy) { - yy = r.y; - p = this.tabStripRect.x; - } - childCenter = r.x + r.width / 2; - if (p <= x && x < childCenter && r.y < y && y < r.getBottom()) { + } + + /** @internal */ + setContentRect(rect: Rect) { + this.contentRect = rect; + } + + /** @internal */ + getContentRect() { + return this.contentRect; + } + + /** @internal */ + setTabStripRect(rect: Rect) { + this.tabStripRect = rect; + } + /** @internal */ + setWeight(weight: number) { + this.attributes.weight = weight; + } + + /** @internal */ + setSelected(index: number) { + this.attributes.selected = index; + } + + getWindowId() { + return (this.parent as RowNode).getWindowId(); + } + + /** @internal */ + canDrop(dragNode: Node & IDraggable, x: number, y: number): DropInfo | undefined { + let dropInfo; + + if (dragNode === this) { const dockLocation = DockLocation.CENTER; - const outlineRect = new Rect(r.x - 2, r.y, 3, r.height); - if (this.rect.x < r.x && r.x < this.rect.getRight()) { - dropInfo = new DropInfo( - this, - outlineRect, - dockLocation, - i, - CLASSES.FLEXLAYOUT__OUTLINE_RECT, - ); - break; + const outlineRect = this.tabStripRect; + dropInfo = new DropInfo(this, outlineRect!, dockLocation, -1, CLASSES.FLEXLAYOUT__OUTLINE_RECT); + } else if (this.getWindowId() !== Model.MAIN_WINDOW_ID && !canDockToWindow(dragNode)) { + return undefined; + } else if (this.contentRect!.contains(x, y)) { + let dockLocation = DockLocation.CENTER; + if (this.model.getMaximizedTabset((this.parent as RowNode).getWindowId()) === undefined) { + dockLocation = DockLocation.getLocation(this.contentRect!, x, y); + } + const outlineRect = dockLocation.getDockRect(this.rect); + dropInfo = new DropInfo(this, outlineRect, dockLocation, -1, CLASSES.FLEXLAYOUT__OUTLINE_RECT); + } else if (this.tabStripRect != null && this.tabStripRect.contains(x, y)) { + let r: Rect; + let yy: number; + let h: number; + if (this.children.length === 0) { + r = this.tabStripRect.clone(); + yy = r.y + 3; + h = r.height - 4; + r.width = 2; } else { - return undefined; + let child = this.children[0] as TabNode; + r = child.getTabRect()!; + yy = r.y; + h = r.height; + let p = this.tabStripRect.x; + let childCenter = 0; + for (let i = 0; i < this.children.length; i++) { + child = this.children[i] as TabNode; + r = child.getTabRect()!; + if (r.y !== yy) { + yy = r.y + p = this.tabStripRect.x; + } + childCenter = r.x + r.width / 2; + if (p <= x && x < childCenter && r.y < y && y < r.getBottom()) { + const dockLocation = DockLocation.CENTER; + const outlineRect = new Rect(r.x - 2, r.y, 3, r.height); + if (this.rect.x < r.x && r.x < this.rect.getRight()) { + dropInfo = new DropInfo(this, outlineRect, dockLocation, i, CLASSES.FLEXLAYOUT__OUTLINE_RECT); + break; + } else { + return undefined; + } + } + p = childCenter; + } + } + if (dropInfo == null && r.getRight() < this.rect!.getRight()) { + const dockLocation = DockLocation.CENTER; + const outlineRect = new Rect(r.getRight() - 2, yy, 3, h); + dropInfo = new DropInfo(this, outlineRect, dockLocation, this.children.length, CLASSES.FLEXLAYOUT__OUTLINE_RECT); } - } - p = childCenter; } - } - if (dropInfo == null && r.getRight() < this.rect!.getRight()) { - const dockLocation = DockLocation.CENTER; - const outlineRect = new Rect(r.getRight() - 2, yy, 3, h); - dropInfo = new DropInfo( - this, - outlineRect, - dockLocation, - this.children.length, - CLASSES.FLEXLAYOUT__OUTLINE_RECT, - ); - } - } - - if (!dragNode.canDockInto(dragNode, dropInfo)) { - return undefined; - } - - return dropInfo; - } - - /** @internal */ - delete() { - (this.parent as RowNode).removeChild(this); - } - - /** @internal */ - remove(node: TabNode) { - const removedIndex = this.removeChild(node); - this.model.tidy(); - - adjustSelectedIndex(this, removedIndex); - } - - /** @internal */ - drop( - dragNode: Node, - location: DockLocation, - index: number, - select?: boolean, - ) { - const dockLocation = location; - - if (this === dragNode) { - // tabset drop into itself - return; // dock back to itself - } - - let dragParent = dragNode.getParent() as BorderNode | TabSetNode | RowNode; - let fromIndex = 0; - if (dragParent !== undefined) { - fromIndex = dragParent.removeChild(dragNode); - // if selected node in border is being docked into tabset then deselect border tabs - if ( - dragParent instanceof BorderNode && - dragParent.getSelected() === fromIndex - ) { - dragParent.setSelected(-1); - } else { - adjustSelectedIndex(dragParent, fromIndex); - } - } - - // if dropping a tab back to same tabset and moving to forward position then reduce insertion index - if ( - dragNode instanceof TabNode && - dragParent === this && - fromIndex < index && - index > 0 - ) { - index--; - } - - // simple_bundled dock to existing tabset - if (dockLocation === DockLocation.CENTER) { - let insertPos = index; - if (insertPos === -1) { - insertPos = this.children.length; - } - - if (dragNode instanceof TabNode) { - this.addChild(dragNode, insertPos); - if (select || (select !== false && this.isAutoSelectTab())) { - this.setSelected(insertPos); + + if (!dragNode.canDockInto(dragNode, dropInfo)) { + return undefined; } - // console.log("added child at : " + insertPos); - } else if (dragNode instanceof RowNode) { - (dragNode as RowNode).forEachNode((child, level) => { - if (child instanceof TabNode) { - this.addChild(child, insertPos); - // console.log("added child at : " + insertPos); - insertPos++; - } - }, 0); - } else { - for (let i = 0; i < dragNode.getChildren().length; i++) { - const child = dragNode.getChildren()[i]; - this.addChild(child, insertPos); - // console.log("added child at : " + insertPos); - insertPos++; + + return dropInfo; + } + + /** @internal */ + delete() { + (this.parent as RowNode).removeChild(this); + } + + /** @internal */ + remove(node: TabNode) { + const removedIndex = this.removeChild(node); + this.model.tidy(); + + adjustSelectedIndex(this, removedIndex); + } + + /** @internal */ + drop(dragNode: Node, location: DockLocation, index: number, select?: boolean) { + const dockLocation = location; + + if (this === dragNode) { + // tabset drop into itself + return; // dock back to itself } - if (this.getSelected() === -1 && this.children.length > 0) { - this.setSelected(0); + + let dragParent = dragNode.getParent() as BorderNode | TabSetNode | RowNode; + let fromIndex = 0; + if (dragParent !== undefined) { + fromIndex = dragParent.removeChild(dragNode); + // if selected node in border is being docked into tabset then deselect border tabs + if (dragParent instanceof BorderNode && dragParent.getSelected() === fromIndex) { + dragParent.setSelected(-1); + } else { + adjustSelectedIndex(dragParent, fromIndex); + } } - } - this.model.setActiveTabset(this, (this.parent as RowNode).getWindowId()); - } else { - let moveNode = dragNode as TabSetNode | RowNode | TabNode; - if (dragNode instanceof TabNode) { - // create new tabset parent - // console.log("create a new tabset"); - const callback = this.model.getOnCreateTabSet(); - moveNode = new TabSetNode( - this.model, - callback ? callback(dragNode as TabNode) : {}, - ); - moveNode.addChild(dragNode); - // console.log("added child at end"); - dragParent = moveNode; - } else if (dragNode instanceof RowNode) { - const parent = this.getParent()! as RowNode; - // need to turn round if same orientation unless docking oposite direction - if ( - dragNode.getOrientation() === parent.getOrientation() && - (location.getOrientation() === parent.getOrientation() || - location === DockLocation.CENTER) - ) { - const node = new RowNode(this.model, this.getWindowId(), {}); - node.addChild(dragNode); - moveNode = node; + + // if dropping a tab back to same tabset and moving to forward position then reduce insertion index + if (dragNode instanceof TabNode && dragParent === this && fromIndex < index && index > 0) { + index--; } - } else { - moveNode = dragNode as TabSetNode; - } - - const parentRow = this.parent as Node; - const pos = parentRow.getChildren().indexOf(this); - - if (parentRow.getOrientation() === dockLocation.orientation) { - moveNode.setWeight(this.getWeight() / 2); - this.setWeight(this.getWeight() / 2); - // console.log("added child 50% size at: " + pos + dockLocation.indexPlus); - parentRow.addChild(moveNode, pos + dockLocation.indexPlus); - } else { - // create a new row to host the new tabset (it will go in the opposite direction) - // console.log("create a new row"); - const newRow = new RowNode(this.model, this.getWindowId(), {}); - newRow.setWeight(this.getWeight()); - newRow.addChild(this); - this.setWeight(50); - moveNode.setWeight(50); - // console.log("added child 50% size at: " + dockLocation.indexPlus); - newRow.addChild(moveNode, dockLocation.indexPlus); - - parentRow.removeChild(this); - parentRow.addChild(newRow, pos); - } - if (moveNode instanceof TabSetNode) { - this.model.setActiveTabset(moveNode, this.getWindowId()); - } - } - this.model.tidy(); - } - - /** @internal */ - updateAttrs(json: any) { - TabSetNode.attributeDefinitions.update(json, this.attributes); - } - - /** @internal */ - getAttributeDefinitions() { - return TabSetNode.attributeDefinitions; - } - - /** @internal */ - static getAttributeDefinitions() { - return TabSetNode.attributeDefinitions; - } - - /** @internal */ - private static createAttributeDefinitions(): AttributeDefinitions { - const attributeDefinitions = new AttributeDefinitions(); - attributeDefinitions - .add('type', TabSetNode.TYPE, true) - .setType(Attribute.STRING) - .setFixed(); - attributeDefinitions - .add('id', undefined) - .setType(Attribute.STRING) - .setDescription( - `the unique id of the tab set, if left undefined a uuid will be assigned`, - ); - attributeDefinitions - .add('weight', 100) - .setType(Attribute.NUMBER) - .setDescription( - `relative weight for sizing of this tabset in parent row`, - ); - attributeDefinitions - .add('selected', 0) - .setType(Attribute.NUMBER) - .setDescription(`index of selected/visible tab in tabset`); - attributeDefinitions.add('name', undefined).setType(Attribute.STRING); - attributeDefinitions - .add('config', undefined) - .setType('any') - .setDescription(`a place to hold json config used in your own code`); - - attributeDefinitions - .addInherited('enableDeleteWhenEmpty', 'tabSetEnableDeleteWhenEmpty') - .setDescription(`whether to delete this tabset when is has no tabs`); - attributeDefinitions - .addInherited('enableDrop', 'tabSetEnableDrop') - .setDescription(`allow user to drag tabs into this tabset`); - attributeDefinitions - .addInherited('enableDrag', 'tabSetEnableDrag') - .setDescription(`allow user to drag tabs out this tabset`); - attributeDefinitions - .addInherited('enableDivide', 'tabSetEnableDivide') - .setDescription( - `allow user to drag tabs to region of this tabset, splitting into new tabset`, - ); - attributeDefinitions - .addInherited('enableMaximize', 'tabSetEnableMaximize') - .setDescription( - `allow user to maximize tabset to fill view via maximize button`, - ); - attributeDefinitions - .addInherited('enableClose', 'tabSetEnableClose') - .setDescription(`allow user to close tabset via a close button`); - attributeDefinitions - .addInherited('enableSingleTabStretch', 'tabSetEnableSingleTabStretch') - .setDescription( - `if the tabset has only a single tab then stretch the single tab to fill area and display in a header style`, - ); - - attributeDefinitions - .addInherited('classNameTabStrip', 'tabSetClassNameTabStrip') - .setDescription(`a class name to apply to the tab strip`); - attributeDefinitions - .addInherited('enableTabStrip', 'tabSetEnableTabStrip') - .setDescription( - `enable tab strip and allow multiple tabs in this tabset`, - ); - attributeDefinitions - .addInherited('minWidth', 'tabSetMinWidth') - .setDescription(`minimum width (in px) for this tabset`); - attributeDefinitions - .addInherited('minHeight', 'tabSetMinHeight') - .setDescription(`minimum height (in px) for this tabset`); - attributeDefinitions - .addInherited('maxWidth', 'tabSetMaxWidth') - .setDescription(`maximum width (in px) for this tabset`); - attributeDefinitions - .addInherited('maxHeight', 'tabSetMaxHeight') - .setDescription(`maximum height (in px) for this tabset`); - - attributeDefinitions - .addInherited('enableTabWrap', 'tabSetEnableTabWrap') - .setDescription(`wrap tabs onto multiple lines`); - attributeDefinitions - .addInherited('tabLocation', 'tabSetTabLocation') - .setDescription(`the location of the tabs either top or bottom`); - attributeDefinitions - .addInherited('autoSelectTab', 'tabSetAutoSelectTab') - .setType(Attribute.BOOLEAN) - .setDescription(`whether to select new/moved tabs in tabset`); - attributeDefinitions - .addInherited('enableActiveIcon', 'tabSetEnableActiveIcon') - .setType(Attribute.BOOLEAN) - .setDescription( - `whether the active icon (*) should be displayed when the tabset is active`, - ); - - attributeDefinitions - .addInherited('enableTabScrollbar', 'tabSetEnableTabScrollbar') - .setType(Attribute.BOOLEAN) - .setDescription(`whether to show a mini scrollbar for the tabs`); - - return attributeDefinitions; - } + + // simple_bundled dock to existing tabset + if (dockLocation === DockLocation.CENTER) { + let insertPos = index; + if (insertPos === -1) { + insertPos = this.children.length; + } + + if (dragNode instanceof TabNode) { + this.addChild(dragNode, insertPos); + if (select || (select !== false && this.isAutoSelectTab())) { + this.setSelected(insertPos); + } + // console.log("added child at : " + insertPos); + } else if (dragNode instanceof RowNode) { + (dragNode as RowNode).forEachNode((child, level) => { + if (child instanceof TabNode) { + this.addChild(child, insertPos); + // console.log("added child at : " + insertPos); + insertPos++; + } + }, 0); + } else { + for (let i = 0; i < dragNode.getChildren().length; i++) { + const child = dragNode.getChildren()[i]; + this.addChild(child, insertPos); + // console.log("added child at : " + insertPos); + insertPos++; + } + if (this.getSelected() === -1 && this.children.length > 0) { + this.setSelected(0); + } + } + this.model.setActiveTabset(this, (this.parent as RowNode).getWindowId()); + } else { + let moveNode = dragNode as TabSetNode | RowNode | TabNode; + if (dragNode instanceof TabNode) { + // create new tabset parent + // console.log("create a new tabset"); + const callback = this.model.getOnCreateTabSet(); + moveNode = new TabSetNode(this.model, callback ? callback(dragNode as TabNode) : {}); + moveNode.addChild(dragNode); + // console.log("added child at end"); + dragParent = moveNode; + } else if (dragNode instanceof RowNode) { + const parent = (this.getParent()! as RowNode); + // need to turn round if same orientation unless docking oposite direction + if (dragNode.getOrientation() === parent.getOrientation() && + (location.getOrientation() === parent.getOrientation() || location === DockLocation.CENTER)) { + const node = new RowNode(this.model, this.getWindowId(), {}); + node.addChild(dragNode); + moveNode = node; + } + } else { + moveNode = dragNode as TabSetNode; + } + + const parentRow = this.parent as Node; + const pos = parentRow.getChildren().indexOf(this); + + if (parentRow.getOrientation() === dockLocation.orientation) { + moveNode.setWeight(this.getWeight() / 2); + this.setWeight(this.getWeight() / 2); + // console.log("added child 50% size at: " + pos + dockLocation.indexPlus); + parentRow.addChild(moveNode, pos + dockLocation.indexPlus); + } else { + // create a new row to host the new tabset (it will go in the opposite direction) + // console.log("create a new row"); + const newRow = new RowNode(this.model, this.getWindowId(), {}); + newRow.setWeight(this.getWeight()); + newRow.addChild(this); + this.setWeight(50); + moveNode.setWeight(50); + // console.log("added child 50% size at: " + dockLocation.indexPlus); + newRow.addChild(moveNode, dockLocation.indexPlus); + + parentRow.removeChild(this); + parentRow.addChild(newRow, pos); + } + if (moveNode instanceof TabSetNode) { + this.model.setActiveTabset(moveNode, this.getWindowId()); + } + } + this.model.tidy(); + } + + /** @internal */ + updateAttrs(json: any) { + TabSetNode.attributeDefinitions.update(json, this.attributes); + } + + /** @internal */ + getAttributeDefinitions() { + return TabSetNode.attributeDefinitions; + } + + /** @internal */ + static getAttributeDefinitions() { + return TabSetNode.attributeDefinitions; + } + + /** @internal */ + private static createAttributeDefinitions(): AttributeDefinitions { + const attributeDefinitions = new AttributeDefinitions(); + attributeDefinitions.add("type", TabSetNode.TYPE, true).setType(Attribute.STRING).setFixed(); + attributeDefinitions.add("id", undefined).setType(Attribute.STRING).setDescription( + `the unique id of the tab set, if left undefined a uuid will be assigned` + ); + attributeDefinitions.add("weight", 100).setType(Attribute.NUMBER).setDescription( + `relative weight for sizing of this tabset in parent row` + ); + attributeDefinitions.add("selected", 0).setType(Attribute.NUMBER).setDescription( + `index of selected/visible tab in tabset` + ); + attributeDefinitions.add("name", undefined).setType(Attribute.STRING); + attributeDefinitions.add("config", undefined).setType("any").setDescription( + `a place to hold json config used in your own code` + ); + + attributeDefinitions.addInherited("enableDeleteWhenEmpty", "tabSetEnableDeleteWhenEmpty").setDescription( + `whether to delete this tabset when is has no tabs` + ); + attributeDefinitions.addInherited("enableDrop", "tabSetEnableDrop").setDescription( + `allow user to drag tabs into this tabset` + ); + attributeDefinitions.addInherited("enableDrag", "tabSetEnableDrag").setDescription( + `allow user to drag tabs out this tabset` + ); + attributeDefinitions.addInherited("enableDivide", "tabSetEnableDivide").setDescription( + `allow user to drag tabs to region of this tabset, splitting into new tabset` + ); + attributeDefinitions.addInherited("enableMaximize", "tabSetEnableMaximize").setDescription( + `allow user to maximize tabset to fill view via maximize button` + ); + attributeDefinitions.addInherited("enableClose", "tabSetEnableClose").setDescription( + `allow user to close tabset via a close button` + ); + attributeDefinitions.addInherited("enableSingleTabStretch", "tabSetEnableSingleTabStretch").setDescription( + `if the tabset has only a single tab then stretch the single tab to fill area and display in a header style` + ); + + attributeDefinitions.addInherited("classNameTabStrip", "tabSetClassNameTabStrip").setDescription( + `a class name to apply to the tab strip` + ); + attributeDefinitions.addInherited("enableTabStrip", "tabSetEnableTabStrip").setDescription( + `enable tab strip and allow multiple tabs in this tabset` + ); + attributeDefinitions.addInherited("minWidth", "tabSetMinWidth").setDescription( + `minimum width (in px) for this tabset` + ); + attributeDefinitions.addInherited("minHeight", "tabSetMinHeight").setDescription( + `minimum height (in px) for this tabset` + ); + attributeDefinitions.addInherited("maxWidth", "tabSetMaxWidth").setDescription( + `maximum width (in px) for this tabset` + ); + attributeDefinitions.addInherited("maxHeight", "tabSetMaxHeight").setDescription( + `maximum height (in px) for this tabset` + ); + + attributeDefinitions.addInherited("enableTabWrap", "tabSetEnableTabWrap").setDescription( + `wrap tabs onto multiple lines` + ); + attributeDefinitions.addInherited("tabLocation", "tabSetTabLocation").setDescription( + `the location of the tabs either top or bottom` + ); + attributeDefinitions.addInherited("autoSelectTab", "tabSetAutoSelectTab").setType(Attribute.BOOLEAN).setDescription( + `whether to select new/moved tabs in tabset` + ); + attributeDefinitions.addInherited("enableActiveIcon", "tabSetEnableActiveIcon").setType(Attribute.BOOLEAN).setDescription( + `whether the active icon (*) should be displayed when the tabset is active` + ); + + attributeDefinitions.addInherited("enableTabScrollbar", "tabSetEnableTabScrollbar").setType(Attribute.BOOLEAN).setDescription( + `whether to show a mini scrollbar for the tabs` + ); + + return attributeDefinitions; + } + } + + diff --git a/src/model/Utils.ts b/src/model/Utils.ts index 216b77e2..8a1af16e 100644 --- a/src/model/Utils.ts +++ b/src/model/Utils.ts @@ -1,62 +1,52 @@ -import { TabSetNode } from './TabSetNode'; -import { BorderNode } from './BorderNode'; -import { RowNode } from './RowNode'; -import { TabNode } from './TabNode'; +import { TabSetNode } from "./TabSetNode"; +import { BorderNode } from "./BorderNode"; +import { RowNode } from "./RowNode"; +import { TabNode } from "./TabNode"; /** @internal */ export function adjustSelectedIndexAfterDock(node: TabNode) { - const parent = node.getParent(); - if ( - parent !== null && - (parent instanceof TabSetNode || parent instanceof BorderNode) - ) { - const children = parent.getChildren(); - for (let i = 0; i < children.length; i++) { - const child = children[i] as TabNode; - if (child === node) { - parent.setSelected(i); - return; - } + const parent = node.getParent(); + if (parent !== null && (parent instanceof TabSetNode || parent instanceof BorderNode)) { + const children = parent.getChildren(); + for (let i = 0; i < children.length; i++) { + const child = children[i] as TabNode; + if (child === node) { + parent.setSelected(i); + return; + } + } } - } } /** @internal */ -export function adjustSelectedIndex( - parent: TabSetNode | BorderNode | RowNode, - removedIndex: number, -) { - // for the tabset/border being removed from set the selected index - if ( - parent !== undefined && - (parent instanceof TabSetNode || parent instanceof BorderNode) - ) { - const selectedIndex = (parent as TabSetNode | BorderNode).getSelected(); - if (selectedIndex !== -1) { - if (removedIndex === selectedIndex && parent.getChildren().length > 0) { - if (removedIndex >= parent.getChildren().length) { - // removed last tab; select new last tab - parent.setSelected(parent.getChildren().length - 1); - } else { - // leave selected index as is, selecting next tab after this one +export function adjustSelectedIndex(parent: TabSetNode | BorderNode | RowNode, removedIndex: number) { + // for the tabset/border being removed from set the selected index + if (parent !== undefined && (parent instanceof TabSetNode || parent instanceof BorderNode)) { + const selectedIndex = (parent as TabSetNode | BorderNode).getSelected(); + if (selectedIndex !== -1) { + if (removedIndex === selectedIndex && parent.getChildren().length > 0) { + if (removedIndex >= parent.getChildren().length) { + // removed last tab; select new last tab + parent.setSelected(parent.getChildren().length - 1); + } else { + // leave selected index as is, selecting next tab after this one + } + } else if (removedIndex < selectedIndex) { + parent.setSelected(selectedIndex - 1); + } else if (removedIndex > selectedIndex) { + // leave selected index as is + } else { + parent.setSelected(-1); + } } - } else if (removedIndex < selectedIndex) { - parent.setSelected(selectedIndex - 1); - } else if (removedIndex > selectedIndex) { - // leave selected index as is - } else { - parent.setSelected(-1); - } } - } } export function randomUUID(): string { - // @ts-ignore - return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) => - ( - c ^ - (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4))) - ).toString(16), - ); -} + // @ts-ignore + return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c => + (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16) + ); + } + + diff --git a/src/view/BorderButton.tsx b/src/view/BorderButton.tsx index 81ce2aa9..f7b649f8 100755 --- a/src/view/BorderButton.tsx +++ b/src/view/BorderButton.tsx @@ -1,221 +1,201 @@ -import * as React from 'react'; -import { I18nLabel } from '../I18nLabel'; -import { Actions } from '../model/Actions'; -import { TabNode } from '../model/TabNode'; -import { IIcons, LayoutInternal } from './Layout'; -import { ICloseType } from '../model/ICloseType'; -import { CLASSES } from '../Types'; -import { getRenderStateEx, isAuxMouseEvent } from './Utils'; +import * as React from "react"; +import { I18nLabel } from "../I18nLabel"; +import { Actions } from "../model/Actions"; +import { TabNode } from "../model/TabNode"; +import { IIcons, LayoutInternal } from "./Layout"; +import { ICloseType } from "../model/ICloseType"; +import { CLASSES } from "../Types"; +import { getRenderStateEx, isAuxMouseEvent } from "./Utils"; /** @internal */ export interface IBorderButtonProps { - layout: LayoutInternal; - node: TabNode; - selected: boolean; - border: string; - icons: IIcons; - path: string; + layout: LayoutInternal; + node: TabNode; + selected: boolean; + border: string; + icons: IIcons; + path: string; } /** @internal */ export const BorderButton = (props: IBorderButtonProps) => { - const { layout, node, selected, border, icons, path } = props; - const selfRef = React.useRef(null); - const contentRef = React.useRef(null); - - const onDragStart = (event: React.DragEvent) => { - if (node.isEnableDrag()) { - event.stopPropagation(); - layout.setDragNode(event.nativeEvent, node as TabNode); + const { layout, node, selected, border, icons, path } = props; + const selfRef = React.useRef(null); + const contentRef = React.useRef(null); + + const onDragStart = (event: React.DragEvent) => { + if (node.isEnableDrag()) { + event.stopPropagation(); + layout.setDragNode(event.nativeEvent, node as TabNode); + } else { + event.preventDefault(); + } + }; + + const onDragEnd = (event: React.DragEvent) => { + event.stopPropagation(); + layout.clearDragMain(); + }; + + const onAuxMouseClick = (event: React.MouseEvent) => { + if (isAuxMouseEvent(event)) { + layout.auxMouseClick(node, event); + } + }; + + const onContextMenu = (event: React.MouseEvent) => { + layout.showContextMenu(node, event); + }; + + const onClick = () => { + layout.doAction(Actions.selectTab(node.getId())); + }; + + // const onDoubleClick = (event: Event) => { + // // if (node.isEnableRename()) { + // // onRename(); + // // } + // }; + + // const onRename = () => { + // layout.setEditingTab(node); + // layout.getCurrentDocument()!.body.addEventListener("pointerdown", onEndEdit); + // }; + + const onEndEdit = (event: Event) => { + if (event.target !== contentRef.current!) { + layout.getCurrentDocument()!.body.removeEventListener("pointerdown", onEndEdit); + layout.setEditingTab(undefined); + } + }; + + const isClosable = () => { + const closeType = node.getCloseType(); + if (selected || closeType === ICloseType.Always) { + return true; + } + if (closeType === ICloseType.Visible) { + // not selected but x should be visible due to hover + if (window.matchMedia && window.matchMedia("(hover: hover) and (pointer: fine)").matches) { + return true; + } + } + return false; + }; + + const onClose = (event: React.MouseEvent) => { + if (isClosable()) { + layout.doAction(Actions.deleteTab(node.getId())); + event.stopPropagation(); + } + }; + + const onClosePointerDown = (event: React.PointerEvent) => { + event.stopPropagation(); + }; + + React.useLayoutEffect(() => { + node.setTabRect(layout.getBoundingClientRect(selfRef.current!)); + if (layout.getEditingTab() === node) { + (contentRef.current! as HTMLInputElement).select(); + } + }); + + const onTextBoxPointerDown = (event: React.PointerEvent) => { + event.stopPropagation(); + }; + + const onTextBoxKeyPress = (event: React.KeyboardEvent) => { + if (event.code === 'Escape') { + // esc + layout.setEditingTab(undefined); + } else if (event.code === 'Enter' || event.code === 'NumpadEnter') { + // enter + layout.setEditingTab(undefined); + layout.doAction(Actions.renameTab(node.getId(), (event.target as HTMLInputElement).value)); + } + }; + + const cm = layout.getClassName; + let classNames = cm(CLASSES.FLEXLAYOUT__BORDER_BUTTON) + " " + cm(CLASSES.FLEXLAYOUT__BORDER_BUTTON_ + border); + + if (selected) { + classNames += " " + cm(CLASSES.FLEXLAYOUT__BORDER_BUTTON__SELECTED); } else { - event.preventDefault(); + classNames += " " + cm(CLASSES.FLEXLAYOUT__BORDER_BUTTON__UNSELECTED); } - }; - - const onDragEnd = (event: React.DragEvent) => { - event.stopPropagation(); - layout.clearDragMain(); - }; - - const onAuxMouseClick = ( - event: React.MouseEvent, - ) => { - if (isAuxMouseEvent(event)) { - layout.auxMouseClick(node, event); - } - }; - - const onContextMenu = (event: React.MouseEvent) => { - layout.showContextMenu(node, event); - }; - - const onClick = () => { - layout.doAction(Actions.selectTab(node.getId())); - }; - - // const onDoubleClick = (event: Event) => { - // // if (node.isEnableRename()) { - // // onRename(); - // // } - // }; - - // const onRename = () => { - // layout.setEditingTab(node); - // layout.getCurrentDocument()!.body.addEventListener("pointerdown", onEndEdit); - // }; - - const onEndEdit = (event: Event) => { - if (event.target !== contentRef.current!) { - layout - .getCurrentDocument()! - .body.removeEventListener('pointerdown', onEndEdit); - layout.setEditingTab(undefined); - } - }; - const isClosable = () => { - const closeType = node.getCloseType(); - if (selected || closeType === ICloseType.Always) { - return true; - } - if (closeType === ICloseType.Visible) { - // not selected but x should be visible due to hover - if ( - window.matchMedia && - window.matchMedia('(hover: hover) and (pointer: fine)').matches - ) { - return true; - } + if (node.getClassName() !== undefined) { + classNames += " " + node.getClassName(); } - return false; - }; - const onClose = (event: React.MouseEvent) => { - if (isClosable()) { - layout.doAction(Actions.deleteTab(node.getId())); - event.stopPropagation(); + let iconAngle = 0; + if (node.getModel().isEnableRotateBorderIcons() === false) { + if (border === "left") { + iconAngle = 90; + } else if (border === "right") { + iconAngle = -90; + } } - }; - const onClosePointerDown = (event: React.PointerEvent) => { - event.stopPropagation(); - }; + const renderState = getRenderStateEx(layout, node, iconAngle); + + let content = renderState.content ? ( +
+ {renderState.content} +
) : null; + + const leading = renderState.leading ? ( +
+ {renderState.leading} +
) : null; - React.useLayoutEffect(() => { - node.setTabRect(layout.getBoundingClientRect(selfRef.current!)); if (layout.getEditingTab() === node) { - (contentRef.current! as HTMLInputElement).select(); + content = ( + + ); } - }); - - const onTextBoxPointerDown = ( - event: React.PointerEvent, - ) => { - event.stopPropagation(); - }; - - const onTextBoxKeyPress = (event: React.KeyboardEvent) => { - if (event.code === 'Escape') { - // esc - layout.setEditingTab(undefined); - } else if (event.code === 'Enter' || event.code === 'NumpadEnter') { - // enter - layout.setEditingTab(undefined); - layout.doAction( - Actions.renameTab( - node.getId(), - (event.target as HTMLInputElement).value, - ), - ); - } - }; - - const cm = layout.getClassName; - let classNames = - cm(CLASSES.FLEXLAYOUT__BORDER_BUTTON) + - ' ' + - cm(CLASSES.FLEXLAYOUT__BORDER_BUTTON_ + border); - - if (selected) { - classNames += ' ' + cm(CLASSES.FLEXLAYOUT__BORDER_BUTTON__SELECTED); - } else { - classNames += ' ' + cm(CLASSES.FLEXLAYOUT__BORDER_BUTTON__UNSELECTED); - } - - if (node.getClassName() !== undefined) { - classNames += ' ' + node.getClassName(); - } - - let iconAngle = 0; - if (node.getModel().isEnableRotateBorderIcons() === false) { - if (border === 'left') { - iconAngle = 90; - } else if (border === 'right') { - iconAngle = -90; + + if (node.isEnableClose()) { + const closeTitle = layout.i18nName(I18nLabel.Close_Tab); + renderState.buttons.push( +
+ {(typeof icons.close === "function") ? icons.close(node) : icons.close} +
+ ); } - } - - const renderState = getRenderStateEx(layout, node, iconAngle); - - let content = renderState.content ? ( -
- {renderState.content} -
- ) : null; - - const leading = renderState.leading ? ( -
- {renderState.leading} -
- ) : null; - - if (layout.getEditingTab() === node) { - content = ( - - ); - } - - if (node.isEnableClose()) { - const closeTitle = layout.i18nName(I18nLabel.Close_Tab); - renderState.buttons.push( -
- {typeof icons.close === 'function' ? icons.close(node) : icons.close} -
, + + return ( +
+ {leading} + {content} + {renderState.buttons} +
); - } - - return ( -
- {leading} - {content} - {renderState.buttons} -
- ); }; diff --git a/src/view/BorderTab.tsx b/src/view/BorderTab.tsx index 4f60c09b..8bc31c33 100644 --- a/src/view/BorderTab.tsx +++ b/src/view/BorderTab.tsx @@ -1,118 +1,107 @@ -import * as React from 'react'; -import { Orientation } from '../Orientation'; -import { LayoutInternal } from './Layout'; -import { BorderNode } from '../model/BorderNode'; -import { Splitter, splitterDragging } from './Splitter'; -import { DockLocation } from '../DockLocation'; -import { CLASSES } from '../Types'; +import * as React from "react"; +import { Orientation } from "../Orientation"; +import { LayoutInternal } from "./Layout"; +import { BorderNode } from "../model/BorderNode"; +import { Splitter, splitterDragging } from "./Splitter"; +import { DockLocation } from "../DockLocation"; +import { CLASSES } from "../Types"; /** @internal */ export interface IBorderTabProps { - layout: LayoutInternal; - border: BorderNode; - show: boolean; + layout: LayoutInternal; + border: BorderNode; + show: boolean; } export function BorderTab(props: IBorderTabProps) { - const { layout, border, show } = props; - const selfRef = React.useRef(null); - const timer = React.useRef(undefined); - const selectedNode = border.getSelectedNode(); - const pinned = selectedNode?.isPinned(); + const { layout, border, show } = props; + const selfRef = React.useRef(null); + const timer = React.useRef(undefined); + const selectedNode = border.getSelectedNode(); + const pinned = selectedNode?.isPinned(); - React.useLayoutEffect(() => { - const contentRect = layout.getBoundingClientRect(selfRef.current!); - if (!isNaN(contentRect.x) && contentRect.width > 0) { - if (!border.getContentRect().equals(contentRect)) { - border.setContentRect(contentRect); - if (splitterDragging) { - // next movement will draw tabs again, only redraw after pause/end - if (timer.current) { - clearTimeout(timer.current); - } - timer.current = setTimeout(() => { - layout.redrawInternal('border content rect ' + contentRect); - timer.current = undefined; - }, 50); - } else { - layout.redrawInternal('border content rect ' + contentRect); + React.useLayoutEffect(() => { + const contentRect = layout.getBoundingClientRect(selfRef.current!); + if (!isNaN(contentRect.x) && contentRect.width > 0) { + if (!border.getContentRect().equals(contentRect)) { + border.setContentRect(contentRect); + if (splitterDragging) { // next movement will draw tabs again, only redraw after pause/end + if (timer.current) { + clearTimeout(timer.current); + } + timer.current = setTimeout(() => { + layout.redrawInternal("border content rect " + contentRect); + timer.current = undefined; + }, 50); + } else { + layout.redrawInternal("border content rect " + contentRect); + } + } } - } - } - }); - - let horizontal = true; - const style: Record = {}; - if (border.getOrientation() === Orientation.HORZ) { - style.width = border.getSize(); - style.minWidth = border.getMinSize(); - style.maxWidth = border.getMaxSize(); - } else { - style.height = border.getSize(); - style.minHeight = border.getMinSize(); - style.maxHeight = border.getMaxSize(); - horizontal = false; - } + }); - style.display = show ? 'flex' : 'none'; + let horizontal = true; + const style: Record = {}; - if (show && pinned === false) { - style.position = 'absolute'; - style.zIndex = 999; - style.pointerEvents = 'none'; - style.backgroundColor = 'transparent'; - const headerRect = border.getTabHeaderRect(); - if (border.getLocation() === DockLocation.LEFT) { - style.left = headerRect.width; - style.top = 0; - style.bottom = 0; - } else if (border.getLocation() === DockLocation.RIGHT) { - style.right = headerRect.width; - style.top = 0; - style.bottom = 0; - } else if (border.getLocation() === DockLocation.TOP) { - style.top = headerRect.height; - style.left = 0; - style.right = 0; + if (border.getOrientation() === Orientation.HORZ) { + style.width = border.getSize(); + style.minWidth = border.getMinSize(); + style.maxWidth = border.getMaxSize(); } else { - // DockLocation.BOTTOM - style.bottom = headerRect.height; - style.left = 0; - style.right = 0; + style.height = border.getSize(); + style.minHeight = border.getMinSize(); + style.maxHeight = border.getMaxSize(); + horizontal = false; } - } - const className = layout.getClassName( - CLASSES.FLEXLAYOUT__BORDER_TAB_CONTENTS, - ); + style.display = show ? "flex" : "none"; - const splitter = - show && pinned !== false ? ( - - ) : null; + if (show && pinned === false) { + style.position = "absolute"; + style.zIndex = 999; + style.pointerEvents = "none"; + style.backgroundColor = "transparent"; + const headerRect = border.getTabHeaderRect(); + if (border.getLocation() === DockLocation.LEFT) { + style.left = headerRect.width; + style.top = 0; + style.bottom = 0; + } else if (border.getLocation() === DockLocation.RIGHT) { + style.right = headerRect.width; + style.top = 0; + style.bottom = 0; + } else if (border.getLocation() === DockLocation.TOP) { + style.top = headerRect.height; + style.left = 0; + style.right = 0; + } else { // DockLocation.BOTTOM + style.bottom = headerRect.height; + style.left = 0; + style.right = 0; + } + } - if ( - border.getLocation() === DockLocation.LEFT || - border.getLocation() === DockLocation.TOP - ) { - return ( - <> -
- {splitter} - - ); - } else { - return ( - <> - {splitter} -
- - ); - } -} + const className = layout.getClassName(CLASSES.FLEXLAYOUT__BORDER_TAB_CONTENTS); + + const splitter = show && pinned !== false ? : null; + + if (border.getLocation() === DockLocation.LEFT || border.getLocation() === DockLocation.TOP) { + return ( + <> +
+
+ {splitter} + + ); + } else { + return ( + <> + {splitter} +
+
+ + ); + + } +} \ No newline at end of file diff --git a/src/view/BorderTabSet.tsx b/src/view/BorderTabSet.tsx index c3531125..98ba3567 100755 --- a/src/view/BorderTabSet.tsx +++ b/src/view/BorderTabSet.tsx @@ -1,402 +1,309 @@ -import * as React from 'react'; -import { DockLocation } from '../DockLocation'; -import { BorderNode } from '../model/BorderNode'; -import { TabNode } from '../model/TabNode'; -import { BorderButton } from './BorderButton'; -import { LayoutInternal, ITabSetRenderValues } from './Layout'; -import { showPopup } from './PopupMenu'; -import { Actions } from '../model/Actions'; -import { I18nLabel } from '../I18nLabel'; -import { useTabOverflow } from './TabOverflowHook'; -import { Orientation } from '../Orientation'; -import { CLASSES } from '../Types'; -import { isAuxMouseEvent } from './Utils'; +import * as React from "react"; +import { DockLocation } from "../DockLocation"; +import { BorderNode } from "../model/BorderNode"; +import { TabNode } from "../model/TabNode"; +import { BorderButton } from "./BorderButton"; +import { LayoutInternal, ITabSetRenderValues } from "./Layout"; +import { showPopup } from "./PopupMenu"; +import { Actions } from "../model/Actions"; +import { I18nLabel } from "../I18nLabel"; +import { useTabOverflow } from "./TabOverflowHook"; +import { Orientation } from "../Orientation"; +import { CLASSES } from "../Types"; +import { isAuxMouseEvent } from "./Utils"; /** @internal */ export interface IBorderTabSetProps { - border: BorderNode; - layout: LayoutInternal; - size: number; + border: BorderNode; + layout: LayoutInternal; + size: number; } /** @internal */ export const BorderTabSet = (props: IBorderTabSetProps) => { - const { border, layout, size } = props; - - const toolbarRef = React.useRef(null); - const miniScrollRef = React.useRef(null); - const overflowbuttonRef = React.useRef(null); - const stickyButtonsRef = React.useRef(null); - const tabStripInnerRef = React.useRef(null); - - const icons = layout.getIcons(); - - React.useLayoutEffect(() => { - border.setTabHeaderRect(layout.getBoundingClientRect(selfRef.current!)); - }); - - const { - selfRef, - userControlledPositionRef, - onScroll, - onScrollPointerDown, - hiddenTabs, - onMouseWheel, - isDockStickyButtons, - isShowHiddenTabs, - } = useTabOverflow( - layout, - border, - Orientation.flip(border.getOrientation()), - tabStripInnerRef, - miniScrollRef, - layout.getClassName(CLASSES.FLEXLAYOUT__BORDER_BUTTON), - ); - - const onAuxMouseClick = ( - event: React.MouseEvent, - ) => { - if (isAuxMouseEvent(event)) { - layout.auxMouseClick(border, event); - } - }; - - const onContextMenu = (event: React.MouseEvent) => { - layout.showContextMenu(border, event); - }; - - const onInterceptPointerDown = (event: React.PointerEvent) => { - event.stopPropagation(); - }; - - const onOverflowClick = ( - event: React.MouseEvent, - ) => { - const callback = layout.getShowOverflowMenu(); - const items = hiddenTabs.map((h) => { - return { index: h, node: border.getChildren()[h] as TabNode }; + const { border, layout, size } = props; + + const toolbarRef = React.useRef(null); + const miniScrollRef = React.useRef(null); + const overflowbuttonRef = React.useRef(null); + const stickyButtonsRef = React.useRef(null); + const tabStripInnerRef = React.useRef(null); + + const icons = layout.getIcons(); + + React.useLayoutEffect(() => { + border.setTabHeaderRect(layout.getBoundingClientRect(selfRef.current!)); }); - if (callback !== undefined) { - callback(border, event, items, onOverflowItemSelect); - } else { - const element = overflowbuttonRef.current!; - showPopup(element, border, items, onOverflowItemSelect, layout); + + const { selfRef, userControlledPositionRef, onScroll, onScrollPointerDown, hiddenTabs, onMouseWheel, isDockStickyButtons, isShowHiddenTabs } = + useTabOverflow(layout, border, Orientation.flip(border.getOrientation()), tabStripInnerRef, miniScrollRef, + layout.getClassName(CLASSES.FLEXLAYOUT__BORDER_BUTTON) + ); + + const onAuxMouseClick = (event: React.MouseEvent) => { + if (isAuxMouseEvent(event)) { + layout.auxMouseClick(border, event); + } + }; + + const onContextMenu = (event: React.MouseEvent) => { + layout.showContextMenu(border, event); + }; + + const onInterceptPointerDown = (event: React.PointerEvent) => { + event.stopPropagation(); + }; + + const onOverflowClick = (event: React.MouseEvent) => { + const callback = layout.getShowOverflowMenu(); + const items = hiddenTabs.map(h => { return { index: h, node: (border.getChildren()[h] as TabNode) }; }); + if (callback !== undefined) { + + callback(border, event, items, onOverflowItemSelect); + } else { + const element = overflowbuttonRef.current!; + showPopup( + element, + border, + items, + onOverflowItemSelect, + layout); + } + event.stopPropagation(); + }; + + const onOverflowItemSelect = (item: { node: TabNode; index: number }) => { + layout.doAction(Actions.selectTab(item.node.getId())); + userControlledPositionRef.current = false; + }; + + const onPopoutTab = (event: React.MouseEvent) => { + const selectedTabNode = border.getChildren()[border.getSelected()] as TabNode; + if (selectedTabNode !== undefined) { + layout.doAction(Actions.popoutTab(selectedTabNode.getId())); + } + event.stopPropagation(); + }; + + const cm = layout.getClassName; + + const tabButtons: any = []; + + const layoutTab = (i: number) => { + const isSelected = border.getSelected() === i; + const child = border.getChildren()[i] as TabNode; + + tabButtons.push( + + ); + if (i < border.getChildren().length - 1) { + tabButtons.push( +
+ ); + } + }; + + for (let i = 0; i < border.getChildren().length; i++) { + layoutTab(i); } - event.stopPropagation(); - }; - - const onOverflowItemSelect = (item: { node: TabNode; index: number }) => { - layout.doAction(Actions.selectTab(item.node.getId())); - userControlledPositionRef.current = false; - }; - - const onPopoutTab = (event: React.MouseEvent) => { - const selectedTabNode = border.getChildren()[ - border.getSelected() - ] as TabNode; - if (selectedTabNode !== undefined) { - layout.doAction(Actions.popoutTab(selectedTabNode.getId())); + + let borderClasses = cm(CLASSES.FLEXLAYOUT__BORDER) + " " + cm(CLASSES.FLEXLAYOUT__BORDER_ + border.getLocation().getName()); + if (border.getClassName() !== undefined) { + borderClasses += " " + border.getClassName(); } - event.stopPropagation(); - }; - - const cm = layout.getClassName; - - const tabButtons: any = []; - - const layoutTab = (i: number) => { - const isSelected = border.getSelected() === i; - const child = border.getChildren()[i] as TabNode; - - tabButtons.push( - , - ); - if (i < border.getChildren().length - 1) { - tabButtons.push( -
, - ); + + // allow customization of tabset + let leading : React.ReactNode = undefined; + let buttons: any[] = []; + let stickyButtons: any[] = []; + const renderState: ITabSetRenderValues = { leading, buttons, stickyButtons: stickyButtons, overflowPosition: undefined }; + layout.customizeTabSet(border, renderState); + leading = renderState.leading; + stickyButtons = renderState.stickyButtons; + buttons = renderState.buttons; + + if (renderState.overflowPosition === undefined) { + renderState.overflowPosition = stickyButtons.length; } - }; - - for (let i = 0; i < border.getChildren().length; i++) { - layoutTab(i); - } - - let borderClasses = - cm(CLASSES.FLEXLAYOUT__BORDER) + - ' ' + - cm(CLASSES.FLEXLAYOUT__BORDER_ + border.getLocation().getName()); - if (border.getClassName() !== undefined) { - borderClasses += ' ' + border.getClassName(); - } - - // allow customization of tabset - let leading: React.ReactNode = undefined; - let buttons: any[] = []; - let stickyButtons: any[] = []; - const renderState: ITabSetRenderValues = { - leading, - buttons, - stickyButtons: stickyButtons, - overflowPosition: undefined, - }; - layout.customizeTabSet(border, renderState); - leading = renderState.leading; - stickyButtons = renderState.stickyButtons; - buttons = renderState.buttons; - - if (renderState.overflowPosition === undefined) { - renderState.overflowPosition = stickyButtons.length; - } - - if (stickyButtons.length > 0) { - if (isDockStickyButtons) { - buttons = [...stickyButtons, ...buttons]; - } else { - tabButtons.push( -
{ - e.preventDefault(); - }} - className={cm( - CLASSES.FLEXLAYOUT__TAB_TOOLBAR_STICKY_BUTTONS_CONTAINER, - )} - > - {stickyButtons} -
, - ); + + if (stickyButtons.length > 0) { + if (isDockStickyButtons) { + buttons = [...stickyButtons, ...buttons]; + } else { + tabButtons.push(
{ e.preventDefault() }} + className={cm(CLASSES.FLEXLAYOUT__TAB_TOOLBAR_STICKY_BUTTONS_CONTAINER)} + > + {stickyButtons} +
); + } } - } - if (isShowHiddenTabs) { - const overflowTitle = layout.i18nName(I18nLabel.Overflow_Menu_Tooltip); - let overflowContent; - if (typeof icons.more === 'function') { - const items = hiddenTabs.map((h) => { - return { index: h, node: border.getChildren()[h] as TabNode }; - }); + if (isShowHiddenTabs) { + const overflowTitle = layout.i18nName(I18nLabel.Overflow_Menu_Tooltip); + let overflowContent; + if (typeof icons.more === "function") { + const items = hiddenTabs.map(h => { return { index: h, node: (border.getChildren()[h] as TabNode) }; }); - overflowContent = icons.more(border, items); - } else { - overflowContent = ( - <> - {icons.more} -
- {hiddenTabs.length > 0 ? hiddenTabs.length : ''} -
- - ); + overflowContent = icons.more(border, items); + } else { + overflowContent = (<> + {icons.more} +
{hiddenTabs.length>0?hiddenTabs.length: ""}
+ ); + } + buttons.splice(Math.min(renderState.overflowPosition, buttons.length), 0, + + ); } - buttons.splice( - Math.min(renderState.overflowPosition, buttons.length), - 0, - , - ); - } - - const selectedIndex = border.getSelected(); - const selectedTabNode = - selectedIndex !== -1 - ? (border.getChildren()[selectedIndex] as TabNode) - : undefined; - const isPinned = selectedTabNode?.isPinned(); - - React.useEffect(() => { - if (selectedTabNode && !isPinned) { - const onBodyPointerDown = (e: PointerEvent) => { - const layoutRect = layout.getDomRect(); - const x = e.clientX - layoutRect.x; - const y = e.clientY - layoutRect.y; - if ( - !border.getTabHeaderRect().contains(x, y) && - !border.getContentRect().contains(x, y) - ) { - layout.doAction(Actions.selectTab(selectedTabNode.getId())); + }, [selectedTabNode, isPinned, border, layout]); + + if (selectedTabNode !== undefined) { + const pinTitle = selectedTabNode.isPinned() ? layout.i18nName(I18nLabel.Unpin_Tab) : layout.i18nName(I18nLabel.Pin_Tab); + const pinIcon = selectedTabNode.isPinned() + ? ((typeof icons.unpin === "function") ? icons.unpin(selectedTabNode) : icons.unpin) + : ((typeof icons.pin === "function") ? icons.pin(selectedTabNode) : icons.pin); + const onPinClick = (event: React.MouseEvent) => { + layout.doAction(Actions.updateNodeAttributes(selectedTabNode.getId(), { pinned: !selectedTabNode.isPinned() })); + event.stopPropagation(); + }; + buttons.push( + + ); + + if (layout.isSupportsPopout() && selectedTabNode.isEnablePopout()) { + const popoutTitle = layout.i18nName(I18nLabel.Popout_Tab); + buttons.push( + + ); } - }; - document.addEventListener('pointerdown', onBodyPointerDown); - return () => - document.removeEventListener('pointerdown', onBodyPointerDown); } - }, [selectedTabNode, isPinned, border, layout]); - - if (selectedTabNode !== undefined) { - const pinTitle = selectedTabNode.isPinned() - ? layout.i18nName(I18nLabel.Unpin_Tab) - : layout.i18nName(I18nLabel.Pin_Tab); - const pinIcon = selectedTabNode.isPinned() - ? typeof icons.unpin === 'function' - ? icons.unpin(selectedTabNode) - : icons.unpin - : typeof icons.pin === 'function' - ? icons.pin(selectedTabNode) - : icons.pin; - const onPinClick = (event: React.MouseEvent) => { - layout.doAction( - Actions.updateNodeAttributes(selectedTabNode.getId(), { - pinned: !selectedTabNode.isPinned(), - }), - ); - event.stopPropagation(); - }; - buttons.push( - , + const toolbar = ( +
+ {buttons} +
); - if (layout.isSupportsPopout() && selectedTabNode.isEnablePopout()) { - const popoutTitle = layout.i18nName(I18nLabel.Popout_Tab); - buttons.push( - , - ); + let innerStyle = {}; + let outerStyle = {}; + const borderHeight = size - 1; + if (border.getLocation() === DockLocation.LEFT) { + innerStyle = { right: "100%", top: 0 }; + outerStyle = { width: borderHeight, overflowY: "auto" }; + } else if (border.getLocation() === DockLocation.RIGHT) { + innerStyle = { left: "100%", top: 0 }; + outerStyle = { width: borderHeight, overflowY: "auto" }; + } else { + innerStyle = { left: 0 }; + outerStyle = { height: borderHeight, overflowX: "auto" }; } - } - const toolbar = ( -
- {buttons} -
- ); - - let innerStyle = {}; - let outerStyle = {}; - const borderHeight = size - 1; - if (border.getLocation() === DockLocation.LEFT) { - innerStyle = { right: '100%', top: 0 }; - outerStyle = { width: borderHeight, overflowY: 'auto' }; - } else if (border.getLocation() === DockLocation.RIGHT) { - innerStyle = { left: '100%', top: 0 }; - outerStyle = { width: borderHeight, overflowY: 'auto' }; - } else { - innerStyle = { left: 0 }; - outerStyle = { height: borderHeight, overflowX: 'auto' }; - } - - let miniScrollbar = undefined; - if (border.isEnableTabScrollbar()) { - miniScrollbar = ( -
- ); - } - let leadingContainer: React.ReactNode = undefined; - if (leading) { - leadingContainer = ( -
{leading}
- ); - } - - return ( -
- {leadingContainer} -
+ let miniScrollbar = undefined; + if (border.isEnableTabScrollbar()) { + miniScrollbar = ( +
+ ); + } + + let leadingContainer: React.ReactNode = undefined; + if (leading) { + leadingContainer = ( +
+ {leading} +
+ ); + } + + return (
-
- {tabButtons} -
-
- {miniScrollbar} -
- {toolbar} -
- ); + {leadingContainer} +
+
+
+ {tabButtons} +
+
+ {miniScrollbar} +
+ {toolbar} +
+ ); + }; diff --git a/src/view/DragContainer.tsx b/src/view/DragContainer.tsx index 7f0bc52a..8f25bf1c 100644 --- a/src/view/DragContainer.tsx +++ b/src/view/DragContainer.tsx @@ -1,31 +1,32 @@ -import * as React from 'react'; -import { TabNode } from '../model/TabNode'; -import { LayoutInternal } from './Layout'; -import { CLASSES } from '../Types'; -import { TabButtonStamp } from './TabButtonStamp'; +import * as React from "react"; +import { TabNode } from "../model/TabNode"; +import { LayoutInternal } from "./Layout"; +import { CLASSES } from "../Types"; +import { TabButtonStamp } from "./TabButtonStamp"; /** @internal */ export interface IDragContainerProps { - node: TabNode; - layout: LayoutInternal; + node: TabNode; + layout: LayoutInternal; } /** @internal */ export const DragContainer = (props: IDragContainerProps) => { - const { layout, node } = props; - const selfRef = React.useRef(null); + const { layout, node} = props; + const selfRef = React.useRef(null); - React.useEffect(() => { - node.setTabStamp(selfRef.current); - }, [node, selfRef.current]); + React.useEffect(()=> { + node.setTabStamp(selfRef.current); + }, [node, selfRef.current]); - const cm = layout.getClassName; + const cm = layout.getClassName; - const classNames = cm(CLASSES.FLEXLAYOUT__DRAG_RECT); + const classNames = cm(CLASSES.FLEXLAYOUT__DRAG_RECT); - return ( -
- -
- ); + return (
+ +
+ ); }; diff --git a/src/view/ErrorBoundary.tsx b/src/view/ErrorBoundary.tsx index e69bebe8..ef3bc164 100644 --- a/src/view/ErrorBoundary.tsx +++ b/src/view/ErrorBoundary.tsx @@ -1,63 +1,53 @@ -import * as React from 'react'; -import { ErrorInfo } from 'react'; -import { CLASSES } from '../Types'; +import * as React from "react"; +import { ErrorInfo } from "react"; +import { CLASSES } from "../Types"; /** @internal */ export interface IErrorBoundaryProps { - message: string; - retryText: string; - children: React.ReactNode; + message: string; + retryText: string; + children: React.ReactNode; } /** @internal */ export interface IErrorBoundaryState { - hasError: boolean; + hasError: boolean; } /** @internal */ -export class ErrorBoundary extends React.Component< - IErrorBoundaryProps, - IErrorBoundaryState -> { - constructor(props: IErrorBoundaryProps) { - super(props); - this.state = { hasError: false }; - } +export class ErrorBoundary extends React.Component { + constructor(props: IErrorBoundaryProps) { + super(props); + this.state = { hasError: false }; + } - static getDerivedStateFromError(error: Error) { - return { hasError: true }; - } + static getDerivedStateFromError(error: Error) { + return { hasError: true }; + } - componentDidCatch(error: Error, errorInfo: ErrorInfo) { - console.debug(error); - console.debug(errorInfo); - } + componentDidCatch(error: Error, errorInfo: ErrorInfo) { + console.debug(error); + console.debug(errorInfo); + } - retry = () => { - this.setState({ hasError: false }); - }; + retry = () => { + this.setState({ hasError: false }); + }; - render() { - if (this.state.hasError) { - return ( -
-
-
- {this.props.message} -

- -

-
-
-
- ); - } + render() { + if (this.state.hasError) { + return ( +
+
+
+ {this.props.message} +

+
+
+ +
+ ); + } - return this.props.children; - } + return this.props.children; + } } diff --git a/src/view/Icons.tsx b/src/view/Icons.tsx index 5036b566..0ae45e62 100644 --- a/src/view/Icons.tsx +++ b/src/view/Icons.tsx @@ -1,200 +1,105 @@ -const style = { - width: '1em', - height: '1em', - display: 'flex', - alignItems: 'center', -}; +const style = { width: "1em", height: "1em", display: "flex", alignItems: "center" }; export const CloseIcon = () => { - return ( - - - - - ); -}; + return ( + + + + + ); +} export const MaximizeIcon = () => { - return ( - - - - - ); -}; + return ( + + ); +} export const OverflowIcon = () => { - return ( - - - - - ); -}; + return ( + + ); +} export const EdgeIcon = () => { - return ( - - - - ); -}; + return ( + + ); +} export const PopoutIcon = () => { - return ( - // - - // - // - // - - - - - - ); -}; + return ( + // + + // + // + // + + + + + + + ); +} export const RestoreIcon = () => { - return ( - - - - - ); -}; + return ( + + ); +} export const PinIcon = () => { - return ( - - - - ); -}; + return ( + + + + ); +} export const UnpinIcon = () => { - return ( - - - - - - ); -}; + return ( + + + + + + ); +} export const AsterickIcon = () => { - return ( - - - - ); -}; + return ( + + ); +} export const AddIcon = () => { - return ( - - - - - ); -}; + return ( + + + + + + ); +} export const MenuIcon = () => { - return ( - - - - ); -}; + return ( + + + + ); +} + export const SettingsIcon = (props: React.SVGProps) => { - return ( - - - - - - - ); -}; + return ( + + + + + + + ); +} \ No newline at end of file diff --git a/src/view/Layout.tsx b/src/view/Layout.tsx index 28caf048..9aace520 100755 --- a/src/view/Layout.tsx +++ b/src/view/Layout.tsx @@ -1,1636 +1,1314 @@ -import * as React from 'react'; -import { createPortal } from 'react-dom'; -import { createRoot } from 'react-dom/client'; -import { DockLocation } from '../DockLocation'; -import { DropInfo } from '../DropInfo'; -import { I18nLabel } from '../I18nLabel'; -import { Orientation } from '../Orientation'; -import { Rect } from '../Rect'; -import { CLASSES } from '../Types'; -import { Action } from '../model/Action'; -import { Actions } from '../model/Actions'; -import { BorderNode } from '../model/BorderNode'; -import { IDraggable } from '../model/IDraggable'; -import { IJsonTabNode } from '../model/IJsonModel'; -import { Model } from '../model/Model'; -import { Node } from '../model/Node'; -import { TabNode } from '../model/TabNode'; -import { TabSetNode } from '../model/TabSetNode'; -import { BorderTab } from './BorderTab'; -import { BorderTabSet } from './BorderTabSet'; -import { DragContainer } from './DragContainer'; -import { PopoutWindow } from './PopoutWindow'; -import { - AsterickIcon, - CloseIcon, - EdgeIcon, - MaximizeIcon, - OverflowIcon, - PopoutIcon, - RestoreIcon, - PinIcon, - UnpinIcon, -} from './Icons'; -import { Overlay } from './Overlay'; -import { Row } from './Row'; -import { Tab } from './Tab'; -import { - copyInlineStyles, - enablePointerOnIFrames, - isDesktop, - isSafari, -} from './Utils'; -import { LayoutWindow } from '../model/LayoutWindow'; -import { TabButtonStamp } from './TabButtonStamp'; -import { SizeTracker } from './SizeTracker'; +import * as React from "react"; +import { createPortal } from "react-dom"; +import { createRoot } from "react-dom/client"; +import { DockLocation } from "../DockLocation"; +import { DropInfo } from "../DropInfo"; +import { I18nLabel } from "../I18nLabel"; +import { Orientation } from "../Orientation"; +import { Rect } from "../Rect"; +import { CLASSES } from "../Types"; +import { Action } from "../model/Action"; +import { Actions } from "../model/Actions"; +import { BorderNode } from "../model/BorderNode"; +import { IDraggable } from "../model/IDraggable"; +import { IJsonTabNode } from "../model/IJsonModel"; +import { Model } from "../model/Model"; +import { Node } from "../model/Node"; +import { TabNode } from "../model/TabNode"; +import { TabSetNode } from "../model/TabSetNode"; +import { BorderTab } from "./BorderTab"; +import { BorderTabSet } from "./BorderTabSet"; +import { DragContainer } from "./DragContainer"; +import { PopoutWindow } from "./PopoutWindow"; +import { AsterickIcon, CloseIcon, EdgeIcon, MaximizeIcon, OverflowIcon, PopoutIcon, RestoreIcon, PinIcon, UnpinIcon } from "./Icons"; +import { Overlay } from "./Overlay"; +import { Row } from "./Row"; +import { Tab } from "./Tab"; +import { copyInlineStyles, enablePointerOnIFrames, isDesktop, isSafari } from "./Utils"; +import { LayoutWindow } from "../model/LayoutWindow"; +import { TabButtonStamp } from "./TabButtonStamp"; +import { SizeTracker } from "./SizeTracker"; export interface ILayoutProps { - /** the model for this layout */ - model: Model; - /** factory function for creating the tab components */ - factory: (node: TabNode) => React.ReactNode; - /** sets a top level class name on popout windows */ - popoutClassName?: string; - /** object mapping keys among close, maximize, restore, more, popout to React nodes to use in place of the default icons, can alternatively return functions for creating the React nodes */ - icons?: IIcons; - /** function called whenever the layout generates an action to update the model (allows for intercepting actions before they are dispatched to the model, for example, asking the user to confirm a tab close.) Returning undefined from the function will halt the action, otherwise return the action to continue */ - onAction?: (action: Action) => Action | undefined; - /** function called when rendering a tab, allows leading (icon), content section, buttons and name used in overflow menu to be customized */ - onRenderTab?: ( - node: TabNode, - renderValues: ITabRenderValues, // change the values in this object as required - ) => void; - /** function called when rendering a tabset, allows header and buttons to be customized */ - onRenderTabSet?: ( - tabSetNode: TabSetNode | BorderNode, - renderValues: ITabSetRenderValues, // change the values in this object as required - ) => void; - /** function called when model has changed */ - onModelChange?: (model: Model, action: Action) => void; - /** function called when an external object (not a tab) gets dragged onto the layout, with a single dragenter argument. Should return either undefined to reject the drag/drop or an object with keys dragText, jsonDrop, to create a tab via drag (similar to a call to addTabToTabSet). Function onDropis passed the added tabNodeand thedrop DragEvent`, unless the drag was canceled. */ - onExternalDrag?: (event: React.DragEvent) => - | undefined - | { - json: any; - onDrop?: (node?: Node, event?: React.DragEvent) => void; - }; - /** function called with default css class name, return value is class name that will be used. Mainly for use with css modules. */ - classNameMapper?: (defaultClassName: string) => string; - /** function called for each I18nLabel to allow user translation, currently used for tab and tabset move messages, return undefined to use default values */ - i18nMapper?: (id: I18nLabel, param?: string) => string | undefined; - /** if left undefined will do simple check based on userAgent */ - supportsPopout?: boolean | undefined; - /** URL of popout window relative to origin, defaults to popout.html */ - popoutURL?: string | undefined; - /** boolean value, defaults to false, resize tabs as splitters are dragged. Warning: this can cause resizing to become choppy when tabs are slow to draw */ - realtimeResize?: boolean | undefined; - /** callback for rendering the drag rectangles */ - onRenderDragRect?: DragRectRenderCallback; - /** callback for handling context actions on tabs and tabsets */ - onContextMenu?: NodeMouseEvent; - /** callback for handling mouse clicks on tabs and tabsets with alt, meta, shift keys, also handles center mouse clicks */ - onAuxMouseClick?: NodeMouseEvent; - /** callback for handling the display of the tab overflow menu */ - onShowOverflowMenu?: ShowOverflowMenuCallback; - /** callback for rendering a placeholder when a tabset is empty */ - onTabSetPlaceHolder?: TabSetPlaceHolderCallback; - /** Name given to popout windows, defaults to 'Popout Window' */ - popoutWindowName?: string; + /** the model for this layout */ + model: Model; + /** factory function for creating the tab components */ + factory: (node: TabNode) => React.ReactNode; + /** sets a top level class name on popout windows */ + popoutClassName?: string; + /** object mapping keys among close, maximize, restore, more, popout to React nodes to use in place of the default icons, can alternatively return functions for creating the React nodes */ + icons?: IIcons; + /** function called whenever the layout generates an action to update the model (allows for intercepting actions before they are dispatched to the model, for example, asking the user to confirm a tab close.) Returning undefined from the function will halt the action, otherwise return the action to continue */ + onAction?: (action: Action) => Action | undefined; + /** function called when rendering a tab, allows leading (icon), content section, buttons and name used in overflow menu to be customized */ + onRenderTab?: ( + node: TabNode, + renderValues: ITabRenderValues, // change the values in this object as required + ) => void; + /** function called when rendering a tabset, allows header and buttons to be customized */ + onRenderTabSet?: ( + tabSetNode: TabSetNode | BorderNode, + renderValues: ITabSetRenderValues, // change the values in this object as required + ) => void; + /** function called when model has changed */ + onModelChange?: (model: Model, action: Action) => void; + /** function called when an external object (not a tab) gets dragged onto the layout, with a single dragenter argument. Should return either undefined to reject the drag/drop or an object with keys dragText, jsonDrop, to create a tab via drag (similar to a call to addTabToTabSet). Function onDropis passed the added tabNodeand thedrop DragEvent`, unless the drag was canceled. */ + onExternalDrag?: (event: React.DragEvent) => undefined | { + json: any, + onDrop?: (node?: Node, event?: React.DragEvent) => void + }; + /** function called with default css class name, return value is class name that will be used. Mainly for use with css modules. */ + classNameMapper?: (defaultClassName: string) => string; + /** function called for each I18nLabel to allow user translation, currently used for tab and tabset move messages, return undefined to use default values */ + i18nMapper?: (id: I18nLabel, param?: string) => string | undefined; + /** if left undefined will do simple check based on userAgent */ + supportsPopout?: boolean | undefined; + /** URL of popout window relative to origin, defaults to popout.html */ + popoutURL?: string | undefined; + /** boolean value, defaults to false, resize tabs as splitters are dragged. Warning: this can cause resizing to become choppy when tabs are slow to draw */ + realtimeResize?: boolean | undefined; + /** callback for rendering the drag rectangles */ + onRenderDragRect?: DragRectRenderCallback; + /** callback for handling context actions on tabs and tabsets */ + onContextMenu?: NodeMouseEvent; + /** callback for handling mouse clicks on tabs and tabsets with alt, meta, shift keys, also handles center mouse clicks */ + onAuxMouseClick?: NodeMouseEvent; + /** callback for handling the display of the tab overflow menu */ + onShowOverflowMenu?: ShowOverflowMenuCallback; + /** callback for rendering a placeholder when a tabset is empty */ + onTabSetPlaceHolder?: TabSetPlaceHolderCallback; + /** Name given to popout windows, defaults to 'Popout Window' */ + popoutWindowName?: string; } /** * A React component that hosts a multi-tabbed layout */ export class Layout extends React.Component { - /** @internal */ - private selfRef: React.RefObject; - /** @internal */ - private revision: number; // so LayoutInternal knows this is a parent render (used for optimization) - - /** @internal */ - constructor(props: ILayoutProps) { - super(props); - this.selfRef = React.createRef(); - this.revision = 0; - } - - /** re-render the layout */ - redraw() { - this.selfRef.current!.redraw('parent ' + this.revision); - } - - /** - * Adds a new tab to the given tabset - * @param tabsetId the id of the tabset where the new tab will be added - * @param json the json for the new tab node - * @returns the added tab node or undefined - */ - addTabToTabSet(tabsetId: string, json: IJsonTabNode): TabNode | undefined { - return this.selfRef.current!.addTabToTabSet(tabsetId, json); - } - - /** - * Adds a new tab by dragging an item to the drop location, must be called from within an HTML - * drag start handler. You can use the setDragComponent() method to set the drag image before calling this - * method. - * @param event the drag start event - * @param json the json for the new tab node - * @param onDrop a callback to call when the drag is complete - */ - addTabWithDragAndDrop( - event: DragEvent, - json: IJsonTabNode, - onDrop?: (node?: Node, event?: React.DragEvent) => void, - ) { - this.selfRef.current!.addTabWithDragAndDrop(event, json, onDrop); - } - - /** - * Move a tab/tabset using drag and drop, must be called from within an HTML - * drag start handler - * @param event the drag start event - * @param node the tab or tabset to drag - */ - moveTabWithDragAndDrop(event: DragEvent, node: TabNode | TabSetNode) { - this.selfRef.current!.moveTabWithDragAndDrop(event, node); - } - - /** - * Adds a new tab to the active tabset (if there is one) - * @param json the json for the new tab node - * @returns the added tab node or undefined - */ - addTabToActiveTabSet(json: IJsonTabNode): TabNode | undefined { - return this.selfRef.current!.addTabToActiveTabSet(json); - } - - /** - * Sets the drag image from a react component for a drag event - * @param event the drag event - * @param component the react component to be used for the drag image - * @param x the x position of the drag cursor on the image - * @param y the x position of the drag cursor on the image - */ - setDragComponent( - event: DragEvent, - component: React.ReactNode, - x: number, - y: number, - ) { - this.selfRef.current!.setDragComponent(event, component, x, y); - } - - /** Get the root div element of the layout */ - getRootDiv() { - return this.selfRef.current!.getRootDiv(); - } - - /** @internal */ - render() { - return ( - - ); - } + /** @internal */ + private selfRef: React.RefObject; + /** @internal */ + private revision: number; // so LayoutInternal knows this is a parent render (used for optimization) + + /** @internal */ + constructor(props: ILayoutProps) { + super(props); + this.selfRef = React.createRef(); + this.revision = 0; + } + + /** re-render the layout */ + redraw() { + this.selfRef.current!.redraw("parent " + this.revision); + } + + /** + * Adds a new tab to the given tabset + * @param tabsetId the id of the tabset where the new tab will be added + * @param json the json for the new tab node + * @returns the added tab node or undefined + */ + addTabToTabSet(tabsetId: string, json: IJsonTabNode): TabNode | undefined { + return this.selfRef.current!.addTabToTabSet(tabsetId, json); + } + + /** + * Adds a new tab by dragging an item to the drop location, must be called from within an HTML + * drag start handler. You can use the setDragComponent() method to set the drag image before calling this + * method. + * @param event the drag start event + * @param json the json for the new tab node + * @param onDrop a callback to call when the drag is complete + */ + addTabWithDragAndDrop(event: DragEvent, json: IJsonTabNode, onDrop?: (node?: Node, event?: React.DragEvent) => void) { + this.selfRef.current!.addTabWithDragAndDrop(event, json, onDrop); + } + + /** + * Move a tab/tabset using drag and drop, must be called from within an HTML + * drag start handler + * @param event the drag start event + * @param node the tab or tabset to drag + */ + moveTabWithDragAndDrop(event: DragEvent, node: (TabNode | TabSetNode)) { + this.selfRef.current!.moveTabWithDragAndDrop(event, node); + } + + /** + * Adds a new tab to the active tabset (if there is one) + * @param json the json for the new tab node + * @returns the added tab node or undefined + */ + addTabToActiveTabSet(json: IJsonTabNode): TabNode | undefined { + return this.selfRef.current!.addTabToActiveTabSet(json); + } + + /** + * Sets the drag image from a react component for a drag event + * @param event the drag event + * @param component the react component to be used for the drag image + * @param x the x position of the drag cursor on the image + * @param y the x position of the drag cursor on the image + */ + setDragComponent(event: DragEvent, component: React.ReactNode, x: number, y: number) { + this.selfRef.current!.setDragComponent(event, component, x, y); + } + + /** Get the root div element of the layout */ + getRootDiv() { + return this.selfRef.current!.getRootDiv(); + } + + /** @internal */ + render() { + return () + } } /** @internal */ interface ILayoutInternalProps extends ILayoutProps { - renderRevision: number; + renderRevision: number; - // used only for popout windows: - windowId?: string; - mainLayout?: LayoutInternal; + // used only for popout windows: + windowId?: string; + mainLayout?: LayoutInternal; } /** @internal */ interface ILayoutInternalState { - rect: Rect; - editingTab?: TabNode; - portal?: React.ReactPortal; - showEdges: boolean; - showOverlay: boolean; - calculatedBorderBarSize: number; - layoutRevision: number; - forceRevision: number; - showHiddenBorder: DockLocation; + rect: Rect; + editingTab?: TabNode; + portal?: React.ReactPortal; + showEdges: boolean; + showOverlay: boolean; + calculatedBorderBarSize: number; + layoutRevision: number; + forceRevision: number; + showHiddenBorder: DockLocation; } /** @internal */ -export class LayoutInternal extends React.Component< - ILayoutInternalProps, - ILayoutInternalState -> { - public static dragState: DragState | undefined = undefined; - - private selfRef: React.RefObject; - private moveablesRef: React.RefObject; - private findBorderBarSizeRef: React.RefObject; - private mainRef: React.RefObject; - private previousModel?: Model; - private orderedTabIds: string[]; - private orderedTabMoveableIds: string[]; - private moveableElementMap = new Map(); - private dropInfo: DropInfo | undefined; - private outlineDiv?: HTMLElement; - private currentDocument?: Document; - private currentWindow?: Window; - private supportsPopout: boolean; - private popoutURL: string; - private icons: IIcons; - private resizeObserver?: ResizeObserver; - - private dragEnterCount: number = 0; - private dragging: boolean = false; - private windowId: string; - private layoutWindow: LayoutWindow; - private mainLayout: LayoutInternal; - private isMainWindow: boolean; - private isDraggingOverWindow: boolean; - private styleObserver: MutationObserver | undefined; - private popoutWindowName: string; - // private renderCount: any; - - constructor(props: ILayoutInternalProps) { - super(props); - - this.orderedTabIds = []; - this.orderedTabMoveableIds = []; - this.selfRef = React.createRef(); - this.moveablesRef = React.createRef(); - this.mainRef = React.createRef(); - this.findBorderBarSizeRef = React.createRef(); - - this.supportsPopout = - props.supportsPopout !== undefined - ? props.supportsPopout - : defaultSupportsPopout; - this.popoutURL = props.popoutURL ? props.popoutURL : 'popout.html'; - this.icons = { ...defaultIcons, ...props.icons }; - this.windowId = props.windowId ? props.windowId : Model.MAIN_WINDOW_ID; - this.mainLayout = this.props.mainLayout ? this.props.mainLayout : this; - this.isDraggingOverWindow = false; - this.layoutWindow = this.props.model.getwindowsMap().get(this.windowId)!; - this.layoutWindow.layout = this; - this.popoutWindowName = this.props.popoutWindowName || 'Popout Window'; - // this.renderCount = 0; - - this.state = { - rect: Rect.empty(), - editingTab: undefined, - showEdges: false, - showOverlay: false, - calculatedBorderBarSize: 29, - layoutRevision: 0, - forceRevision: 0, - showHiddenBorder: DockLocation.CENTER, - }; +export class LayoutInternal extends React.Component { + public static dragState: DragState | undefined = undefined; + + private selfRef: React.RefObject; + private moveablesRef: React.RefObject; + private findBorderBarSizeRef: React.RefObject; + private mainRef: React.RefObject; + private previousModel?: Model; + private orderedTabIds: string[]; + private orderedTabMoveableIds: string[]; + private moveableElementMap = new Map(); + private dropInfo: DropInfo | undefined; + private outlineDiv?: HTMLElement; + private currentDocument?: Document; + private currentWindow?: Window; + private supportsPopout: boolean; + private popoutURL: string; + private icons: IIcons; + private resizeObserver?: ResizeObserver; + + private dragEnterCount: number = 0; + private dragging: boolean = false; + private windowId: string; + private layoutWindow: LayoutWindow; + private mainLayout: LayoutInternal; + private isMainWindow: boolean; + private isDraggingOverWindow: boolean; + private styleObserver: MutationObserver | undefined; + private popoutWindowName: string; + // private renderCount: any; + + constructor(props: ILayoutInternalProps) { + super(props); + + this.orderedTabIds = []; + this.orderedTabMoveableIds = []; + this.selfRef = React.createRef(); + this.moveablesRef = React.createRef(); + this.mainRef = React.createRef(); + this.findBorderBarSizeRef = React.createRef(); + + this.supportsPopout = props.supportsPopout !== undefined ? props.supportsPopout : defaultSupportsPopout; + this.popoutURL = props.popoutURL ? props.popoutURL : "popout.html"; + this.icons = { ...defaultIcons, ...props.icons }; + this.windowId = props.windowId ? props.windowId : Model.MAIN_WINDOW_ID; + this.mainLayout = this.props.mainLayout ? this.props.mainLayout : this; + this.isDraggingOverWindow = false; + this.layoutWindow = this.props.model.getwindowsMap().get(this.windowId)!; + this.layoutWindow.layout = this; + this.popoutWindowName = this.props.popoutWindowName || "Popout Window"; + // this.renderCount = 0; + + this.state = { + rect: Rect.empty(), + editingTab: undefined, + showEdges: false, + showOverlay: false, + calculatedBorderBarSize: 29, + layoutRevision: 0, + forceRevision: 0, + showHiddenBorder: DockLocation.CENTER + }; + + this.isMainWindow = this.windowId === Model.MAIN_WINDOW_ID; + } - this.isMainWindow = this.windowId === Model.MAIN_WINDOW_ID; - } + componentDidMount() { + this.updateRect(); - componentDidMount() { - this.updateRect(); + this.currentDocument = (this.selfRef.current as HTMLElement).ownerDocument; + this.currentWindow = this.currentDocument.defaultView!; - this.currentDocument = (this.selfRef.current as HTMLElement).ownerDocument; - this.currentWindow = this.currentDocument.defaultView!; + this.layoutWindow.window = this.currentWindow; + this.layoutWindow.toScreenRectFunction = (r) => this.getScreenRect(r); - this.layoutWindow.window = this.currentWindow; - this.layoutWindow.toScreenRectFunction = (r) => this.getScreenRect(r); + this.resizeObserver = new ResizeObserver(entries => { + requestAnimationFrame(() => { + this.updateRect(); + }); + }); + if (this.selfRef.current) { + this.resizeObserver.observe(this.selfRef.current); + } - this.resizeObserver = new ResizeObserver((entries) => { - requestAnimationFrame(() => { - this.updateRect(); - }); - }); - if (this.selfRef.current) { - this.resizeObserver.observe(this.selfRef.current); - } - - if (this.isMainWindow) { - this.props.model.addChangeListener(this.onModelChange); - this.updateLayoutMetrics(); - } else { - // since resizeObserver doesn't always work as expected when observing element in another document - this.currentWindow.addEventListener('resize', () => { - this.updateRect(); - }); + if (this.isMainWindow) { + this.props.model.addChangeListener(this.onModelChange); + this.updateLayoutMetrics(); + } else { + // since resizeObserver doesn't always work as expected when observing element in another document + this.currentWindow.addEventListener("resize", () => { + this.updateRect(); + }); + + const sourceElement = this.props.mainLayout!.getRootDiv()!; + const targetElement = this.selfRef.current!; - const sourceElement = this.props.mainLayout!.getRootDiv()!; - const targetElement = this.selfRef.current!; + copyInlineStyles(sourceElement, targetElement); - copyInlineStyles(sourceElement, targetElement); + this.styleObserver = new MutationObserver(() => { + const changed = copyInlineStyles(sourceElement, targetElement); + if (changed) { + this.redraw("mutation observer"); + } + }); - this.styleObserver = new MutationObserver(() => { - const changed = copyInlineStyles(sourceElement, targetElement); - if (changed) { - this.redraw('mutation observer'); + // Observe changes to the source element's style attribute + this.styleObserver.observe(sourceElement, { attributeFilter: ['style'] }); } - }); - // Observe changes to the source element's style attribute - this.styleObserver.observe(sourceElement, { attributeFilter: ['style'] }); + // allow tabs to overlay when hidden + document.addEventListener('visibilitychange', () => { + for (const [_, layoutWindow] of this.props.model.getwindowsMap()) { + const layout = layoutWindow.layout; + if (layout) { + this.redraw("visibility change"); + } + } + }); } - // allow tabs to overlay when hidden - document.addEventListener('visibilitychange', () => { - for (const [_, layoutWindow] of this.props.model.getwindowsMap()) { - const layout = layoutWindow.layout; - if (layout) { - this.redraw('visibility change'); + componentDidUpdate() { + this.currentDocument = (this.selfRef.current as HTMLElement).ownerDocument; + this.currentWindow = this.currentDocument.defaultView!; + if (this.isMainWindow) { + if (this.props.model !== this.previousModel) { + if (this.previousModel !== undefined) { + this.previousModel.removeChangeListener(this.onModelChange); // stop listening to old model + } + this.props.model.getwindowsMap().get(this.windowId)!.layout = this; + this.props.model.addChangeListener(this.onModelChange); + this.layoutWindow = this.props.model.getwindowsMap().get(this.windowId)!; + this.layoutWindow.layout = this; + this.layoutWindow.toScreenRectFunction = (r) => this.getScreenRect(r); + this.previousModel = this.props.model; + this.tidyMoveablesMap(); + } + + this.updateLayoutMetrics(); } - } - }); - } - - componentDidUpdate() { - this.currentDocument = (this.selfRef.current as HTMLElement).ownerDocument; - this.currentWindow = this.currentDocument.defaultView!; - if (this.isMainWindow) { - if (this.props.model !== this.previousModel) { - if (this.previousModel !== undefined) { - this.previousModel.removeChangeListener(this.onModelChange); // stop listening to old model + } + + componentWillUnmount() { + if (this.selfRef.current) { + this.resizeObserver?.unobserve(this.selfRef.current); } - this.props.model.getwindowsMap().get(this.windowId)!.layout = this; - this.props.model.addChangeListener(this.onModelChange); - this.layoutWindow = this.props.model - .getwindowsMap() - .get(this.windowId)!; - this.layoutWindow.layout = this; - this.layoutWindow.toScreenRectFunction = (r) => this.getScreenRect(r); - this.previousModel = this.props.model; - this.tidyMoveablesMap(); - } - - this.updateLayoutMetrics(); - } - } - - componentWillUnmount() { - if (this.selfRef.current) { - this.resizeObserver?.unobserve(this.selfRef.current); - } - - if (this.isMainWindow) { - this.props.model.removeChangeListener(this.onModelChange); - } - this.styleObserver?.disconnect(); - } - - render() { - // console.log("render", this.windowId, this.state.revision, this.renderCount++); - // first render will be used to find the size (via selfRef) - if (!this.selfRef.current) { - return ( -
-
- {this.renderMetricsElements()} -
- ); - } - - const model = this.props.model; - model.getRoot(this.windowId).calcMinMaxSize(); - model.getRoot(this.windowId).setPaths(''); - model.getBorderSet().setPaths(); - - const inner = this.renderLayout(); - const outer = this.renderBorders(inner); - - const tabs = this.renderTabs(); - const reorderedTabs = this.reorderComponents(tabs, this.orderedTabIds); - - let floatingWindows = null; - let reorderedTabMoveables = null; - let tabStamps = null; - let metricElements = null; - - if (this.isMainWindow) { - floatingWindows = this.renderWindows(); - metricElements = this.renderMetricsElements(); - const tabMoveables = this.renderTabMoveables(); - reorderedTabMoveables = this.reorderComponents( - tabMoveables, - this.orderedTabMoveableIds, - ); - tabStamps = ( -
- {this.renderTabStamps()} -
- ); - } - - return ( -
-
- {metricElements} - - {outer} - {reorderedTabs} - {reorderedTabMoveables} - {tabStamps} - {this.state.portal} - {floatingWindows} -
- ); - } - - renderBorders(inner: React.ReactNode) { - const classMain = this.getClassName(CLASSES.FLEXLAYOUT__LAYOUT_MAIN); - const borders = this.props.model.getBorderSet().getBorderMap(); - if (this.isMainWindow && borders.size > 0) { - inner = ( -
- {inner} -
- ); - const borderSetComponents = new Map(); - const borderSetContentComponents = new Map< - DockLocation, - React.ReactNode - >(); - for (const [_, location] of DockLocation.values) { - const border = borders.get(location); - const showBorder = - border && - border.isShowing() && - (!border.isAutoHide() || - (border.isAutoHide() && - (border.getChildren().length > 0 || - this.state.showHiddenBorder === location))); - if (showBorder) { - borderSetComponents.set( - location, - , - ); - borderSetContentComponents.set( - location, - , - ); + + if (this.isMainWindow) { + this.props.model.removeChangeListener(this.onModelChange); } - } - - const classBorderOuter = this.getClassName( - CLASSES.FLEXLAYOUT__LAYOUT_BORDER_CONTAINER, - ); - const classBorderInner = this.getClassName( - CLASSES.FLEXLAYOUT__LAYOUT_BORDER_CONTAINER_INNER, - ); - - if (this.props.model.getBorderSet().getLayoutHorizontal()) { - const innerWithBorderTabs = ( -
- {borderSetContentComponents.get(DockLocation.TOP)} -
- {borderSetContentComponents.get(DockLocation.LEFT)} - {inner} - {borderSetContentComponents.get(DockLocation.RIGHT)} -
- {borderSetContentComponents.get(DockLocation.BOTTOM)} -
- ); + this.styleObserver?.disconnect(); + } + + render() { + // console.log("render", this.windowId, this.state.revision, this.renderCount++); + // first render will be used to find the size (via selfRef) + if (!this.selfRef.current) { + return ( +
+
+ {this.renderMetricsElements()} +
+ ); + } + + const model = this.props.model; + model.getRoot(this.windowId).calcMinMaxSize(); + model.getRoot(this.windowId).setPaths(""); + model.getBorderSet().setPaths(); + + const inner = this.renderLayout(); + const outer = this.renderBorders(inner); + + const tabs = this.renderTabs(); + const reorderedTabs = this.reorderComponents(tabs, this.orderedTabIds); + + let floatingWindows = null; + let reorderedTabMoveables = null; + let tabStamps = null; + let metricElements = null; + + if (this.isMainWindow) { + floatingWindows = this.renderWindows(); + metricElements = this.renderMetricsElements(); + const tabMoveables = this.renderTabMoveables(); + reorderedTabMoveables = this.reorderComponents(tabMoveables, this.orderedTabMoveableIds); + tabStamps =
+ {this.renderTabStamps()} +
; + } + return ( -
- {borderSetComponents.get(DockLocation.TOP)} -
- {borderSetComponents.get(DockLocation.LEFT)} - {innerWithBorderTabs} - {borderSetComponents.get(DockLocation.RIGHT)} -
- {borderSetComponents.get(DockLocation.BOTTOM)} -
- ); - } else { - const innerWithBorderTabs = ( -
- {borderSetContentComponents.get(DockLocation.LEFT)}
- {borderSetContentComponents.get(DockLocation.TOP)} - {inner} - {borderSetContentComponents.get(DockLocation.BOTTOM)} +
+ {metricElements} + + {outer} + {reorderedTabs} + {reorderedTabMoveables} + {tabStamps} + {this.state.portal} + {floatingWindows}
- {borderSetContentComponents.get(DockLocation.RIGHT)} -
); + } + renderBorders( + inner: React.ReactNode + ) { + const classMain = this.getClassName(CLASSES.FLEXLAYOUT__LAYOUT_MAIN); + const borders = this.props.model.getBorderSet().getBorderMap() + if (this.isMainWindow && borders.size > 0) { + inner = ( +
+ {inner} +
); + const borderSetComponents = new Map(); + const borderSetContentComponents = new Map(); + for (const [_, location] of DockLocation.values) { + const border = borders.get(location); + const showBorder = border && border.isShowing() && ( + !border.isAutoHide() || + (border.isAutoHide() && (border.getChildren().length > 0 || this.state.showHiddenBorder === location))); + if (showBorder) { + borderSetComponents.set(location, ); + borderSetContentComponents.set(location, ); + } + } + + const classBorderOuter = this.getClassName(CLASSES.FLEXLAYOUT__LAYOUT_BORDER_CONTAINER); + const classBorderInner = this.getClassName(CLASSES.FLEXLAYOUT__LAYOUT_BORDER_CONTAINER_INNER); + + if (this.props.model.getBorderSet().getLayoutHorizontal()) { + const innerWithBorderTabs = ( +
+ {borderSetContentComponents.get(DockLocation.TOP)} +
+ {borderSetContentComponents.get(DockLocation.LEFT)} + {inner} + {borderSetContentComponents.get(DockLocation.RIGHT)} +
+ {borderSetContentComponents.get(DockLocation.BOTTOM)} +
+ ); + return ( +
+ {borderSetComponents.get(DockLocation.TOP)} +
+ {borderSetComponents.get(DockLocation.LEFT)} + {innerWithBorderTabs} + {borderSetComponents.get(DockLocation.RIGHT)} +
+ {borderSetComponents.get(DockLocation.BOTTOM)} +
+ ); + } else { + const innerWithBorderTabs = ( +
+ {borderSetContentComponents.get(DockLocation.LEFT)} +
+ {borderSetContentComponents.get(DockLocation.TOP)} + {inner} + {borderSetContentComponents.get(DockLocation.BOTTOM)} +
+ {borderSetContentComponents.get(DockLocation.RIGHT)} +
+ ); + + return ( +
+ {borderSetComponents.get(DockLocation.LEFT)} +
+ {borderSetComponents.get(DockLocation.TOP)} + {innerWithBorderTabs} + {borderSetComponents.get(DockLocation.BOTTOM)} +
+ {borderSetComponents.get(DockLocation.RIGHT)} +
+ ); + } + + } else { // no borders + return ( +
+ {inner} +
+ ); + } + } + + renderLayout() { return ( -
- {borderSetComponents.get(DockLocation.LEFT)} -
- {borderSetComponents.get(DockLocation.TOP)} - {innerWithBorderTabs} - {borderSetComponents.get(DockLocation.BOTTOM)} -
- {borderSetComponents.get(DockLocation.RIGHT)} -
+ <> + + {this.renderEdgeIndicators()} + ); - } - } else { - // no borders - return ( -
- {inner} -
- ); - } - } - - renderLayout() { - return ( - <> - - {this.renderEdgeIndicators()} - - ); - } - - renderEdgeIndicators() { - const edges: React.ReactNode[] = []; - const arrowIcon = this.icons.edgeArrow; - if (this.state.showEdges) { - const r = this.props.model.getRoot(this.windowId).getRect(); - const length = edgeRectLength; - const width = edgeRectWidth; - const offset = edgeRectLength / 2; - const className = this.getClassName(CLASSES.FLEXLAYOUT__EDGE_RECT); - const radius = 50; - edges.push( -
-
{arrowIcon}
-
, - ); - edges.push( -
-
{arrowIcon}
-
, - ); - edges.push( -
-
{arrowIcon}
-
, - ); - edges.push( -
-
{arrowIcon}
-
, - ); - } - - return edges; - } - - renderWindows() { - const floatingWindows: React.ReactNode[] = []; - if (this.supportsPopout) { - const windows = this.props.model.getwindowsMap(); - let i = 1; - for (const [windowId, layoutWindow] of windows) { - if (windowId !== Model.MAIN_WINDOW_ID) { - floatingWindows.push( - , - element, - key, - ), - ); - - child.setRendered(renderTab); + + return edges; + } + + renderWindows() { + const floatingWindows: React.ReactNode[] = []; + if (this.supportsPopout) { + const windows = this.props.model.getwindowsMap(); + let i = 1; + for (const [windowId, layoutWindow] of windows) { + + if (windowId !== Model.MAIN_WINDOW_ID) { + floatingWindows.push( + + + , element, key)); + + child.setRendered(renderTab); + } + } + }); + + return tabMoveables; + } - return tabMoveables; - } + renderTabStamps() { + const tabStamps: React.ReactNode[] = []; - renderTabStamps() { - const tabStamps: React.ReactNode[] = []; + this.props.model.visitNodes((node) => { + if (node instanceof TabNode) { + const child = node as TabNode; + + // what the tab should look like when dragged (since images need to have been loaded before drag image can be taken) + tabStamps.push() + } + }); + + return tabStamps; + } - this.props.model.visitNodes((node) => { - if (node instanceof TabNode) { - const child = node as TabNode; + renderTabs() { + const tabs = new Map(); + this.props.model.visitWindowNodes(this.windowId, (node) => { + if (node instanceof TabNode) { + const child = node as TabNode; + const selected = child.isSelected(); + const path = child.getPath(); + + const renderTab = child.isRendered() || selected || !child.isEnableRenderOnDemand(); + + if (renderTab) { + tabs.set(child.getId(), ( + + )); + } + } + }); + return tabs; + } - // what the tab should look like when dragged (since images need to have been loaded before drag image can be taken) - tabStamps.push( - , + renderMetricsElements() { + return ( +
+ FindBorderBarSize +
); - } - }); - - return tabStamps; - } - - renderTabs() { - const tabs = new Map(); - this.props.model.visitWindowNodes(this.windowId, (node) => { - if (node instanceof TabNode) { - const child = node as TabNode; - const selected = child.isSelected(); - const path = child.getPath(); - - const renderTab = - child.isRendered() || selected || !child.isEnableRenderOnDemand(); - - if (renderTab) { - tabs.set( - child.getId(), - , - ); + } + + checkForBorderToShow(x: number, y: number) { + const r = this.getBoundingClientRect(this.mainRef.current!); + const c = r.getCenter(); + const margin = edgeRectWidth; + const offset = edgeRectLength / 2; + + let overEdge = false; + if (this.props.model.isEnableEdgeDock() && this.state.showHiddenBorder === DockLocation.CENTER) { + if ((y > c.y - offset && y < c.y + offset) || + (x > c.x - offset && x < c.x + offset)) { + overEdge = true; + } } - } - }); - return tabs; - } - - renderMetricsElements() { - return ( -
- FindBorderBarSize -
- ); - } - - checkForBorderToShow(x: number, y: number) { - const r = this.getBoundingClientRect(this.mainRef.current!); - const c = r.getCenter(); - const margin = edgeRectWidth; - const offset = edgeRectLength / 2; - - let overEdge = false; - if ( - this.props.model.isEnableEdgeDock() && - this.state.showHiddenBorder === DockLocation.CENTER - ) { - if ( - (y > c.y - offset && y < c.y + offset) || - (x > c.x - offset && x < c.x + offset) - ) { - overEdge = true; - } - } - - let location = DockLocation.CENTER; - if (!overEdge) { - if (x <= r.x + margin) { - location = DockLocation.LEFT; - } else if (x >= r.getRight() - margin) { - location = DockLocation.RIGHT; - } else if (y <= r.y + margin) { - location = DockLocation.TOP; - } else if (y >= r.getBottom() - margin) { - location = DockLocation.BOTTOM; - } - } - - if (location !== this.state.showHiddenBorder) { - this.setState({ showHiddenBorder: location }); - } - } - - updateLayoutMetrics = () => { - if (this.findBorderBarSizeRef.current) { - const borderBarSize = - this.findBorderBarSizeRef.current.getBoundingClientRect().height; - if (borderBarSize !== this.state.calculatedBorderBarSize) { - this.setState({ calculatedBorderBarSize: borderBarSize }); - } - } - }; - - tidyMoveablesMap() { - // console.log("tidyMoveablesMap"); - const tabs = new Map(); - this.props.model.visitNodes((node, _) => { - if (node instanceof TabNode) { - tabs.set(node.getId(), node); - } - }); - - for (const [nodeId, element] of this.moveableElementMap) { - if (!tabs.has(nodeId)) { - // console.log("delete", nodeId); - element.remove(); // remove from dom - this.moveableElementMap.delete(nodeId); // remove map entry - } - } - } - - reorderComponents(components: Map, ids: string[]) { - const nextIds: string[] = []; - const nextIdsSet = new Set(); - - let reordered: React.ReactNode[] = []; - // Keep any previous tabs in the same DOM order as before, removing any that have been deleted - for (const id of ids) { - if (components.get(id)) { - nextIds.push(id); - nextIdsSet.add(id); - } - } - ids.splice(0, ids.length, ...nextIds); - - // Add tabs that have been added to the DOM - for (const [id, _] of components) { - if (!nextIdsSet.has(id)) { - ids.push(id); - } - } - - reordered = ids.map((id) => { - return components.get(id); - }); - - return reordered; - } - - onModelChange = (action: Action) => { - this.redrawInternal('model change'); - if (this.props.onModelChange) { - this.props.onModelChange(this.props.model, action); - } - }; - - redraw(type?: string) { - // console.log("redraw", this.windowId, type); - this.mainLayout.setState((state, props) => { - return { forceRevision: state.forceRevision + 1 }; - }); - } - - redrawInternal(type: string) { - // console.log("redrawInternal", this.windowId, type); - this.mainLayout.setState((state, props) => { - return { layoutRevision: state.layoutRevision + 1 }; - }); - } - - doAction(action: Action): Node | undefined { - if (this.props.onAction !== undefined) { - const outcome = this.props.onAction(action); - if (outcome !== undefined) { - return this.props.model.doAction(outcome); - } - return undefined; - } else { - return this.props.model.doAction(action); - } - } - - updateRect = () => { - if (this.selfRef.current) { - const rect = Rect.fromDomRect( - this.selfRef.current.getBoundingClientRect(), - ); - if ( - !rect.equals(this.state.rect) && - rect.width !== 0 && - rect.height !== 0 - ) { - // console.log("updateRect", rect.floor()); - this.setState({ rect }); - if (this.windowId !== Model.MAIN_WINDOW_ID) { - this.redrawInternal('rect updated'); + + let location = DockLocation.CENTER; + if (!overEdge) { + if (x <= r.x + margin) { + location = DockLocation.LEFT; + } else if (x >= r.getRight() - margin) { + location = DockLocation.RIGHT; + } else if (y <= r.y + margin) { + location = DockLocation.TOP; + } else if (y >= r.getBottom() - margin) { + location = DockLocation.BOTTOM; + } + } + + if (location !== this.state.showHiddenBorder) { + this.setState({ showHiddenBorder: location }); } - } } - }; - - getBoundingClientRect(div: HTMLElement): Rect { - const layoutRect = this.getDomRect(); - if (layoutRect) { - return Rect.getBoundingClientRect(div).relativeTo(layoutRect); - } - return Rect.empty(); - } - - getMoveableContainer() { - return this.moveablesRef.current; - } - - getMoveableElement(id: string) { - let moveableElement = this.moveableElementMap.get(id); - if (moveableElement === undefined) { - moveableElement = document.createElement('div'); - this.moveablesRef.current!.appendChild(moveableElement); - moveableElement.className = CLASSES.FLEXLAYOUT__TAB_MOVEABLE; - this.moveableElementMap.set(id, moveableElement); - } - return moveableElement; - } - - getMainLayout() { - return this.mainLayout; - } - - getClassName = (defaultClassName: string) => { - if (this.props.classNameMapper === undefined) { - return defaultClassName; - } else { - return this.props.classNameMapper(defaultClassName); - } - }; - - getCurrentDocument() { - return this.currentDocument; - } - - getDomRect() { - // must get on demand, since page may have scrolled - if (this.selfRef.current) { - return Rect.fromDomRect(this.selfRef.current.getBoundingClientRect()); - } else { - return Rect.empty(); - } - } - - getWindowId() { - return this.windowId; - } - - getRootDiv() { - return this.selfRef.current; - } - - getMainElement() { - return this.mainRef.current; - } - - getFactory() { - return this.props.factory; - } - - isSupportsPopout() { - return this.supportsPopout; - } - - isRealtimeResize() { - return this.props.realtimeResize ?? false; - } - - getPopoutURL() { - return this.popoutURL; - } - - setEditingTab(tabNode?: TabNode) { - this.setState({ editingTab: tabNode }); - } - - getEditingTab() { - return this.state.editingTab; - } - - getModel() { - return this.props.model; - } - - onCloseWindow = (windowLayout: LayoutWindow) => { - this.doAction(Actions.closeWindow(windowLayout.windowId)); - }; - - onSetWindow = (windowLayout: LayoutWindow, window: Window) => {}; - - getScreenRect(inRect: Rect) { - const rect = inRect.clone(); - const layoutRect = this.getDomRect(); - // Note: outerHeight can be less than innerHeight when window is zoomed, so cannot use - // const navHeight = Math.min(65, this.currentWindow!.outerHeight - this.currentWindow!.innerHeight); - // const navWidth = Math.min(65, this.currentWindow!.outerWidth - this.currentWindow!.innerWidth); - const navHeight = 60; - const navWidth = 2; - // console.log(rect.y, this.currentWindow!.screenX,layoutRect.y); - rect.x = - this.currentWindow!.screenX + - this.currentWindow!.scrollX + - navWidth / 2 + - layoutRect.x + - rect.x; - rect.y = - this.currentWindow!.screenY + - this.currentWindow!.scrollY + - (navHeight - navWidth / 2) + - layoutRect.y + - rect.y; - rect.height += navHeight; - rect.width += navWidth; - return rect; - } - - addTabToTabSet(tabsetId: string, json: IJsonTabNode): TabNode | undefined { - const tabsetNode = this.props.model.getNodeById(tabsetId); - if (tabsetNode !== undefined) { - const node = this.doAction( - Actions.addNode(json, tabsetId, DockLocation.CENTER, -1), - ); - return node as TabNode; - } - return undefined; - } - - addTabToActiveTabSet(json: IJsonTabNode): TabNode | undefined { - const tabsetNode = this.props.model.getActiveTabset(this.windowId); - if (tabsetNode !== undefined) { - const node = this.doAction( - Actions.addNode(json, tabsetNode.getId(), DockLocation.CENTER, -1), - ); - return node as TabNode; - } - return undefined; - } - - showControlInPortal = (control: React.ReactNode, element: HTMLElement) => { - const portal = createPortal(control, element) as React.ReactPortal; - this.setState({ portal }); - }; - - hideControlInPortal = () => { - this.setState({ portal: undefined }); - }; - - getIcons = () => { - return this.icons; - }; - - maximize(tabsetNode: TabSetNode) { - this.doAction( - Actions.maximizeToggle(tabsetNode.getId(), this.getWindowId()), - ); - } - - customizeTab(tabNode: TabNode, renderValues: ITabRenderValues) { - if (this.props.onRenderTab) { - this.props.onRenderTab(tabNode, renderValues); - } - } - - customizeTabSet( - tabSetNode: TabSetNode | BorderNode, - renderValues: ITabSetRenderValues, - ) { - if (this.props.onRenderTabSet) { - this.props.onRenderTabSet(tabSetNode, renderValues); - } - } - - i18nName(id: I18nLabel, param?: string) { - let message; - if (this.props.i18nMapper) { - message = this.props.i18nMapper(id, param); - } - if (message === undefined) { - message = id + (param === undefined ? '' : param); - } - return message; - } - - getShowOverflowMenu() { - return this.props.onShowOverflowMenu; - } - - getTabSetPlaceHolderCallback() { - return this.props.onTabSetPlaceHolder; - } - - showContextMenu( - node: TabNode | TabSetNode | BorderNode, - event: React.MouseEvent, - ) { - if (this.props.onContextMenu) { - this.props.onContextMenu(node, event); + + updateLayoutMetrics = () => { + if (this.findBorderBarSizeRef.current) { + const borderBarSize = this.findBorderBarSizeRef.current.getBoundingClientRect().height; + if (borderBarSize !== this.state.calculatedBorderBarSize) { + this.setState({ calculatedBorderBarSize: borderBarSize }); + } + } + }; + + tidyMoveablesMap() { + // console.log("tidyMoveablesMap"); + const tabs = new Map(); + this.props.model.visitNodes((node, _) => { + if (node instanceof TabNode) { + tabs.set(node.getId(), node); + } + }); + + for (const [nodeId, element] of this.moveableElementMap) { + if (!tabs.has(nodeId)) { + // console.log("delete", nodeId); + element.remove(); // remove from dom + this.moveableElementMap.delete(nodeId); // remove map entry + } + } } - } - auxMouseClick( - node: TabNode | TabSetNode | BorderNode, - event: React.MouseEvent, - ) { - if (this.props.onAuxMouseClick) { - this.props.onAuxMouseClick(node, event); - } - } - - public showOverlay(show: boolean) { - this.setState({ showOverlay: show }); - enablePointerOnIFrames(!show, this.currentDocument!); - } - - // *************************** Start Drag Drop ************************************* - - addTabWithDragAndDrop( - event: DragEvent, - json: IJsonTabNode, - onDrop?: (node?: Node, event?: React.DragEvent) => void, - ) { - const tempNode = TabNode.fromJson(json, this.props.model, false); - LayoutInternal.dragState = new DragState( - this.mainLayout, - DragSource.Add, - tempNode, - json, - onDrop, - ); - } - - moveTabWithDragAndDrop(event: DragEvent, node: TabNode | TabSetNode) { - this.setDragNode(event, node); - } - - public setDragNode = (event: DragEvent, node: Node & IDraggable) => { - LayoutInternal.dragState = new DragState( - this.mainLayout, - DragSource.Internal, - node, - undefined, - undefined, - ); - // Note: can only set (very) limited types on android! so cannot set json - // Note: must set text/plain for android to allow drag, - // so just set a simple message indicating its a flexlayout drag (this is not used anywhere else) - event.dataTransfer!.setData('text/plain', '--flexlayout--'); - event.dataTransfer!.effectAllowed = 'copyMove'; - event.dataTransfer!.dropEffect = 'move'; - - this.dragEnterCount = 0; - - if (node instanceof TabSetNode) { - let rendered = false; - let content = this.i18nName(I18nLabel.Move_Tabset); - if (node.getChildren().length > 0) { - content = this.i18nName(I18nLabel.Move_Tabs).replace( - '?', - String(node.getChildren().length), - ); - } - if (this.props.onRenderDragRect) { - const dragComponent = this.props.onRenderDragRect( - content, - node, - undefined, - ); - if (dragComponent) { - this.setDragComponent(event, dragComponent, 10, 10); - rendered = true; + reorderComponents(components: Map, ids: string[]) { + const nextIds: string[] = []; + const nextIdsSet = new Set(); + + let reordered: React.ReactNode[] = []; + // Keep any previous tabs in the same DOM order as before, removing any that have been deleted + for (const id of ids) { + if (components.get(id)) { + nextIds.push(id); + nextIdsSet.add(id); + } } - } - if (!rendered) { - this.setDragComponent(event, content, 10, 10); - } - } else { - const element = event.target as HTMLElement; - const rect = element.getBoundingClientRect(); - const offsetX = event.clientX - rect.left; - const offsetY = event.clientY - rect.top; - const parentNode = node?.getParent(); - const isInVerticalBorder = - parentNode instanceof BorderNode && - (parentNode as BorderNode).getOrientation() === Orientation.HORZ; - const x = isInVerticalBorder ? 10 : offsetX; - const y = isInVerticalBorder ? 10 : offsetY; - - let rendered = false; - if (this.props.onRenderDragRect) { - const content = ( - - ); - const dragComponent = this.props.onRenderDragRect( - content, - node, - undefined, - ); - if (dragComponent) { - this.setDragComponent(event, dragComponent, x, y); - rendered = true; + ids.splice(0, ids.length, ...nextIds); + + // Add tabs that have been added to the DOM + for (const [id, _] of components) { + if (!nextIdsSet.has(id)) { + ids.push(id); + } } - } - if (!rendered) { - if (isSafari()) { - // safari doesnt render the offscreen tabstamps - this.setDragComponent( - event, - , - x, - y, - ); + + reordered = ids.map((id) => { + return components.get(id); + }); + + return reordered; + } + + onModelChange = (action: Action) => { + this.redrawInternal("model change"); + if (this.props.onModelChange) { + this.props.onModelChange(this.props.model, action); + } + }; + + redraw(type?: string) { + // console.log("redraw", this.windowId, type); + this.mainLayout.setState((state, props) => { return { forceRevision: state.forceRevision + 1 } }); + } + + redrawInternal(type: string) { + // console.log("redrawInternal", this.windowId, type); + this.mainLayout.setState((state, props) => { return { layoutRevision: state.layoutRevision + 1 } }); + } + + doAction(action: Action): Node | undefined { + if (this.props.onAction !== undefined) { + const outcome = this.props.onAction(action); + if (outcome !== undefined) { + return this.props.model.doAction(outcome); + } + return undefined; } else { - event.dataTransfer!.setDragImage( - (node as TabNode).getTabStamp()!, - x, - y, - ); + return this.props.model.doAction(action); } - } - } - }; - - public setDragComponent( - event: DragEvent, - component: React.ReactNode, - x: number, - y: number, - ) { - const dragElement = ( -
{ + if (this.selfRef.current) { + const rect = Rect.fromDomRect(this.selfRef.current.getBoundingClientRect()); + if (!rect.equals(this.state.rect) && rect.width !== 0 && rect.height !== 0) { + // console.log("updateRect", rect.floor()); + this.setState({ rect }); + if (this.windowId !== Model.MAIN_WINDOW_ID) { + this.redrawInternal("rect updated"); + } + } } - > - {component} -
- ); - - const tempDiv = this.currentDocument!.createElement('div'); - tempDiv.setAttribute('data-layout-path', '/drag-rectangle'); - tempDiv.style.position = 'absolute'; - tempDiv.style.left = '-10000px'; - tempDiv.style.top = '-10000px'; - this.currentDocument!.body.appendChild(tempDiv); - createRoot(tempDiv).render(dragElement); - - event.dataTransfer!.setDragImage(tempDiv, x, y); - setTimeout(() => { - this.currentDocument!.body.removeChild(tempDiv!); - }, 0); - } - - setDraggingOverWindow(overWindow: boolean) { - // console.log("setDraggingOverWindow", overWindow); - if (this.isDraggingOverWindow !== overWindow) { - if (this.outlineDiv) { - this.outlineDiv!.style.visibility = overWindow ? 'hidden' : 'visible'; - } - - if (overWindow) { - this.setState({ showEdges: false }); - } else { - // add edge indicators - if (this.props.model.getMaximizedTabset(this.windowId) === undefined) { - this.setState({ showEdges: this.props.model.isEnableEdgeDock() }); + }; + + getBoundingClientRect(div: HTMLElement): Rect { + const layoutRect = this.getDomRect(); + if (layoutRect) { + return Rect.getBoundingClientRect(div).relativeTo(layoutRect); } - } + return Rect.empty(); + } - this.isDraggingOverWindow = overWindow; + getMoveableContainer() { + return this.moveablesRef.current; } - } - onDragEnterRaw = (event: React.DragEvent) => { - this.dragEnterCount++; - if (this.dragEnterCount === 1) { - this.onDragEnter(event); + getMoveableElement(id: string) { + let moveableElement = this.moveableElementMap.get(id); + if (moveableElement === undefined) { + moveableElement = document.createElement("div"); + this.moveablesRef.current!.appendChild(moveableElement); + moveableElement.className = CLASSES.FLEXLAYOUT__TAB_MOVEABLE; + this.moveableElementMap.set(id, moveableElement); + } + return moveableElement; } - }; - onDragLeaveRaw = (event: React.DragEvent) => { - this.dragEnterCount--; - if (this.dragEnterCount === 0) { - this.onDragLeave(event); + getMainLayout() { + return this.mainLayout; } - }; - clearDragMain() { - // console.log("clear drag main"); - LayoutInternal.dragState = undefined; - if (this.windowId === Model.MAIN_WINDOW_ID) { - this.isDraggingOverWindow = false; + getClassName = (defaultClassName: string) => { + if (this.props.classNameMapper === undefined) { + return defaultClassName; + } else { + return this.props.classNameMapper(defaultClassName); + } + }; + + getCurrentDocument() { + return this.currentDocument; } - for (const [, layoutWindow] of this.props.model.getwindowsMap()) { - // console.log(layoutWindow); - layoutWindow.layout!.clearDragLocal(); + + getDomRect() { + // must get on demand, since page may have scrolled + if (this.selfRef.current) { + return Rect.fromDomRect(this.selfRef.current.getBoundingClientRect()); + } else { + return Rect.empty(); + } } - } - clearDragLocal() { - // console.log("clear drag local", this.windowId); - this.setState({ showEdges: false }); - this.showOverlay(false); - this.dragEnterCount = 0; - this.dragging = false; - if (this.outlineDiv) { - this.selfRef.current!.removeChild(this.outlineDiv); - this.outlineDiv = undefined; + getWindowId() { + return this.windowId; } - } - onDragEnter = (event: React.DragEvent) => { - // console.log("onDragEnter", this.windowId, this.dragEnterCount); + getRootDiv() { + return this.selfRef.current; + } - if (!LayoutInternal.dragState && this.props.onExternalDrag) { - // not internal dragging - const externalDrag = this.props.onExternalDrag!(event); - if (externalDrag) { - const tempNode = TabNode.fromJson( - externalDrag.json, - this.props.model, - false, - ); - LayoutInternal.dragState = new DragState( - this.mainLayout, - DragSource.External, - tempNode, - externalDrag.json, - externalDrag.onDrop, + getMainElement() { + return this.mainRef.current; + } + + getFactory() { + return this.props.factory; + } + + isSupportsPopout() { + return this.supportsPopout; + } + + isRealtimeResize() { + return this.props.realtimeResize ?? false; + } + + getPopoutURL() { + return this.popoutURL; + } + + setEditingTab(tabNode?: TabNode) { + this.setState({ editingTab: tabNode }); + } + + getEditingTab() { + return this.state.editingTab; + } + + getModel() { + return this.props.model; + } + + onCloseWindow = (windowLayout: LayoutWindow) => { + this.doAction(Actions.closeWindow(windowLayout.windowId)); + }; + + onSetWindow = (windowLayout: LayoutWindow, window: Window) => { + }; + + getScreenRect(inRect: Rect) { + const rect = inRect.clone(); + const layoutRect = this.getDomRect(); + // Note: outerHeight can be less than innerHeight when window is zoomed, so cannot use + // const navHeight = Math.min(65, this.currentWindow!.outerHeight - this.currentWindow!.innerHeight); + // const navWidth = Math.min(65, this.currentWindow!.outerWidth - this.currentWindow!.innerWidth); + const navHeight = 60; + const navWidth = 2; + // console.log(rect.y, this.currentWindow!.screenX,layoutRect.y); + rect.x = this.currentWindow!.screenX + this.currentWindow!.scrollX + navWidth / 2 + layoutRect.x + rect.x; + rect.y = this.currentWindow!.screenY + this.currentWindow!.scrollY + (navHeight - navWidth / 2) + layoutRect.y + rect.y; + rect.height += navHeight; + rect.width += navWidth; + return rect; + } + + addTabToTabSet(tabsetId: string, json: IJsonTabNode): TabNode | undefined { + const tabsetNode = this.props.model.getNodeById(tabsetId); + if (tabsetNode !== undefined) { + const node = this.doAction(Actions.addNode(json, tabsetId, DockLocation.CENTER, -1)); + return node as TabNode; + } + return undefined; + } + + addTabToActiveTabSet(json: IJsonTabNode): TabNode | undefined { + const tabsetNode = this.props.model.getActiveTabset(this.windowId); + if (tabsetNode !== undefined) { + const node = this.doAction(Actions.addNode(json, tabsetNode.getId(), DockLocation.CENTER, -1)); + return node as TabNode; + } + return undefined; + } + + showControlInPortal = (control: React.ReactNode, element: HTMLElement) => { + const portal = createPortal(control, element) as React.ReactPortal; + this.setState({ portal }); + }; + + hideControlInPortal = () => { + this.setState({ portal: undefined }); + }; + + getIcons = () => { + return this.icons; + }; + + maximize(tabsetNode: TabSetNode) { + this.doAction(Actions.maximizeToggle(tabsetNode.getId(), this.getWindowId())); + } + + customizeTab( + tabNode: TabNode, + renderValues: ITabRenderValues, + ) { + if (this.props.onRenderTab) { + this.props.onRenderTab(tabNode, renderValues); + } + } + + customizeTabSet( + tabSetNode: TabSetNode | BorderNode, + renderValues: ITabSetRenderValues, + ) { + if (this.props.onRenderTabSet) { + this.props.onRenderTabSet(tabSetNode, renderValues); + } + } + + i18nName(id: I18nLabel, param?: string) { + let message; + if (this.props.i18nMapper) { + message = this.props.i18nMapper(id, param); + } + if (message === undefined) { + message = id + (param === undefined ? "" : param); + } + return message; + } + + getShowOverflowMenu() { + return this.props.onShowOverflowMenu; + } + + getTabSetPlaceHolderCallback() { + return this.props.onTabSetPlaceHolder; + } + + showContextMenu(node: TabNode | TabSetNode | BorderNode, event: React.MouseEvent) { + if (this.props.onContextMenu) { + this.props.onContextMenu(node, event); + } + } + + auxMouseClick(node: TabNode | TabSetNode | BorderNode, event: React.MouseEvent) { + if (this.props.onAuxMouseClick) { + this.props.onAuxMouseClick(node, event); + } + } + + public showOverlay(show: boolean) { + this.setState({ showOverlay: show }); + enablePointerOnIFrames(!show, this.currentDocument!); + } + + + + // *************************** Start Drag Drop ************************************* + + addTabWithDragAndDrop(event: DragEvent, json: IJsonTabNode, onDrop?: (node?: Node, event?: React.DragEvent) => void) { + const tempNode = TabNode.fromJson(json, this.props.model, false); + LayoutInternal.dragState = new DragState(this.mainLayout, DragSource.Add, tempNode, json, onDrop); + } + + moveTabWithDragAndDrop(event: DragEvent, node: (TabNode | TabSetNode)) { + this.setDragNode(event, node); + } + + public setDragNode = (event: DragEvent, node: Node & IDraggable) => { + LayoutInternal.dragState = new DragState(this.mainLayout, DragSource.Internal, node, undefined, undefined); + // Note: can only set (very) limited types on android! so cannot set json + // Note: must set text/plain for android to allow drag, + // so just set a simple message indicating its a flexlayout drag (this is not used anywhere else) + event.dataTransfer!.setData('text/plain', "--flexlayout--"); + event.dataTransfer!.effectAllowed = "copyMove"; + event.dataTransfer!.dropEffect = "move"; + + this.dragEnterCount = 0; + + if (node instanceof TabSetNode) { + let rendered = false; + let content = this.i18nName(I18nLabel.Move_Tabset); + if (node.getChildren().length > 0) { + content = this.i18nName(I18nLabel.Move_Tabs).replace("?", String(node.getChildren().length)); + } + if (this.props.onRenderDragRect) { + const dragComponent = this.props.onRenderDragRect(content, node, undefined); + if (dragComponent) { + this.setDragComponent(event, dragComponent, 10, 10); + rendered = true; + } + } + if (!rendered) { + this.setDragComponent(event, content, 10, 10); + } + } else { + const element = event.target as HTMLElement; + const rect = element.getBoundingClientRect(); + const offsetX = event.clientX - rect.left; + const offsetY = event.clientY - rect.top; + const parentNode = node?.getParent(); + const isInVerticalBorder = parentNode instanceof BorderNode && (parentNode as BorderNode).getOrientation() === Orientation.HORZ; + const x = isInVerticalBorder ? 10 : offsetX; + const y = isInVerticalBorder ? 10 : offsetY; + + let rendered = false; + if (this.props.onRenderDragRect) { + const content = ; + const dragComponent = this.props.onRenderDragRect(content, node, undefined); + if (dragComponent) { + this.setDragComponent(event, dragComponent, x, y); + rendered = true; + } + } + if (!rendered) { + if (isSafari()) { // safari doesnt render the offscreen tabstamps + this.setDragComponent(event, , x, y); + } else { + event.dataTransfer!.setDragImage((node as TabNode).getTabStamp()!, x, y); + } + } + } + }; + + + + public setDragComponent(event: DragEvent, component: React.ReactNode, x: number, y: number) { + const dragElement = ( +
+ {component} +
); - } - } - - if (LayoutInternal.dragState) { - if ( - this.windowId !== Model.MAIN_WINDOW_ID && - LayoutInternal.dragState.mainLayout === this.mainLayout - ) { - LayoutInternal.dragState.mainLayout.setDraggingOverWindow(true); - } - - if (LayoutInternal.dragState.mainLayout !== this.mainLayout) { - return; // drag not by this layout or its popouts - } - - event.preventDefault(); - - this.dropInfo = undefined; - const rootdiv = this.selfRef.current; - this.outlineDiv = this.currentDocument!.createElement('div'); - this.outlineDiv.className = this.getClassName( - CLASSES.FLEXLAYOUT__OUTLINE_RECT, - ); - this.outlineDiv.style.visibility = 'hidden'; - const speed = this.props.model.getAttribute('tabDragSpeed') as number; - this.outlineDiv.style.transition = `top ${speed}s, left ${speed}s, width ${speed}s, height ${speed}s`; - - rootdiv!.appendChild(this.outlineDiv); - - this.dragging = true; - this.showOverlay(true); - // add edge indicators - if ( - !this.isDraggingOverWindow && - this.props.model.getMaximizedTabset(this.windowId) === undefined - ) { - this.setState({ showEdges: this.props.model.isEnableEdgeDock() }); - } - - const clientRect = this.selfRef.current!.getBoundingClientRect()!; - const r = new Rect( - event.clientX - clientRect.left, - event.clientY - clientRect.top, - 1, - 1, - ); - r.positionElement(this.outlineDiv); - } - }; - - onDragOver = (event: React.DragEvent) => { - if (this.dragging && !this.isDraggingOverWindow) { - // console.log("onDragOver"); - - event.preventDefault(); - const clientRect = this.selfRef.current?.getBoundingClientRect(); - const pos = { - x: event.clientX - (clientRect?.left ?? 0), - y: event.clientY - (clientRect?.top ?? 0), - }; - - this.checkForBorderToShow(pos.x, pos.y); - - const dropInfo = this.props.model.findDropTargetNode( - this.windowId, - LayoutInternal.dragState!.dragNode!, - pos.x, - pos.y, - ); - if (dropInfo) { - this.dropInfo = dropInfo; + + const tempDiv = this.currentDocument!.createElement('div'); + tempDiv.setAttribute("data-layout-path", "/drag-rectangle"); + tempDiv.style.position = "absolute"; + tempDiv.style.left = "-10000px"; + tempDiv.style.top = "-10000px"; + this.currentDocument!.body.appendChild(tempDiv); + createRoot(tempDiv).render(dragElement); + + event.dataTransfer!.setDragImage(tempDiv, x, y); + setTimeout(() => { + this.currentDocument!.body.removeChild(tempDiv!); + }, 0); + } + + setDraggingOverWindow(overWindow: boolean) { + // console.log("setDraggingOverWindow", overWindow); + if (this.isDraggingOverWindow !== overWindow) { + if (this.outlineDiv) { + this.outlineDiv!.style.visibility = overWindow ? "hidden" : "visible"; + } + + if (overWindow) { + this.setState({ showEdges: false }); + } else { + // add edge indicators + if (this.props.model.getMaximizedTabset(this.windowId) === undefined) { + this.setState({ showEdges: this.props.model.isEnableEdgeDock() }); + } + } + + this.isDraggingOverWindow = overWindow; + } + } + + onDragEnterRaw = (event: React.DragEvent) => { + this.dragEnterCount++; + if (this.dragEnterCount === 1) { + this.onDragEnter(event); + } + } + + onDragLeaveRaw = (event: React.DragEvent) => { + this.dragEnterCount--; + if (this.dragEnterCount === 0) { + this.onDragLeave(event); + } + } + + clearDragMain() { + // console.log("clear drag main"); + LayoutInternal.dragState = undefined; + if (this.windowId === Model.MAIN_WINDOW_ID) { + this.isDraggingOverWindow = false; + } + for (const [, layoutWindow] of this.props.model.getwindowsMap()) { + // console.log(layoutWindow); + layoutWindow.layout!.clearDragLocal(); + } + } + + clearDragLocal() { + // console.log("clear drag local", this.windowId); + this.setState({ showEdges: false }); + this.showOverlay(false); + this.dragEnterCount = 0; + this.dragging = false; if (this.outlineDiv) { - this.outlineDiv.className = this.getClassName(dropInfo.className); - dropInfo.rect.positionElement(this.outlineDiv); - this.outlineDiv.style.visibility = 'visible'; + this.selfRef.current!.removeChild(this.outlineDiv); + this.outlineDiv = undefined; + } + } + + onDragEnter = (event: React.DragEvent) => { + // console.log("onDragEnter", this.windowId, this.dragEnterCount); + + if (!LayoutInternal.dragState && this.props.onExternalDrag) { // not internal dragging + const externalDrag = this.props.onExternalDrag!(event); + if (externalDrag) { + const tempNode = TabNode.fromJson(externalDrag.json, this.props.model, false); + LayoutInternal.dragState = new DragState(this.mainLayout, DragSource.External, tempNode, externalDrag.json, externalDrag.onDrop); + } + } + + if (LayoutInternal.dragState) { + if (this.windowId !== Model.MAIN_WINDOW_ID && LayoutInternal.dragState.mainLayout === this.mainLayout) { + LayoutInternal.dragState.mainLayout.setDraggingOverWindow(true); + } + + if (LayoutInternal.dragState.mainLayout !== this.mainLayout) { + return; // drag not by this layout or its popouts + } + + event.preventDefault(); + + this.dropInfo = undefined; + const rootdiv = this.selfRef.current; + this.outlineDiv = this.currentDocument!.createElement("div"); + this.outlineDiv.className = this.getClassName(CLASSES.FLEXLAYOUT__OUTLINE_RECT); + this.outlineDiv.style.visibility = "hidden"; + const speed = this.props.model.getAttribute("tabDragSpeed") as number; + this.outlineDiv.style.transition = `top ${speed}s, left ${speed}s, width ${speed}s, height ${speed}s`; + + rootdiv!.appendChild(this.outlineDiv); + + this.dragging = true; + this.showOverlay(true); + // add edge indicators + if (!this.isDraggingOverWindow && this.props.model.getMaximizedTabset(this.windowId) === undefined) { + this.setState({ showEdges: this.props.model.isEnableEdgeDock() }); + } + + const clientRect = this.selfRef.current!.getBoundingClientRect()!; + const r = new Rect( + event.clientX - (clientRect.left), + event.clientY - (clientRect.top), + 1, 1 + ); + r.positionElement(this.outlineDiv); + } + } + + onDragOver = (event: React.DragEvent) => { + if (this.dragging && !this.isDraggingOverWindow) { + // console.log("onDragOver"); + + event.preventDefault(); + const clientRect = this.selfRef.current?.getBoundingClientRect(); + const pos = { + x: event.clientX - (clientRect?.left ?? 0), + y: event.clientY - (clientRect?.top ?? 0), + }; + + this.checkForBorderToShow(pos.x, pos.y); + + const dropInfo = this.props.model.findDropTargetNode(this.windowId, LayoutInternal.dragState!.dragNode!, pos.x, pos.y); + if (dropInfo) { + this.dropInfo = dropInfo; + if (this.outlineDiv) { + this.outlineDiv.className = this.getClassName(dropInfo.className); + dropInfo.rect.positionElement(this.outlineDiv); + this.outlineDiv.style.visibility = "visible"; + } + } } - } - } - }; - - onDragLeave = (event: React.DragEvent) => { - // console.log("onDragLeave", this.windowId, this.dragging); - if (this.dragging) { - if (this.windowId !== Model.MAIN_WINDOW_ID) { - LayoutInternal.dragState!.mainLayout.setDraggingOverWindow(false); - } - - this.clearDragLocal(); - } - }; - - onDrop = (event: React.DragEvent) => { - // console.log("ondrop", this.windowId, this.dragging, Layout.dragState); - - if (this.dragging) { - event.preventDefault(); - - const dragState = LayoutInternal.dragState!; - if (this.dropInfo) { - if (dragState.dragJson !== undefined) { - const newNode = this.doAction( - Actions.addNode( - dragState.dragJson, - this.dropInfo.node.getId(), - this.dropInfo.location, - this.dropInfo.index, - ), - ); - - if (dragState.fnNewNodeDropped !== undefined) { - dragState.fnNewNodeDropped(newNode, event); - } - } else if (dragState.dragNode !== undefined) { - this.doAction( - Actions.moveNode( - dragState.dragNode.getId(), - this.dropInfo.node.getId(), - this.dropInfo.location, - this.dropInfo.index, - ), - ); + } + + onDragLeave = (event: React.DragEvent) => { + // console.log("onDragLeave", this.windowId, this.dragging); + if (this.dragging) { + if (this.windowId !== Model.MAIN_WINDOW_ID) { + LayoutInternal.dragState!.mainLayout.setDraggingOverWindow(false); + } + + this.clearDragLocal(); } - } + } + + onDrop = (event: React.DragEvent) => { + // console.log("ondrop", this.windowId, this.dragging, Layout.dragState); + + if (this.dragging) { + event.preventDefault(); - this.mainLayout.clearDragMain(); + const dragState = LayoutInternal.dragState!; + if (this.dropInfo) { + if (dragState.dragJson !== undefined) { + const newNode = this.doAction(Actions.addNode(dragState.dragJson, this.dropInfo.node.getId(), this.dropInfo.location, this.dropInfo.index)); + + if (dragState.fnNewNodeDropped !== undefined) { + dragState.fnNewNodeDropped(newNode, event); + } + } else if (dragState.dragNode !== undefined) { + this.doAction(Actions.moveNode(dragState.dragNode.getId(), this.dropInfo.node.getId(), this.dropInfo.location, this.dropInfo.index)); + } + } + + this.mainLayout.clearDragMain(); + } + this.dragEnterCount = 0; // must set to zero here ref sublayouts } - this.dragEnterCount = 0; // must set to zero here ref sublayouts - }; - // *************************** End Drag Drop ************************************* + // *************************** End Drag Drop ************************************* } declare const __VERSION__: string; export const FlexLayoutVersion = __VERSION__; export type DragRectRenderCallback = ( - content: React.ReactNode | undefined, - node?: Node, - json?: IJsonTabNode, + content: React.ReactNode | undefined, + node?: Node, + json?: IJsonTabNode ) => React.ReactNode | undefined; export type NodeMouseEvent = ( - node: TabNode | TabSetNode | BorderNode, - event: React.MouseEvent, + node: TabNode | TabSetNode | BorderNode, + event: React.MouseEvent ) => void; export type ShowOverflowMenuCallback = ( - node: TabSetNode | BorderNode, - mouseEvent: React.MouseEvent, - items: { index: number; node: TabNode }[], - onSelect: (item: { index: number; node: TabNode }) => void, + node: TabSetNode | BorderNode, + mouseEvent: React.MouseEvent, + items: { index: number; node: TabNode }[], + onSelect: (item: { index: number; node: TabNode }) => void, ) => void; export type TabSetPlaceHolderCallback = (node: TabSetNode) => React.ReactNode; export interface ITabSetRenderValues { - /** a component to be placed before the tabs */ - leading: React.ReactNode; - /** components that will be added after the tabs */ - stickyButtons: React.ReactNode[]; - /** components that will be added at the end of the tabset */ - buttons: React.ReactNode[]; - /** position to insert overflow button within [...stickyButtons, ...buttons] - * if left undefined position will be after the sticky buttons (if any) - */ - overflowPosition: number | undefined; + /** a component to be placed before the tabs */ + leading: React.ReactNode; + /** components that will be added after the tabs */ + stickyButtons: React.ReactNode[]; + /** components that will be added at the end of the tabset */ + buttons: React.ReactNode[]; + /** position to insert overflow button within [...stickyButtons, ...buttons] + * if left undefined position will be after the sticky buttons (if any) + */ + overflowPosition: number | undefined; } export interface ITabRenderValues { - /** the icon or other leading component */ - leading: React.ReactNode; - /** the main tab text/component */ - content: React.ReactNode; - /** a set of react components to add to the tab after the content */ - buttons: React.ReactNode[]; + /** the icon or other leading component */ + leading: React.ReactNode; + /** the main tab text/component */ + content: React.ReactNode; + /** a set of react components to add to the tab after the content */ + buttons: React.ReactNode[]; } export interface IIcons { - close?: React.ReactNode | ((tabNode: TabNode) => React.ReactNode); - closeTabset?: React.ReactNode | ((tabSetNode: TabSetNode) => React.ReactNode); - popout?: React.ReactNode | ((tabNode: TabNode) => React.ReactNode); - maximize?: React.ReactNode | ((tabSetNode: TabSetNode) => React.ReactNode); - restore?: React.ReactNode | ((tabSetNode: TabSetNode) => React.ReactNode); - more?: - | React.ReactNode - | (( - tabSetNode: TabSetNode | BorderNode, - hiddenTabs: { node: TabNode; index: number }[], - ) => React.ReactNode); - edgeArrow?: React.ReactNode; - activeTabset?: - | React.ReactNode - | ((tabSetNode: TabSetNode) => React.ReactNode); - pin?: React.ReactNode | ((tabNode: TabNode) => React.ReactNode); - unpin?: React.ReactNode | ((tabNode: TabNode) => React.ReactNode); + close?: (React.ReactNode | ((tabNode: TabNode) => React.ReactNode)); + closeTabset?: (React.ReactNode | ((tabSetNode: TabSetNode) => React.ReactNode)); + popout?: (React.ReactNode | ((tabNode: TabNode) => React.ReactNode)); + maximize?: (React.ReactNode | ((tabSetNode: TabSetNode) => React.ReactNode)); + restore?: (React.ReactNode | ((tabSetNode: TabSetNode) => React.ReactNode)); + more?: (React.ReactNode | ((tabSetNode: (TabSetNode | BorderNode), hiddenTabs: { node: TabNode; index: number }[]) => React.ReactNode)); + edgeArrow?: React.ReactNode; + activeTabset?: (React.ReactNode | ((tabSetNode: TabSetNode) => React.ReactNode)); + pin?: (React.ReactNode | ((tabNode: TabNode) => React.ReactNode)); + unpin?: (React.ReactNode | ((tabNode: TabNode) => React.ReactNode)); } const defaultIcons = { - close: , - closeTabset: , - popout: , - maximize: , - restore: , - more: , - edgeArrow: , - activeTabset: , - pin: , - unpin: , + close: , + closeTabset: , + popout: , + maximize: , + restore: , + more: , + edgeArrow: , + activeTabset: , + pin: , + unpin: , }; enum DragSource { - Internal = 'internal', - External = 'external', - Add = 'add', + Internal = "internal", + External = "external", + Add = "add" } /** @internal */ @@ -1643,27 +1321,23 @@ const edgeRectWidth = 10; // global layout drag state class DragState { - public readonly mainLayout: LayoutInternal; - public readonly dragSource: DragSource; - public readonly dragNode: (Node & IDraggable) | undefined; - public readonly dragJson: IJsonTabNode | undefined; - public readonly fnNewNodeDropped: - | ((node?: Node, event?: React.DragEvent) => void) - | undefined; - - public constructor( - mainLayout: LayoutInternal, - dragSource: DragSource, - dragNode: (Node & IDraggable) | undefined, - dragJson: IJsonTabNode | undefined, - fnNewNodeDropped: - | ((node?: Node, event?: React.DragEvent) => void) - | undefined, - ) { - this.mainLayout = mainLayout; - this.dragSource = dragSource; - this.dragNode = dragNode; - this.dragJson = dragJson; - this.fnNewNodeDropped = fnNewNodeDropped; - } + public readonly mainLayout: LayoutInternal; + public readonly dragSource: DragSource; + public readonly dragNode: Node & IDraggable | undefined; + public readonly dragJson: IJsonTabNode | undefined; + public readonly fnNewNodeDropped: ((node?: Node, event?: React.DragEvent) => void) | undefined; + + public constructor( + mainLayout: LayoutInternal, + dragSource: DragSource, + dragNode: Node & IDraggable | undefined, + dragJson: IJsonTabNode | undefined, + fnNewNodeDropped: ((node?: Node, event?: React.DragEvent) => void) | undefined + ) { + this.mainLayout = mainLayout; + this.dragSource = dragSource; + this.dragNode = dragNode; + this.dragJson = dragJson; + this.fnNewNodeDropped = fnNewNodeDropped; + } } diff --git a/src/view/Overlay.tsx b/src/view/Overlay.tsx index 614e6b8a..65e7ea64 100644 --- a/src/view/Overlay.tsx +++ b/src/view/Overlay.tsx @@ -1,20 +1,21 @@ -import { LayoutInternal } from './Layout'; -import { CLASSES } from '../Types'; +import { LayoutInternal } from "./Layout"; +import { CLASSES } from "../Types"; /** @internal */ export interface IOverlayProps { - layout: LayoutInternal; - show: boolean; + layout: LayoutInternal; + show: boolean; } /** @internal */ export const Overlay = (props: IOverlayProps) => { - const { layout, show } = props; + const {layout, show} = props; - return ( -
- ); -}; + return ( +
+ ); +} \ No newline at end of file diff --git a/src/view/PopoutWindow.tsx b/src/view/PopoutWindow.tsx index 06d229a6..e7ef1c09 100644 --- a/src/view/PopoutWindow.tsx +++ b/src/view/PopoutWindow.tsx @@ -1,191 +1,152 @@ -import * as React from 'react'; -import { createPortal } from 'react-dom'; -import { CLASSES } from '../Types'; -import { LayoutInternal } from './Layout'; -import { LayoutWindow } from '../model/LayoutWindow'; +import * as React from "react"; +import { createPortal } from "react-dom"; +import { CLASSES } from "../Types"; +import { LayoutInternal } from "./Layout"; +import { LayoutWindow } from "../model/LayoutWindow"; /** @internal */ export interface IPopoutWindowProps { - title: string; - layout: LayoutInternal; - layoutWindow: LayoutWindow; - url: string; - onCloseWindow: (layoutWindow: LayoutWindow) => void; - onSetWindow: (layoutWindow: LayoutWindow, window: Window) => void; + title: string; + layout: LayoutInternal; + layoutWindow: LayoutWindow; + url: string; + onCloseWindow: (layoutWindow: LayoutWindow) => void; + onSetWindow: (layoutWindow: LayoutWindow, window: Window) => void; } /** @internal */ -export const PopoutWindow = ( - props: React.PropsWithChildren, -) => { - const { - title, - layout, - layoutWindow, - url, - onCloseWindow, - onSetWindow, - children, - } = props; - const popoutWindow = React.useRef(null); - const [content, setContent] = React.useState( - undefined, - ); - // map from main docs style -> this docs equivalent style - const styleMap = new Map(); - - React.useLayoutEffect(() => { - if (!popoutWindow.current) { - // only create window once, even in strict mode - const windowId = layoutWindow.windowId; - const rect = layoutWindow.rect; - - popoutWindow.current = window.open( - url, - windowId, - `left=${rect.x},top=${rect.y},width=${rect.width},height=${rect.height}`, - ); - - if (popoutWindow.current) { - layoutWindow.window = popoutWindow.current; - onSetWindow(layoutWindow, popoutWindow.current); - - // listen for parent unloading to remove all popouts - window.addEventListener('beforeunload', () => { - if (popoutWindow.current) { - const closedWindow = popoutWindow.current; - popoutWindow.current = null; // need to set to null before close, since this will trigger popup window before unload... - closedWindow.close(); - } - }); - - popoutWindow.current.addEventListener('load', () => { - if (popoutWindow.current) { - popoutWindow.current.focus(); - - // note: resizeto must be before moveto in chrome otherwise the window will end up at 0,0 - popoutWindow.current.resizeTo(rect.width, rect.height); - popoutWindow.current.moveTo(rect.x, rect.y); - - const popoutDocument = popoutWindow.current.document; - popoutDocument.title = title; - const popoutContent = popoutDocument.createElement('div'); - popoutContent.className = - CLASSES.FLEXLAYOUT__FLOATING_WINDOW_CONTENT; - popoutDocument.body.appendChild(popoutContent); - copyStyles(popoutDocument, styleMap).then(() => { - setContent(popoutContent); // re-render once link styles loaded - }); - - // listen for style mutations - const observer = new MutationObserver((mutationsList: any) => - handleStyleMutations(mutationsList, popoutDocument, styleMap), - ); - observer.observe(document.head, { childList: true }); - - // listen for popout unloading (needs to be after load for safari) - popoutWindow.current.addEventListener('beforeunload', () => { - if (popoutWindow.current) { +export const PopoutWindow = (props: React.PropsWithChildren) => { + const { title, layout, layoutWindow, url, onCloseWindow, onSetWindow, children } = props; const popoutWindow = React.useRef(null); + const [content, setContent] = React.useState(undefined); + // map from main docs style -> this docs equivalent style + const styleMap = new Map(); + + React.useLayoutEffect(() => { + if (!popoutWindow.current) { // only create window once, even in strict mode + const windowId = layoutWindow.windowId; + const rect = layoutWindow.rect; + + popoutWindow.current = window.open(url, windowId, `left=${rect.x},top=${rect.y},width=${rect.width},height=${rect.height}`); + + if (popoutWindow.current) { + layoutWindow.window = popoutWindow.current; + onSetWindow(layoutWindow, popoutWindow.current); + + // listen for parent unloading to remove all popouts + window.addEventListener("beforeunload", () => { + if (popoutWindow.current) { + const closedWindow = popoutWindow.current; + popoutWindow.current = null; // need to set to null before close, since this will trigger popup window before unload... + closedWindow.close(); + } + }); + + popoutWindow.current.addEventListener("load", () => { + if (popoutWindow.current) { + popoutWindow.current.focus(); + + // note: resizeto must be before moveto in chrome otherwise the window will end up at 0,0 + popoutWindow.current.resizeTo(rect.width, rect.height); + popoutWindow.current.moveTo(rect.x, rect.y); + + const popoutDocument = popoutWindow.current.document; + popoutDocument.title = title; + const popoutContent = popoutDocument.createElement("div"); + popoutContent.className = CLASSES.FLEXLAYOUT__FLOATING_WINDOW_CONTENT; + popoutDocument.body.appendChild(popoutContent); + copyStyles(popoutDocument, styleMap).then(() => { + setContent(popoutContent); // re-render once link styles loaded + }); + + // listen for style mutations + const observer = new MutationObserver((mutationsList: any) => handleStyleMutations(mutationsList, popoutDocument, styleMap)); + observer.observe(document.head, { childList: true }); + + // listen for popout unloading (needs to be after load for safari) + popoutWindow.current.addEventListener("beforeunload", () => { + if (popoutWindow.current) { + onCloseWindow(layoutWindow); // remove the layoutWindow in the model + popoutWindow.current = null; + observer.disconnect(); + } + }); + } + }); + } else { + console.warn(`Unable to open window ${url}`); onCloseWindow(layoutWindow); // remove the layoutWindow in the model + } + } + return () => { + // only close popoutWindow if windowId has been removed from the model (ie this was due to model change) + if (!layout.getModel().getwindowsMap().has(layoutWindow.windowId)) { + popoutWindow.current?.close(); popoutWindow.current = null; - observer.disconnect(); - } - }); - } - }); - } else { - console.warn(`Unable to open window ${url}`); - onCloseWindow(layoutWindow); // remove the layoutWindow in the model - } + } + } + }, []); + + if (content !== undefined) { + return createPortal(children, content!); + } else { + return null; } - return () => { - // only close popoutWindow if windowId has been removed from the model (ie this was due to model change) - if (!layout.getModel().getwindowsMap().has(layoutWindow.windowId)) { - popoutWindow.current?.close(); - popoutWindow.current = null; - } - }; - }, []); - - if (content !== undefined) { - return createPortal(children, content!); - } else { - return null; - } }; -function handleStyleMutations( - mutationsList: any, - popoutDocument: Document, - styleMap: Map, -) { - for (const mutation of mutationsList) { - if (mutation.type === 'childList') { - for (const addition of mutation.addedNodes) { - if ( - addition instanceof HTMLLinkElement || - addition instanceof HTMLStyleElement - ) { - copyStyle(popoutDocument, addition, styleMap); - } - } - for (const removal of mutation.removedNodes) { - if ( - removal instanceof HTMLLinkElement || - removal instanceof HTMLStyleElement - ) { - const popoutStyle = styleMap.get(removal); - if (popoutStyle) { - popoutDocument.head.removeChild(popoutStyle); - } +function handleStyleMutations(mutationsList: any, popoutDocument: Document, styleMap: Map) { + for (const mutation of mutationsList) { + if (mutation.type === 'childList') { + for (const addition of mutation.addedNodes) { + if (addition instanceof HTMLLinkElement || addition instanceof HTMLStyleElement) { + copyStyle(popoutDocument, addition, styleMap); + } + } + for (const removal of mutation.removedNodes) { + if (removal instanceof HTMLLinkElement || removal instanceof HTMLStyleElement) { + const popoutStyle = styleMap.get(removal); + if (popoutStyle) { + popoutDocument.head.removeChild(popoutStyle); + } + } + } } - } } - } -} +}; + + /** @internal */ -function copyStyles( - popoutDoc: Document, - styleMap: Map, -): Promise { - const promises: Promise[] = []; - const styleElements = document.querySelectorAll( - 'style, link[rel="stylesheet"]', - ) as NodeListOf; - for (const element of styleElements) { - copyStyle(popoutDoc, element, styleMap, promises); - } - return Promise.all(promises); +function copyStyles(popoutDoc: Document, styleMap: Map): Promise { + const promises: Promise[] = []; + const styleElements = document.querySelectorAll('style, link[rel="stylesheet"]') as NodeListOf + for (const element of styleElements) { + copyStyle(popoutDoc, element, styleMap, promises); + } + return Promise.all(promises); } /** @internal */ -function copyStyle( - popoutDoc: Document, - element: HTMLElement, - styleMap: Map, - promises?: Promise[], -) { - if (element instanceof HTMLLinkElement) { - // prefer links since they will keep paths to images etc - const linkElement = element.cloneNode(true) as HTMLLinkElement; - popoutDoc.head.appendChild(linkElement); - styleMap.set(element, linkElement); - - if (promises) { - promises.push( - new Promise((resolve) => { - linkElement.onload = () => resolve(true); - }), - ); - } - } else if (element instanceof HTMLStyleElement) { - try { - const styleElement = element.cloneNode(true) as HTMLStyleElement; - popoutDoc.head.appendChild(styleElement); - styleMap.set(element, styleElement); - } catch (e) { - // can throw an exception +function copyStyle(popoutDoc: Document, element: HTMLElement, styleMap: Map, promises?: Promise[]) { + if (element instanceof HTMLLinkElement) { + // prefer links since they will keep paths to images etc + const linkElement = element.cloneNode(true) as HTMLLinkElement; + popoutDoc.head.appendChild(linkElement); + styleMap.set(element, linkElement); + + if (promises) { + promises.push(new Promise((resolve) => { + linkElement.onload = () => resolve(true); + })); + } + } else if (element instanceof HTMLStyleElement) { + try { + const styleElement = element.cloneNode(true) as HTMLStyleElement; + popoutDoc.head.appendChild(styleElement); + styleMap.set(element, styleElement); + } catch (e) { + // can throw an exception + } } - } } + + diff --git a/src/view/PopupMenu.tsx b/src/view/PopupMenu.tsx index 6523285b..f6974f3f 100644 --- a/src/view/PopupMenu.tsx +++ b/src/view/PopupMenu.tsx @@ -1,164 +1,156 @@ -import * as React from 'react'; -import { TabNode } from '../model/TabNode'; -import { CLASSES } from '../Types'; -import { LayoutInternal } from './Layout'; -import { TabButtonStamp } from './TabButtonStamp'; -import { TabSetNode } from '../model/TabSetNode'; -import { BorderNode } from '../model/BorderNode'; -import { useEffect, useRef } from 'react'; +import * as React from "react"; +import { TabNode } from "../model/TabNode"; +import { CLASSES } from "../Types"; +import { LayoutInternal } from "./Layout"; +import { TabButtonStamp } from "./TabButtonStamp"; +import { TabSetNode } from "../model/TabSetNode"; +import { BorderNode } from "../model/BorderNode"; +import { useEffect, useRef } from "react"; /** @internal */ export function showPopup( - triggerElement: Element, - parentNode: TabSetNode | BorderNode, - items: { index: number; node: TabNode }[], - onSelect: (item: { index: number; node: TabNode }) => void, - layout: LayoutInternal, + triggerElement: Element, + parentNode: TabSetNode | BorderNode, + items: { index: number; node: TabNode }[], + onSelect: (item: { index: number; node: TabNode }) => void, + layout: LayoutInternal, ) { - const layoutDiv = layout.getRootDiv(); - const classNameMapper = layout.getClassName; - const currentDocument = triggerElement.ownerDocument; - const triggerRect = triggerElement.getBoundingClientRect(); - const layoutRect = - layoutDiv?.getBoundingClientRect() ?? new DOMRect(0, 0, 100, 100); - - const elm = currentDocument.createElement('div'); - elm.className = classNameMapper(CLASSES.FLEXLAYOUT__POPUP_MENU_CONTAINER); - if (triggerRect.left < layoutRect.left + layoutRect.width / 2) { - elm.style.left = triggerRect.left - layoutRect.left + 'px'; - } else { - elm.style.right = layoutRect.right - triggerRect.right + 'px'; - } - - if (triggerRect.top < layoutRect.top + layoutRect.height / 2) { - elm.style.top = triggerRect.top - layoutRect.top + 'px'; - } else { - elm.style.bottom = layoutRect.bottom - triggerRect.bottom + 'px'; - } - - layout.showOverlay(true); - - if (layoutDiv) { - layoutDiv.appendChild(elm); - } - - const onHide = () => { - layout.hideControlInPortal(); - layout.showOverlay(false); + const layoutDiv = layout.getRootDiv(); + const classNameMapper = layout.getClassName; + const currentDocument = triggerElement.ownerDocument; + const triggerRect = triggerElement.getBoundingClientRect(); + const layoutRect = layoutDiv?.getBoundingClientRect() ?? new DOMRect(0, 0, 100, 100); + + const elm = currentDocument.createElement("div"); + elm.className = classNameMapper(CLASSES.FLEXLAYOUT__POPUP_MENU_CONTAINER); + if (triggerRect.left < layoutRect.left + layoutRect.width / 2) { + elm.style.left = triggerRect.left - layoutRect.left + "px"; + } else { + elm.style.right = layoutRect.right - triggerRect.right + "px"; + } + + if (triggerRect.top < layoutRect.top + layoutRect.height / 2) { + elm.style.top = triggerRect.top - layoutRect.top + "px"; + } else { + elm.style.bottom = layoutRect.bottom - triggerRect.bottom + "px"; + } + + layout.showOverlay(true); + if (layoutDiv) { - layoutDiv.removeChild(elm); + layoutDiv.appendChild(elm); } - elm.removeEventListener('pointerdown', onElementPointerDown); - currentDocument.removeEventListener('pointerdown', onDocPointerDown); - }; - - const onElementPointerDown = (event: Event) => { - event.stopPropagation(); - }; - - const onDocPointerDown = (_event: Event) => { - onHide(); - }; - - elm.addEventListener('pointerdown', onElementPointerDown); - currentDocument.addEventListener('pointerdown', onDocPointerDown); - - layout.showControlInPortal( - , - elm, - ); + + const onHide = () => { + layout.hideControlInPortal(); + layout.showOverlay(false); + if (layoutDiv) { + layoutDiv.removeChild(elm); + } + elm.removeEventListener("pointerdown", onElementPointerDown); + currentDocument.removeEventListener("pointerdown", onDocPointerDown); + }; + + const onElementPointerDown = (event: Event) => { + event.stopPropagation(); + }; + + const onDocPointerDown = (_event: Event) => { + onHide(); + }; + + elm.addEventListener("pointerdown", onElementPointerDown); + currentDocument.addEventListener("pointerdown", onDocPointerDown); + + layout.showControlInPortal(, elm); } /** @internal */ interface IPopupMenuProps { - parentNode: TabSetNode | BorderNode; - items: { index: number; node: TabNode }[]; - currentDocument: Document; - onHide: () => void; - onSelect: (item: { index: number; node: TabNode }) => void; - classNameMapper: (defaultClassName: string) => string; - layout: LayoutInternal; + parentNode: TabSetNode | BorderNode; + items: { index: number; node: TabNode }[]; + currentDocument: Document; + onHide: () => void; + onSelect: (item: { index: number; node: TabNode }) => void; + classNameMapper: (defaultClassName: string) => string; + layout: LayoutInternal; } /** @internal */ const PopupMenu = (props: IPopupMenuProps) => { - const { parentNode, items, onHide, onSelect, classNameMapper, layout } = - props; - const divRef = useRef(null); - - useEffect(() => { - // Set focus when the component mounts - if (divRef.current) { - divRef.current.focus(); - } - }, []); - - const onItemClick = ( - item: { index: number; node: TabNode }, - event: React.MouseEvent, - ) => { - onSelect(item); - onHide(); - event.stopPropagation(); - }; - - const onDragStart = (event: React.DragEvent, node: TabNode) => { - event.stopPropagation(); // prevent starting a tabset drag as well - layout.setDragNode(event.nativeEvent, node as TabNode); - setTimeout(() => { - onHide(); - }, 0); - }; - - const onDragEnd = (event: React.DragEvent) => { - layout.clearDragMain(); - }; - - const handleKeyDown = (event: React.KeyboardEvent) => { - if (event.key === 'Escape') { - onHide(); + const { parentNode, items, onHide, onSelect, classNameMapper, layout } = props; + const divRef = useRef(null); + + useEffect(() => { + // Set focus when the component mounts + if (divRef.current) { + divRef.current.focus(); + } + }, []); + + const onItemClick = (item: { index: number; node: TabNode }, event: React.MouseEvent) => { + onSelect(item); + onHide(); + event.stopPropagation(); + }; + + const onDragStart = (event: React.DragEvent, node: TabNode) => { + event.stopPropagation(); // prevent starting a tabset drag as well + layout.setDragNode(event.nativeEvent, node as TabNode); + setTimeout(() => { + onHide(); + }, 0); + + }; + + const onDragEnd = (event: React.DragEvent) => { + layout.clearDragMain(); + }; + + const handleKeyDown = (event: React.KeyboardEvent) => { + if (event.key === "Escape") { + onHide(); + } + }; + + const itemElements = items.map((item, i) => { + let classes = classNameMapper(CLASSES.FLEXLAYOUT__POPUP_MENU_ITEM); + if (parentNode.getSelected() === item.index) { + classes += " " + classNameMapper(CLASSES.FLEXLAYOUT__POPUP_MENU_ITEM__SELECTED); + } + return ( +
onItemClick(item, event)} + draggable={true} + onDragStart={(e) => onDragStart(e, item.node)} + onDragEnd={onDragEnd} + title={item.node.getHelpText()} > + +
+ ) } - }; + ); - const itemElements = items.map((item, i) => { - let classes = classNameMapper(CLASSES.FLEXLAYOUT__POPUP_MENU_ITEM); - if (parentNode.getSelected() === item.index) { - classes += - ' ' + classNameMapper(CLASSES.FLEXLAYOUT__POPUP_MENU_ITEM__SELECTED); - } return ( -
onItemClick(item, event)} - draggable={true} - onDragStart={(e) => onDragStart(e, item.node)} - onDragEnd={onDragEnd} - title={item.node.getHelpText()} - > - -
- ); - }); - - return ( -
- {itemElements} -
- ); +
+ {itemElements} +
); }; diff --git a/src/view/Row.tsx b/src/view/Row.tsx index 6f352bae..7615ad5c 100644 --- a/src/view/Row.tsx +++ b/src/view/Row.tsx @@ -1,74 +1,68 @@ -import * as React from 'react'; -import { RowNode } from '../model/RowNode'; -import { TabSetNode } from '../model/TabSetNode'; -import { CLASSES } from '../Types'; -import { LayoutInternal } from './Layout'; -import { TabSet } from './TabSet'; -import { Splitter } from './Splitter'; -import { Orientation } from '../Orientation'; +import * as React from "react"; +import { RowNode } from "../model/RowNode"; +import { TabSetNode } from "../model/TabSetNode"; +import { CLASSES } from "../Types"; +import { LayoutInternal } from "./Layout"; +import { TabSet } from "./TabSet"; +import { Splitter } from "./Splitter"; +import { Orientation } from "../Orientation"; /** @internal */ export interface IRowProps { - layout: LayoutInternal; - node: RowNode; + layout: LayoutInternal; + node: RowNode; } /** @internal */ export const Row = (props: IRowProps) => { - const { layout, node } = props; - const selfRef = React.useRef(null); + const { layout, node } = props; + const selfRef = React.useRef(null); - const horizontal = node.getOrientation() === Orientation.HORZ; + const horizontal = node.getOrientation() === Orientation.HORZ; - React.useLayoutEffect(() => { - node.setRect(layout.getBoundingClientRect(selfRef.current!)); - }); + React.useLayoutEffect(() => { + node.setRect(layout.getBoundingClientRect(selfRef.current!)); + }); - const items: React.ReactNode[] = []; + const items: React.ReactNode[] = []; - let i = 0; + let i = 0; - for (const child of node.getChildren()) { - if (i > 0) { - items.push( - , - ); + for (const child of node.getChildren()) { + if (i > 0) { + items.push() + } + if (child instanceof RowNode) { + items.push(); + } else if (child instanceof TabSetNode) { + items.push(); + } + i++; } - if (child instanceof RowNode) { - items.push(); - } else if (child instanceof TabSetNode) { - items.push(); - } - i++; - } - const style: Record = { - flexGrow: Math.max(1, node.getWeight() * 1000), // NOTE: flex-grow cannot have values < 1 otherwise will not fill parent, need to normalize - minWidth: node.getMinWidth(), - minHeight: node.getMinHeight(), - maxWidth: node.getMaxWidth(), - maxHeight: node.getMaxHeight(), - }; + const style: Record = { + flexGrow: Math.max(1, node.getWeight()*1000), // NOTE: flex-grow cannot have values < 1 otherwise will not fill parent, need to normalize + minWidth: node.getMinWidth(), + minHeight: node.getMinHeight(), + maxWidth: node.getMaxWidth(), + maxHeight: node.getMaxHeight(), + }; - if (horizontal) { - style.flexDirection = 'row'; - } else { - style.flexDirection = 'column'; - } + if (horizontal) { + style.flexDirection = "row"; + } else { + style.flexDirection = "column"; + } - return ( -
- {items} -
- ); + return ( +
+ {items} +
+ ); }; + + diff --git a/src/view/SizeTracker.tsx b/src/view/SizeTracker.tsx index 5957f0e3..2a9dc925 100644 --- a/src/view/SizeTracker.tsx +++ b/src/view/SizeTracker.tsx @@ -1,39 +1,35 @@ -import * as React from 'react'; -import { Rect } from '../Rect'; -import { ErrorBoundary } from './ErrorBoundary'; -import { I18nLabel } from '../I18nLabel'; -import { LayoutInternal } from './Layout'; -import { TabNode } from '../model/TabNode'; +import * as React from "react"; +import { Rect } from "../Rect"; +import { ErrorBoundary } from "./ErrorBoundary"; +import { I18nLabel } from "../I18nLabel"; +import { LayoutInternal } from "./Layout"; +import { TabNode } from "../model/TabNode"; export interface ISizeTrackerProps { - layout: LayoutInternal; - node: TabNode; - rect: Rect; - visible: boolean; - forceRevision: number; - tabsRevision: number; + layout: LayoutInternal, + node: TabNode, + rect: Rect; + visible: boolean; + forceRevision: number; + tabsRevision: number; } export const SizeTracker = React.memo(({ layout, node }: ISizeTrackerProps) => { - return ( - - {layout.props.factory(node)} - - ); + return ( + + {layout.props.factory(node)} + ); }, arePropsEqual); // only re-render if visible && (size changed or forceRevision changed or tabsRevision changed) -function arePropsEqual( - prevProps: ISizeTrackerProps, - nextProps: ISizeTrackerProps, -) { - const reRender = - nextProps.visible && - (!prevProps.rect.equalSize(nextProps.rect) || - prevProps.forceRevision !== nextProps.forceRevision || - prevProps.tabsRevision !== nextProps.tabsRevision); - return !reRender; +function arePropsEqual(prevProps: ISizeTrackerProps, nextProps: ISizeTrackerProps) { + const reRender = nextProps.visible && + (!prevProps.rect.equalSize(nextProps.rect) || + prevProps.forceRevision !== nextProps.forceRevision || + prevProps.tabsRevision !== nextProps.tabsRevision + ); + return !reRender; } + diff --git a/src/view/Splitter.tsx b/src/view/Splitter.tsx index a5bb6d7c..2e002e08 100755 --- a/src/view/Splitter.tsx +++ b/src/view/Splitter.tsx @@ -1,302 +1,262 @@ -import * as React from 'react'; -import { Actions } from '../model/Actions'; -import { BorderNode } from '../model/BorderNode'; -import { RowNode } from '../model/RowNode'; -import { Orientation } from '../Orientation'; -import { CLASSES } from '../Types'; -import { LayoutInternal } from './Layout'; -import { enablePointerOnIFrames, isDesktop, startDrag } from './Utils'; -import { Rect } from '../Rect'; +import * as React from "react"; +import { Actions } from "../model/Actions"; +import { BorderNode } from "../model/BorderNode"; +import { RowNode } from "../model/RowNode"; +import { Orientation } from "../Orientation"; +import { CLASSES } from "../Types"; +import { LayoutInternal } from "./Layout"; +import { enablePointerOnIFrames, isDesktop, startDrag } from "./Utils"; +import { Rect } from "../Rect"; /** @internal */ export interface ISplitterProps { - layout: LayoutInternal; - node: RowNode | BorderNode; - index: number; - horizontal: boolean; + layout: LayoutInternal; + node: RowNode | BorderNode; + index: number; + horizontal: boolean; } /** @internal */ -export let splitterDragging: boolean = false; // used in tabset & borderTab +export let splitterDragging:boolean = false; // used in tabset & borderTab /** @internal */ export const Splitter = (props: ISplitterProps) => { - const { layout, node, index, horizontal } = props; - - const [dragging, setDragging] = React.useState(false); - const selfRef = React.useRef(null); - const extendedRef = React.useRef(null); - const pBounds = React.useRef([]); - const outlineDiv = React.useRef(undefined); - const handleDiv = React.useRef(undefined); - const dragStartX = React.useRef(0); - const dragStartY = React.useRef(0); - const initalSizes = React.useRef<{ - initialSizes: number[]; - sum: number; - startPosition: number; - }>({ initialSizes: [], sum: 0, startPosition: 0 }); - // const throttleTimer = React.useRef(undefined); - - const size = node.getModel().getSplitterSize(); - let extra = node.getModel().getSplitterExtra(); - - if (!isDesktop()) { - // make hit test area on mobile at least 20px - extra = Math.max(20, extra + size) - size; - } - - React.useEffect(() => { - // Android fix: must have passive touchstart handler to prevent default handling - selfRef.current?.addEventListener('touchstart', onTouchStart, { - passive: false, - }); - extendedRef.current?.addEventListener('touchstart', onTouchStart, { - passive: false, - }); - return () => { - selfRef.current?.removeEventListener('touchstart', onTouchStart); - extendedRef.current?.removeEventListener('touchstart', onTouchStart); - }; - }, []); + const { layout, node, index, horizontal } = props; + + const [dragging, setDragging] = React.useState(false); + const selfRef = React.useRef(null); + const extendedRef = React.useRef(null); + const pBounds = React.useRef([]); + const outlineDiv = React.useRef(undefined); + const handleDiv = React.useRef(undefined); + const dragStartX = React.useRef(0); + const dragStartY = React.useRef(0); + const initalSizes = React.useRef<{ initialSizes: number[], sum: number, startPosition: number }>({ initialSizes: [], sum: 0, startPosition: 0 }) + // const throttleTimer = React.useRef(undefined); + + const size = node.getModel().getSplitterSize(); + let extra = node.getModel().getSplitterExtra(); + + if (!isDesktop()) { + // make hit test area on mobile at least 20px + extra = Math.max(20, extra + size) - size; + } - const onTouchStart = (event: TouchEvent) => { - event.preventDefault(); - event.stopImmediatePropagation(); - }; + React.useEffect(() => { + // Android fix: must have passive touchstart handler to prevent default handling + selfRef.current?.addEventListener("touchstart", onTouchStart, { passive: false }); + extendedRef.current?.addEventListener("touchstart", onTouchStart, { passive: false }); + return () => { + selfRef.current?.removeEventListener("touchstart", onTouchStart); + extendedRef.current?.removeEventListener("touchstart", onTouchStart); + } + }, []); - const onPointerDown = (event: React.PointerEvent) => { - event.stopPropagation(); - if (node instanceof RowNode) { - initalSizes.current = node.getSplitterInitials(index); + const onTouchStart = (event: TouchEvent) => { + event.preventDefault(); + event.stopImmediatePropagation(); } - enablePointerOnIFrames(false, layout.getCurrentDocument()!); - startDrag( - event.currentTarget.ownerDocument, - event, - onDragMove, - onDragEnd, - onDragCancel, - ); - - pBounds.current = node.getSplitterBounds(index, true); - const rootdiv = layout.getRootDiv(); - outlineDiv.current = layout.getCurrentDocument()!.createElement('div'); - outlineDiv.current.style.flexDirection = horizontal ? 'row' : 'column'; - outlineDiv.current.className = layout.getClassName( - CLASSES.FLEXLAYOUT__SPLITTER_DRAG, - ); - outlineDiv.current.style.cursor = - node.getOrientation() === Orientation.VERT ? 'ns-resize' : 'ew-resize'; - - if (node.getModel().isSplitterEnableHandle()) { - handleDiv.current = layout.getCurrentDocument()!.createElement('div'); - handleDiv.current.className = - cm(CLASSES.FLEXLAYOUT__SPLITTER_HANDLE) + - ' ' + - (horizontal - ? cm(CLASSES.FLEXLAYOUT__SPLITTER_HANDLE_HORZ) - : cm(CLASSES.FLEXLAYOUT__SPLITTER_HANDLE_VERT)); - outlineDiv.current.appendChild(handleDiv.current); - } + const onPointerDown = (event: React.PointerEvent) => { + event.stopPropagation(); + if (node instanceof RowNode) { + initalSizes.current = node.getSplitterInitials(index); + } - const r = selfRef.current?.getBoundingClientRect(); - if (!r) { - return; - } - const domRect = layout.getDomRect(); - if (!domRect) { - return; - } - const rect = new Rect(r.x - domRect.x, r.y - domRect.y, r.width, r.height); + enablePointerOnIFrames(false, layout.getCurrentDocument()!); + startDrag(event.currentTarget.ownerDocument, event, onDragMove, onDragEnd, onDragCancel); + + pBounds.current = node.getSplitterBounds(index, true); + const rootdiv = layout.getRootDiv(); + outlineDiv.current = layout.getCurrentDocument()!.createElement("div"); + outlineDiv.current.style.flexDirection = horizontal ? "row" : "column"; + outlineDiv.current.className = layout.getClassName(CLASSES.FLEXLAYOUT__SPLITTER_DRAG); + outlineDiv.current.style.cursor = node.getOrientation() === Orientation.VERT ? "ns-resize" : "ew-resize"; + + if (node.getModel().isSplitterEnableHandle()) { + handleDiv.current = layout.getCurrentDocument()!.createElement("div"); + handleDiv.current.className = cm(CLASSES.FLEXLAYOUT__SPLITTER_HANDLE) + " " + + (horizontal ? cm(CLASSES.FLEXLAYOUT__SPLITTER_HANDLE_HORZ) : cm(CLASSES.FLEXLAYOUT__SPLITTER_HANDLE_VERT)); + outlineDiv.current.appendChild(handleDiv.current); + } - dragStartX.current = event.clientX - r.x; - dragStartY.current = event.clientY - r.y; + const r = selfRef.current?.getBoundingClientRect()!; + const rect = new Rect( + r.x - layout.getDomRect()!.x, + r.y - layout.getDomRect()!.y, + r.width, + r.height + ); - rect.positionElement(outlineDiv.current); - if (rootdiv) { - rootdiv.appendChild(outlineDiv.current); - } + dragStartX.current = event.clientX - r.x; + dragStartY.current = event.clientY - r.y; - setDragging(true); - splitterDragging = true; - }; + rect.positionElement(outlineDiv.current); + if (rootdiv) { + rootdiv.appendChild(outlineDiv.current); + } - const onDragCancel = () => { - const rootdiv = layout.getRootDiv(); - if (rootdiv && outlineDiv.current) { - rootdiv.removeChild(outlineDiv.current as Element); - } - outlineDiv.current = undefined; - setDragging(false); - splitterDragging = false; - }; - - const onDragMove = (x: number, y: number) => { - if (outlineDiv.current) { - const clientRect = layout.getDomRect(); - if (!clientRect) { - return; - } - if (node.getOrientation() === Orientation.VERT) { - outlineDiv.current!.style.top = - getBoundPosition(y - clientRect.y - dragStartY.current) + 'px'; - } else { - outlineDiv.current!.style.left = - getBoundPosition(x - clientRect.x - dragStartX.current) + 'px'; - } - - if (layout.isRealtimeResize()) { - updateLayout(true); - } - } - }; + setDragging(true); + splitterDragging = true; + }; - const onDragEnd = () => { - if (outlineDiv.current) { - updateLayout(false); + const onDragCancel = () => { + const rootdiv = layout.getRootDiv(); + if (rootdiv && outlineDiv.current) { + rootdiv.removeChild(outlineDiv.current as Element); + } + outlineDiv.current = undefined; + setDragging(false); + splitterDragging = false; + }; - const rootdiv = layout.getRootDiv(); - if (rootdiv && outlineDiv.current) { - rootdiv.removeChild(outlineDiv.current as HTMLElement); - } - outlineDiv.current = undefined; - } - enablePointerOnIFrames(true, layout.getCurrentDocument()!); - setDragging(false); - splitterDragging = false; - }; - - const updateLayout = (realtime: boolean) => { - const redraw = () => { - if (outlineDiv.current) { - let value = 0; - if (node.getOrientation() === Orientation.VERT) { - value = outlineDiv.current!.offsetTop; - } else { - value = outlineDiv.current!.offsetLeft; + const onDragMove = (x: number, y: number) => { + + if (outlineDiv.current) { + const clientRect = layout.getDomRect(); + if (!clientRect) { + return; + } + if (node.getOrientation() === Orientation.VERT) { + outlineDiv.current!.style.top = getBoundPosition(y - clientRect.y - dragStartY.current) + "px"; + } else { + outlineDiv.current!.style.left = getBoundPosition(x - clientRect.x - dragStartX.current) + "px"; + } + + if (layout.isRealtimeResize()) { + updateLayout(true); + } } + }; - if (node instanceof BorderNode) { - const pos = (node as BorderNode).calculateSplit(node, value); - layout.doAction(Actions.adjustBorderSplit(node.getId(), pos)); - } else { - const init = initalSizes.current; - const weights = node.calculateSplit( - index, - value, - init.initialSizes, - init.sum, - init.startPosition, - ); - layout.doAction(Actions.adjustWeights(node.getId(), weights)); + const onDragEnd = () => { + if (outlineDiv.current) { + updateLayout(false); + + const rootdiv = layout.getRootDiv(); + if (rootdiv && outlineDiv.current) { + rootdiv.removeChild(outlineDiv.current as HTMLElement); + } + outlineDiv.current = undefined; } - } + enablePointerOnIFrames(true, layout.getCurrentDocument()!); + setDragging(false); + splitterDragging = false; }; - redraw(); - }; + const updateLayout = (realtime: boolean) => { + + const redraw = () => { + if (outlineDiv.current) { + let value = 0; + if (node.getOrientation() === Orientation.VERT) { + value = outlineDiv.current!.offsetTop; + } else { + value = outlineDiv.current!.offsetLeft; + } + + + if (node instanceof BorderNode) { + const pos = (node as BorderNode).calculateSplit(node, value); + layout.doAction(Actions.adjustBorderSplit(node.getId(), pos)); + } else { + const init = initalSizes.current; + const weights = node.calculateSplit(index, value, init.initialSizes, init.sum, init.startPosition); + layout.doAction(Actions.adjustWeights(node.getId(), weights)); + } + } + }; + + redraw(); + }; + + const getBoundPosition = (p: number) => { + const bounds = pBounds.current as number[]; + let rtn = p; + if (p < bounds[0]) { + rtn = bounds[0]; + } + if (p > bounds[1]) { + rtn = bounds[1]; + } + + return rtn; + }; + + const cm = layout.getClassName; + const style: Record = { + cursor: horizontal ? "ew-resize" : "ns-resize", + flexDirection: horizontal ? "column" : "row" + }; + let className = cm(CLASSES.FLEXLAYOUT__SPLITTER) + " " + cm(CLASSES.FLEXLAYOUT__SPLITTER_ + node.getOrientation().getName()); - const getBoundPosition = (p: number) => { - const bounds = pBounds.current as number[]; - let rtn = p; - if (p < bounds[0]) { - rtn = bounds[0]; + if (node instanceof BorderNode) { + className += " " + cm(CLASSES.FLEXLAYOUT__SPLITTER_BORDER); + } else { + if (node.getModel().getMaximizedTabset(layout.getWindowId()) !== undefined) { + style.display = "none"; + } } - if (p > bounds[1]) { - rtn = bounds[1]; + + if (horizontal) { + style.width = size + "px"; + style.minWidth = size + "px"; + } else { + style.height = size + "px"; + style.minHeight = size + "px"; } - return rtn; - }; - - const cm = layout.getClassName; - const style: Record = { - cursor: horizontal ? 'ew-resize' : 'ns-resize', - flexDirection: horizontal ? 'column' : 'row', - }; - let className = - cm(CLASSES.FLEXLAYOUT__SPLITTER) + - ' ' + - cm(CLASSES.FLEXLAYOUT__SPLITTER_ + node.getOrientation().getName()); - - if (node instanceof BorderNode) { - className += ' ' + cm(CLASSES.FLEXLAYOUT__SPLITTER_BORDER); - } else { - if ( - node.getModel().getMaximizedTabset(layout.getWindowId()) !== undefined - ) { - style.display = 'none'; + let handle; + if (!dragging && node.getModel().isSplitterEnableHandle()) { + handle = ( +
+
+ ); } - } - - if (horizontal) { - style.width = size + 'px'; - style.minWidth = size + 'px'; - } else { - style.height = size + 'px'; - style.minHeight = size + 'px'; - } - - let handle; - if (!dragging && node.getModel().isSplitterEnableHandle()) { - handle = ( -
- ); - } - - if (extra === 0) { - return ( -
- {handle} -
- ); - } else { - // add extended transparent div for hit testing - - const style2: Record = {}; - if (node.getOrientation() === Orientation.HORZ) { - style2.height = '100%'; - style2.width = size + extra + 'px'; - style2.cursor = 'ew-resize'; + + if (extra === 0) { + return (
+ {handle} +
); } else { - style2.height = size + extra + 'px'; - style2.width = '100%'; - style2.cursor = 'ns-resize'; - } + // add extended transparent div for hit testing - const className2 = cm(CLASSES.FLEXLAYOUT__SPLITTER_EXTRA); - - return ( -
-
-
- ); - } + const style2: Record = {}; + if (node.getOrientation() === Orientation.HORZ) { + style2.height = "100%"; + style2.width = size + extra + "px"; + style2.cursor = "ew-resize"; + } else { + style2.height = size + extra + "px"; + style2.width = "100%"; + style2.cursor = "ns-resize"; + } + + const className2 = cm(CLASSES.FLEXLAYOUT__SPLITTER_EXTRA); + + return ( +
+
+
+
); + } }; + diff --git a/src/view/Tab.tsx b/src/view/Tab.tsx index 9d75188f..486be228 100755 --- a/src/view/Tab.tsx +++ b/src/view/Tab.tsx @@ -1,142 +1,133 @@ -import * as React from 'react'; -import { TabNode } from '../model/TabNode'; -import { TabSetNode } from '../model/TabSetNode'; -import { CLASSES } from '../Types'; -import { LayoutInternal } from './Layout'; -import { BorderNode } from '../model/BorderNode'; -import { Actions } from '../model/Actions'; +import * as React from "react"; +import { TabNode } from "../model/TabNode"; +import { TabSetNode } from "../model/TabSetNode"; +import { CLASSES } from "../Types"; +import { LayoutInternal } from "./Layout"; +import { BorderNode } from "../model/BorderNode"; +import { Actions } from "../model/Actions"; /** @internal */ export interface ITabProps { - layout: LayoutInternal; - node: TabNode; - selected: boolean; - path: string; + layout: LayoutInternal; + node: TabNode; + selected: boolean; + path: string; } /** @internal */ export const Tab = (props: ITabProps) => { - const { layout, selected, node, path } = props; - const selfRef = React.useRef(null); - const firstSelect = React.useRef(true); - - const parentNode = node.getParent() as TabSetNode | BorderNode; - const rect = parentNode.getContentRect()!; - - React.useLayoutEffect(() => { - const element = node.getMoveableElement()!; - selfRef.current!.appendChild(element); - node.setMoveableElement(element); - - const handleScroll = () => { - node.saveScrollPosition(); + const { layout, selected, node, path } = props; + const selfRef = React.useRef(null); + const firstSelect = React.useRef(true); + + const parentNode = node.getParent() as TabSetNode | BorderNode; + const rect = parentNode.getContentRect()!; + + React.useLayoutEffect(() => { + const element = node.getMoveableElement()!; + selfRef.current!.appendChild(element); + node.setMoveableElement(element); + + const handleScroll = () => { + node.saveScrollPosition(); + }; + + // keep scroll position + element.addEventListener('scroll', handleScroll); + + // listen for clicks to change active tabset + selfRef.current!.addEventListener("pointerdown", onPointerDown); + + return () => { + element.removeEventListener('scroll', handleScroll); + if (selfRef.current) { + selfRef.current.removeEventListener("pointerdown", onPointerDown); + } + node.setVisible(false); + } + }, []); + + React.useEffect(() => { + if (node.isSelected()) { + if (firstSelect.current) { + node.restoreScrollPosition(); // if window docked back in + firstSelect.current = false; + } + } + }); + + const onPointerDown = () => { + const parent = node.getParent()!; // cannot use parentNode here since will be out of date + if (parent instanceof TabSetNode) { + if (!parent.isActive()) { + layout.doAction(Actions.setActiveTabset(parent.getId(), layout.getWindowId())); + } + } }; - // keep scroll position - element.addEventListener('scroll', handleScroll); + node.setRect(rect); // needed for resize event + const cm = layout.getClassName; + const style: Record = {}; - // listen for clicks to change active tabset - selfRef.current!.addEventListener('pointerdown', onPointerDown); + rect.styleWithPosition(style); - return () => { - element.removeEventListener('scroll', handleScroll); - if (selfRef.current) { - selfRef.current.removeEventListener('pointerdown', onPointerDown); - } - node.setVisible(false); - }; - }, []); - - React.useEffect(() => { - if (node.isSelected()) { - if (firstSelect.current) { - node.restoreScrollPosition(); // if window docked back in - firstSelect.current = false; - } + if (parentNode instanceof BorderNode && !node.isPinned()) { + style.zIndex = 1000; + style.boxShadow = "0 2px 8px rgba(0,0,0,0.2)"; + } + + let overlay = null; + + if (selected) { + node.setVisible(true); + if (document.hidden && node.isEnablePopoutOverlay()) { + const overlayStyle: Record = {}; + rect.styleWithPosition(overlayStyle); + overlay = (
) + } + } else { + style.display = "none"; + node.setVisible(false); } - }); - - const onPointerDown = () => { - const parent = node.getParent()!; // cannot use parentNode here since will be out of date - if (parent instanceof TabSetNode) { - if (!parent.isActive()) { - layout.doAction( - Actions.setActiveTabset(parent.getId(), layout.getWindowId()), - ); - } + + if (parentNode instanceof TabSetNode) { + if (node.getModel().getMaximizedTabset(layout.getWindowId()) !== undefined) { + if (parentNode.isMaximized()) { + style.zIndex = 10; + } else { + style.display = "none"; + } + } } - }; - - node.setRect(rect); // needed for resize event - const cm = layout.getClassName; - const style: Record = {}; - - rect.styleWithPosition(style); - - if (parentNode instanceof BorderNode && !node.isPinned()) { - style.zIndex = 1000; - style.boxShadow = '0 2px 8px rgba(0,0,0,0.2)'; - } - - let overlay = null; - - if (selected) { - node.setVisible(true); - if (document.hidden && node.isEnablePopoutOverlay()) { - const overlayStyle: Record = {}; - rect.styleWithPosition(overlayStyle); - overlay = ( -
- ); + + if (parentNode instanceof BorderNode) { + if (!parentNode.isShowing()) { + style.display = "none"; + } } - } else { - style.display = 'none'; - node.setVisible(false); - } - - if (parentNode instanceof TabSetNode) { - if ( - node.getModel().getMaximizedTabset(layout.getWindowId()) !== undefined - ) { - if (parentNode.isMaximized()) { - style.zIndex = 10; - } else { - style.display = 'none'; - } + + let className = cm(CLASSES.FLEXLAYOUT__TAB); + if (parentNode instanceof BorderNode) { + className += " " + cm(CLASSES.FLEXLAYOUT__TAB_BORDER); + className += " " + cm(CLASSES.FLEXLAYOUT__TAB_BORDER_ + parentNode.getLocation().getName()); } - } - if (parentNode instanceof BorderNode) { - if (!parentNode.isShowing()) { - style.display = 'none'; + if (node.getContentClassName() !== undefined) { + className += " " + node.getContentClassName(); } - } - - let className = cm(CLASSES.FLEXLAYOUT__TAB); - if (parentNode instanceof BorderNode) { - className += ' ' + cm(CLASSES.FLEXLAYOUT__TAB_BORDER); - className += - ' ' + - cm(CLASSES.FLEXLAYOUT__TAB_BORDER_ + parentNode.getLocation().getName()); - } - - if (node.getContentClassName() !== undefined) { - className += ' ' + node.getContentClassName(); - } - - return ( - <> - {overlay} - -
- - ); + + return ( + <> + {overlay} + +
+ + ); }; + + diff --git a/src/view/TabButton.tsx b/src/view/TabButton.tsx index b2b93b9a..cd2e48c6 100755 --- a/src/view/TabButton.tsx +++ b/src/view/TabButton.tsx @@ -1,223 +1,200 @@ -import * as React from 'react'; -import { I18nLabel } from '../I18nLabel'; -import { Actions } from '../model/Actions'; -import { TabNode } from '../model/TabNode'; -import { TabSetNode } from '../model/TabSetNode'; -import { LayoutInternal } from './Layout'; -import { ICloseType } from '../model/ICloseType'; -import { CLASSES } from '../Types'; -import { getRenderStateEx, isAuxMouseEvent } from './Utils'; +import * as React from "react"; +import { I18nLabel } from "../I18nLabel"; +import { Actions } from "../model/Actions"; +import { TabNode } from "../model/TabNode"; +import { TabSetNode } from "../model/TabSetNode"; +import { LayoutInternal } from "./Layout"; +import { ICloseType } from "../model/ICloseType"; +import { CLASSES } from "../Types"; +import { getRenderStateEx, isAuxMouseEvent } from "./Utils"; /** @internal */ export interface ITabButtonProps { - layout: LayoutInternal; - node: TabNode; - selected: boolean; - path: string; + layout: LayoutInternal; + node: TabNode; + selected: boolean; + path: string; } /** @internal */ export const TabButton = (props: ITabButtonProps) => { - const { layout, node, selected, path } = props; - const selfRef = React.useRef(null); - const contentRef = React.useRef(null); - const icons = layout.getIcons(); - - React.useLayoutEffect(() => { - node.setTabRect(layout.getBoundingClientRect(selfRef.current!)); - if (layout.getEditingTab() === node) { - (contentRef.current! as HTMLInputElement).select(); - } - }); - - const onDragStart = (event: React.DragEvent) => { - if (node.isEnableDrag()) { - event.stopPropagation(); // prevent starting a tabset drag as well - layout.setDragNode(event.nativeEvent, node as TabNode); - } else { - event.preventDefault(); + const { layout, node, selected, path } = props; + const selfRef = React.useRef(null); + const contentRef = React.useRef(null); + const icons = layout.getIcons(); + + React.useLayoutEffect(() => { + node.setTabRect(layout.getBoundingClientRect(selfRef.current!)); + if (layout.getEditingTab() === node) { + (contentRef.current! as HTMLInputElement).select(); + } + }); + + const onDragStart = (event: React.DragEvent) => { + if (node.isEnableDrag()) { + event.stopPropagation(); // prevent starting a tabset drag as well + layout.setDragNode(event.nativeEvent, node as TabNode); + } else { + event.preventDefault(); + } + }; + + const onDragEnd = (event: React.DragEvent) => { + layout.clearDragMain(); + }; + + const onAuxMouseClick = (event: React.MouseEvent) => { + if (isAuxMouseEvent(event)) { + layout.auxMouseClick(node, event); + } + }; + + const onContextMenu = (event: React.MouseEvent) => { + layout.showContextMenu(node, event); + }; + + const onClick = () => { + layout.doAction(Actions.selectTab(node.getId())); + }; + + const onDoubleClick = (event: React.MouseEvent) => { + if (node.isEnableRename()) { + onRename(); + event.stopPropagation(); + } + }; + + const onRename = () => { + layout.setEditingTab(node); + layout.getCurrentDocument()!.body.addEventListener("pointerdown", onEndEdit); + }; + + const onEndEdit = (event: Event) => { + if (event.target !== contentRef.current!) { + layout.getCurrentDocument()!.body.removeEventListener("pointerdown", onEndEdit); + layout.setEditingTab(undefined); + } + }; + + const isClosable = () => { + const closeType = node.getCloseType(); + if (selected || closeType === ICloseType.Always) { + return true; + } + if (closeType === ICloseType.Visible) { + // not selected but x should be visible due to hover + if (window.matchMedia && window.matchMedia("(hover: hover) and (pointer: fine)").matches) { + return true; + } + } + return false; + }; + + const onClose = (event: React.MouseEvent) => { + if (isClosable()) { + layout.doAction(Actions.deleteTab(node.getId())); + event.stopPropagation(); + } + }; + + const onClosePointerDown = (event: React.PointerEvent) => { + event.stopPropagation(); + }; + + const onTextBoxPointerDown = (event: React.PointerEvent) => { + event.stopPropagation(); + }; + + const onTextBoxKeyPress = (event: React.KeyboardEvent) => { + if (event.code === 'Escape') { + // esc + layout.setEditingTab(undefined); + } else if (event.code === 'Enter' || event.code === 'NumpadEnter') { + // enter + layout.setEditingTab(undefined); + layout.doAction(Actions.renameTab(node.getId(), (event.target as HTMLInputElement).value)); + } + }; + + const cm = layout.getClassName; + const parentNode = node.getParent() as TabSetNode; + + const isStretch = parentNode.isEnableSingleTabStretch() && parentNode.getChildren().length === 1; + const baseClassName = isStretch ? CLASSES.FLEXLAYOUT__TAB_BUTTON_STRETCH : CLASSES.FLEXLAYOUT__TAB_BUTTON; + let classNames = cm(baseClassName); + classNames += " " + cm(baseClassName + "_" + parentNode.getTabLocation()); + + if (!isStretch) { + if (selected) { + classNames += " " + cm(baseClassName + "--selected"); + } else { + classNames += " " + cm(baseClassName + "--unselected"); + } } - }; - const onDragEnd = (event: React.DragEvent) => { - layout.clearDragMain(); - }; - - const onAuxMouseClick = ( - event: React.MouseEvent, - ) => { - if (isAuxMouseEvent(event)) { - layout.auxMouseClick(node, event); + if (node.getClassName() !== undefined) { + classNames += " " + node.getClassName(); } - }; - const onContextMenu = (event: React.MouseEvent) => { - layout.showContextMenu(node, event); - }; + const renderState = getRenderStateEx(layout, node); - const onClick = () => { - layout.doAction(Actions.selectTab(node.getId())); - }; + let content = renderState.content ? ( +
+ {renderState.content} +
) : null; - const onDoubleClick = (event: React.MouseEvent) => { - if (node.isEnableRename()) { - onRename(); - event.stopPropagation(); - } - }; - - const onRename = () => { - layout.setEditingTab(node); - layout - .getCurrentDocument()! - .body.addEventListener('pointerdown', onEndEdit); - }; - - const onEndEdit = (event: Event) => { - if (event.target !== contentRef.current!) { - layout - .getCurrentDocument()! - .body.removeEventListener('pointerdown', onEndEdit); - layout.setEditingTab(undefined); - } - }; + const leading = renderState.leading ? ( +
+ {renderState.leading} +
) : null; - const isClosable = () => { - const closeType = node.getCloseType(); - if (selected || closeType === ICloseType.Always) { - return true; - } - if (closeType === ICloseType.Visible) { - // not selected but x should be visible due to hover - if ( - window.matchMedia && - window.matchMedia('(hover: hover) and (pointer: fine)').matches - ) { - return true; - } + if (layout.getEditingTab() === node) { + content = ( + + ); } - return false; - }; - const onClose = (event: React.MouseEvent) => { - if (isClosable()) { - layout.doAction(Actions.deleteTab(node.getId())); - event.stopPropagation(); - } - }; - - const onClosePointerDown = (event: React.PointerEvent) => { - event.stopPropagation(); - }; - - const onTextBoxPointerDown = ( - event: React.PointerEvent, - ) => { - event.stopPropagation(); - }; - - const onTextBoxKeyPress = (event: React.KeyboardEvent) => { - if (event.code === 'Escape') { - // esc - layout.setEditingTab(undefined); - } else if (event.code === 'Enter' || event.code === 'NumpadEnter') { - // enter - layout.setEditingTab(undefined); - layout.doAction( - Actions.renameTab( - node.getId(), - (event.target as HTMLInputElement).value, - ), - ); + if (node.isEnableClose() && !isStretch) { + const closeTitle = layout.i18nName(I18nLabel.Close_Tab); + renderState.buttons.push( +
+ {(typeof icons.close === "function") ? icons.close(node) : icons.close} +
+ ); } - }; - - const cm = layout.getClassName; - const parentNode = node.getParent() as TabSetNode; - - const isStretch = - parentNode.isEnableSingleTabStretch() && - parentNode.getChildren().length === 1; - const baseClassName = isStretch - ? CLASSES.FLEXLAYOUT__TAB_BUTTON_STRETCH - : CLASSES.FLEXLAYOUT__TAB_BUTTON; - let classNames = cm(baseClassName); - classNames += ' ' + cm(baseClassName + '_' + parentNode.getTabLocation()); - - if (!isStretch) { - if (selected) { - classNames += ' ' + cm(baseClassName + '--selected'); - } else { - classNames += ' ' + cm(baseClassName + '--unselected'); - } - } - - if (node.getClassName() !== undefined) { - classNames += ' ' + node.getClassName(); - } - - const renderState = getRenderStateEx(layout, node); - - let content = renderState.content ? ( -
- {renderState.content} -
- ) : null; - - const leading = renderState.leading ? ( -
- {renderState.leading} -
- ) : null; - - if (layout.getEditingTab() === node) { - content = ( - - ); - } - - if (node.isEnableClose() && !isStretch) { - const closeTitle = layout.i18nName(I18nLabel.Close_Tab); - renderState.buttons.push( -
- {typeof icons.close === 'function' ? icons.close(node) : icons.close} -
, + + return ( +
+ {leading} + {content} + {renderState.buttons} +
); - } - - return ( -
- {leading} - {content} - {renderState.buttons} -
- ); }; diff --git a/src/view/TabButtonStamp.tsx b/src/view/TabButtonStamp.tsx index eb46e629..a66a449f 100755 --- a/src/view/TabButtonStamp.tsx +++ b/src/view/TabButtonStamp.tsx @@ -1,42 +1,42 @@ -import { TabNode } from '../model/TabNode'; -import { LayoutInternal } from './Layout'; -import { CLASSES } from '../Types'; -import { getRenderStateEx } from './Utils'; +import { TabNode } from "../model/TabNode"; +import { LayoutInternal } from "./Layout"; +import { CLASSES } from "../Types"; +import { getRenderStateEx } from "./Utils"; /** @internal */ export interface ITabButtonStampProps { - node: TabNode; - layout: LayoutInternal; + node: TabNode; + layout: LayoutInternal; } /** @internal */ export const TabButtonStamp = (props: ITabButtonStampProps) => { - const { layout, node } = props; - - const cm = layout.getClassName; - - const classNames = cm(CLASSES.FLEXLAYOUT__TAB_BUTTON_STAMP); - - const renderState = getRenderStateEx(layout, node); - - const content = renderState.content ? ( -
- {renderState.content} -
- ) : ( - node.getNameForOverflowMenu() - ); - - const leading = renderState.leading ? ( -
- {renderState.leading} -
- ) : null; - - return ( -
- {leading} - {content} -
- ); + const { layout, node } = props; + + const cm = layout.getClassName; + + const classNames = cm(CLASSES.FLEXLAYOUT__TAB_BUTTON_STAMP); + + const renderState = getRenderStateEx(layout, node); + + const content = renderState.content ? ( +
+ {renderState.content} +
) + : node.getNameForOverflowMenu(); + + const leading = renderState.leading ? ( +
+ {renderState.leading} +
) : null; + + return ( +
+ {leading} + {content} +
+ ); }; diff --git a/src/view/TabOverflowHook.tsx b/src/view/TabOverflowHook.tsx index 23059695..6f7a254a 100755 --- a/src/view/TabOverflowHook.tsx +++ b/src/view/TabOverflowHook.tsx @@ -1,342 +1,317 @@ -import * as React from 'react'; -import { TabSetNode } from '../model/TabSetNode'; -import { BorderNode } from '../model/BorderNode'; -import { Orientation } from '../Orientation'; -import { LayoutInternal } from './Layout'; -import { TabNode } from '../model/TabNode'; -import { startDrag } from './Utils'; -import { Rect } from '../Rect'; +import * as React from "react"; +import { TabSetNode } from "../model/TabSetNode"; +import { BorderNode } from "../model/BorderNode"; +import { Orientation } from "../Orientation"; +import { LayoutInternal } from "./Layout"; +import { TabNode } from "../model/TabNode"; +import { startDrag } from "./Utils"; +import { Rect } from "../Rect"; /** @internal */ export const useTabOverflow = ( - layout: LayoutInternal, - node: TabSetNode | BorderNode, - orientation: Orientation, - tabStripRef: React.RefObject, - miniScrollRef: React.RefObject, - tabClassName: string, + layout: LayoutInternal, + node: TabSetNode | BorderNode, + orientation: Orientation, + tabStripRef: React.RefObject, + miniScrollRef: React.RefObject, + tabClassName: string ) => { - const [hiddenTabs, setHiddenTabs] = React.useState([]); - const [isShowHiddenTabs, setShowHiddenTabs] = React.useState(false); - const [isDockStickyButtons, setDockStickyButtons] = - React.useState(false); - - const selfRef = React.useRef(null); - const userControlledPositionRef = React.useRef(false); - const updateHiddenTabsTimerRef = React.useRef( - undefined, - ); - const hiddenTabsRef = React.useRef([]); - const thumbInternalPos = React.useRef(0); - const repositioningRef = React.useRef(false); - hiddenTabsRef.current = hiddenTabs; - - // if node id changes (new model) then reset scroll to 0 - React.useLayoutEffect(() => { - if (tabStripRef.current) { - setScrollPosition(0); - } - }, [node.getId()]); + const [hiddenTabs, setHiddenTabs] = React.useState([]); + const [isShowHiddenTabs, setShowHiddenTabs] = React.useState(false); + const [isDockStickyButtons, setDockStickyButtons] = React.useState(false); + + const selfRef = React.useRef(null); + const userControlledPositionRef = React.useRef(false); + const updateHiddenTabsTimerRef = React.useRef(undefined); + const hiddenTabsRef = React.useRef([]); + const thumbInternalPos = React.useRef(0); + const repositioningRef = React.useRef(false); + hiddenTabsRef.current = hiddenTabs; + + // if node id changes (new model) then reset scroll to 0 + React.useLayoutEffect(() => { + if (tabStripRef.current) { + setScrollPosition(0); + } + }, [node.getId()]); - // if selected node or tabset/border rectangle change then unset usercontrolled (so selected tab will be kept in view) - React.useLayoutEffect(() => { - userControlledPositionRef.current = false; - }, [node.getSelectedNode(), node.getRect().width, node.getRect().height]); + // if selected node or tabset/border rectangle change then unset usercontrolled (so selected tab will be kept in view) + React.useLayoutEffect(() => { + userControlledPositionRef.current = false; + }, [node.getSelectedNode(), node.getRect().width, node.getRect().height]); - React.useLayoutEffect(() => { - checkForOverflow(); // if tabs + sticky buttons length > scroll area => move sticky buttons to right buttons + React.useLayoutEffect(() => { + checkForOverflow(); // if tabs + sticky buttons length > scroll area => move sticky buttons to right buttons - if (userControlledPositionRef.current === false) { - scrollIntoView(); - } + if (userControlledPositionRef.current === false) { + scrollIntoView(); + } - updateScrollMetrics(); - updateHiddenTabs(); - }); + updateScrollMetrics(); + updateHiddenTabs(); + }); - React.useEffect(() => { - selfRef.current?.addEventListener('wheel', onWheel, { passive: false }); - return () => { - selfRef.current?.removeEventListener('wheel', onWheel); + React.useEffect(() => { + selfRef.current?.addEventListener("wheel", onWheel, { passive: false }); + return () => { + selfRef.current?.removeEventListener("wheel", onWheel); + }; + }, [selfRef.current]); + + // needed to prevent default mouse wheel over tabset/border when page scrolled (cannot do with react event?) + const onWheel = (event: Event) => { + event.preventDefault(); }; - }, [selfRef.current]); - - // needed to prevent default mouse wheel over tabset/border when page scrolled (cannot do with react event?) - const onWheel = (event: Event) => { - event.preventDefault(); - }; - - function scrollIntoView() { - const selectedTabNode = node.getSelectedNode() as TabNode; - if (selectedTabNode && tabStripRef.current) { - const stripRect = layout.getBoundingClientRect(tabStripRef.current); - const selectedRect = selectedTabNode.getTabRect()!; - - let shift = getNear(stripRect) - getNear(selectedRect); - if (shift > 0 || getSize(selectedRect) > getSize(stripRect)) { - setScrollPosition(getScrollPosition(tabStripRef.current) - shift); - repositioningRef.current = true; // prevent onScroll setting userControlledPosition - } else { - shift = getFar(selectedRect) - getFar(stripRect); - if (shift > 0) { - setScrollPosition(getScrollPosition(tabStripRef.current) + shift); - repositioningRef.current = true; + + function scrollIntoView() { + const selectedTabNode = node.getSelectedNode() as TabNode; + if (selectedTabNode && tabStripRef.current) { + const stripRect = layout.getBoundingClientRect(tabStripRef.current); + const selectedRect = selectedTabNode.getTabRect()!; + + let shift = getNear(stripRect) - getNear(selectedRect); + if (shift > 0 || getSize(selectedRect) > getSize(stripRect)) { + setScrollPosition(getScrollPosition(tabStripRef.current) - shift); + repositioningRef.current = true; // prevent onScroll setting userControlledPosition + } else { + shift = getFar(selectedRect) - getFar(stripRect); + if (shift > 0) { + setScrollPosition(getScrollPosition(tabStripRef.current) + shift); + repositioningRef.current = true; + } + } } - } } - } - - const updateScrollMetrics = () => { - if (tabStripRef.current && miniScrollRef.current) { - const t = tabStripRef.current; - const s = miniScrollRef.current; - - const size = getElementSize(t); - const scrollSize = getScrollSize(t); - const position = getScrollPosition(t); - - if (scrollSize > size && scrollSize > 0) { - let thumbSize = (size * size) / scrollSize; - let adjust = 0; - if (thumbSize < 20) { - adjust = 20 - thumbSize; - thumbSize = 20; - } - const thumbPos = (position * (size - adjust)) / scrollSize; - if (orientation === Orientation.HORZ) { - s.style.width = thumbSize + 'px'; - s.style.left = thumbPos + 'px'; - } else { - s.style.height = thumbSize + 'px'; - s.style.top = thumbPos + 'px'; + + const updateScrollMetrics = () => { + if (tabStripRef.current && miniScrollRef.current) { + const t = tabStripRef.current; + const s = miniScrollRef.current; + + const size = getElementSize(t); + const scrollSize = getScrollSize(t); + const position = getScrollPosition(t); + + if (scrollSize > size && scrollSize > 0) { + let thumbSize = size * size / scrollSize; + let adjust = 0; + if (thumbSize < 20) { + adjust = 20 - thumbSize; + thumbSize = 20; + } + const thumbPos = position * (size - adjust) / scrollSize; + if (orientation === Orientation.HORZ) { + s.style.width = thumbSize + "px"; + s.style.left = thumbPos + "px"; + } else { + s.style.height = thumbSize + "px"; + s.style.top = thumbPos + "px"; + } + s.style.display = "block"; + } else { + s.style.display = "none"; + } + + if (orientation === Orientation.HORZ) { + s.style.bottom = "0px"; + } else { + s.style.right = "0px"; + } } - s.style.display = 'block'; - } else { - s.style.display = 'none'; - } - - if (orientation === Orientation.HORZ) { - s.style.bottom = '0px'; - } else { - s.style.right = '0px'; - } } - }; - const updateHiddenTabs = () => { - const newHiddenTabs = findHiddenTabs(); - const showHidden = newHiddenTabs.length > 0; + const updateHiddenTabs = () => { + const newHiddenTabs = findHiddenTabs(); + const showHidden = newHiddenTabs.length > 0; - if (showHidden !== isShowHiddenTabs) { - setShowHiddenTabs(showHidden); + if (showHidden !== isShowHiddenTabs) { + setShowHiddenTabs(showHidden); + } + + if (updateHiddenTabsTimerRef.current === undefined) { + // throttle updates to prevent Maximum update depth exceeded error + updateHiddenTabsTimerRef.current = setTimeout(() => { + const newHiddenTabs = findHiddenTabs(); + if (!arraysEqual(newHiddenTabs, hiddenTabsRef.current)) { + setHiddenTabs(newHiddenTabs); + } + + updateHiddenTabsTimerRef.current = undefined; + }, 100); + } } - if (updateHiddenTabsTimerRef.current === undefined) { - // throttle updates to prevent Maximum update depth exceeded error - updateHiddenTabsTimerRef.current = setTimeout(() => { - const newHiddenTabs = findHiddenTabs(); - if (!arraysEqual(newHiddenTabs, hiddenTabsRef.current)) { - setHiddenTabs(newHiddenTabs); + const onScroll = () => { + if (!repositioningRef.current){ + userControlledPositionRef.current=true; } + repositioningRef.current = false; + updateScrollMetrics() + updateHiddenTabs(); + }; - updateHiddenTabsTimerRef.current = undefined; - }, 100); + const onScrollPointerDown = (event: React.PointerEvent) => { + event.stopPropagation(); + miniScrollRef.current!.setPointerCapture(event.pointerId) + const r = miniScrollRef.current?.getBoundingClientRect()!; + if (orientation === Orientation.HORZ) { + thumbInternalPos.current = event.clientX - r.x; + } else { + thumbInternalPos.current = event.clientY - r.y; + } + startDrag(event.currentTarget.ownerDocument, event, onDragMove, onDragEnd, onDragCancel); } - }; - const onScroll = () => { - if (!repositioningRef.current) { - userControlledPositionRef.current = true; - } - repositioningRef.current = false; - updateScrollMetrics(); - updateHiddenTabs(); - }; - - const onScrollPointerDown = (event: React.PointerEvent) => { - event.stopPropagation(); - miniScrollRef.current!.setPointerCapture(event.pointerId); - const miniScrollElem = miniScrollRef.current; - if (!miniScrollElem) return; - const r = miniScrollElem.getBoundingClientRect(); - if (orientation === Orientation.HORZ) { - thumbInternalPos.current = event.clientX - r.x; - } else { - thumbInternalPos.current = event.clientY - r.y; - } - startDrag( - event.currentTarget.ownerDocument, - event, - onDragMove, - onDragEnd, - onDragCancel, - ); - }; - - const onDragMove = (x: number, y: number) => { - if (tabStripRef.current && miniScrollRef.current) { - const t = tabStripRef.current; - const s = miniScrollRef.current; - const size = getElementSize(t); - const scrollSize = getScrollSize(t); - const thumbSize = getElementSize(s); - - const r = t.getBoundingClientRect()!; - let thumb = 0; - if (orientation === Orientation.HORZ) { - thumb = x - r.x - thumbInternalPos.current; - } else { - thumb = y - r.y - thumbInternalPos.current; - } - - thumb = Math.max(0, Math.min(scrollSize - thumbSize, thumb)); - if (size > 0) { - const scrollPos = (thumb * scrollSize) / size; - setScrollPosition(scrollPos); - } + const onDragMove = (x: number, y: number) => { + if (tabStripRef.current && miniScrollRef.current) { + const t = tabStripRef.current; + const s = miniScrollRef.current; + const size = getElementSize(t); + const scrollSize = getScrollSize(t); + const thumbSize = getElementSize(s); + + const r = t.getBoundingClientRect()!; + let thumb = 0; + if (orientation === Orientation.HORZ) { + thumb = x - r.x - thumbInternalPos.current; + } else { + thumb = y - r.y - thumbInternalPos.current + } + + thumb = Math.max(0, Math.min(scrollSize - thumbSize, thumb)); + if (size > 0) { + const scrollPos = thumb * scrollSize / size; + setScrollPosition(scrollPos); + } + } } - }; - const onDragEnd = () => {}; + const onDragEnd = () => { + } - const onDragCancel = () => {}; + const onDragCancel = () => { + } - const checkForOverflow = () => { - if (tabStripRef.current) { - const strip = tabStripRef.current; - const tabContainer = strip.firstElementChild!; + const checkForOverflow = () => { + if (tabStripRef.current) { + const strip = tabStripRef.current; + const tabContainer = strip.firstElementChild!; - const offset = isDockStickyButtons ? 10 : 0; // prevents flashing, after sticky buttons docked set, must be 10 pixels smaller before unsetting - const dock = - getElementSize(tabContainer) + offset > - getElementSize(tabStripRef.current); - if (dock !== isDockStickyButtons) { - setDockStickyButtons(dock); - } - } - }; - - const findHiddenTabs: () => number[] = () => { - const hidden: number[] = []; - if (tabStripRef.current) { - const strip = tabStripRef.current; - const stripRect = strip.getBoundingClientRect(); - const visibleNear = getNear(stripRect) - 1; - const visibleFar = getFar(stripRect) + 1; - - const tabContainer = strip.firstElementChild!; - - let i = 0; - Array.from(tabContainer.children).forEach((child) => { - const tabRect = child.getBoundingClientRect(); - - if (child.classList.contains(tabClassName)) { - if (getNear(tabRect) < visibleNear || getFar(tabRect) > visibleFar) { - hidden.push(i); - } - i++; + const offset = isDockStickyButtons ? 10 : 0; // prevents flashing, after sticky buttons docked set, must be 10 pixels smaller before unsetting + const dock = (getElementSize(tabContainer) + offset) > getElementSize(tabStripRef.current); + if (dock !== isDockStickyButtons) { + setDockStickyButtons(dock); + } } - }); } - return hidden; - }; + const findHiddenTabs: () => number[] = () => { + const hidden: number[] = []; + if (tabStripRef.current) { + const strip = tabStripRef.current; + const stripRect = strip.getBoundingClientRect(); + const visibleNear = getNear(stripRect) - 1; + const visibleFar = getFar(stripRect) + 1; + + const tabContainer = strip.firstElementChild!; + + let i = 0; + Array.from(tabContainer.children).forEach((child) => { + const tabRect = child.getBoundingClientRect(); + + if (child.classList.contains(tabClassName)) { + if (getNear(tabRect) < visibleNear || getFar(tabRect) > visibleFar) { + hidden.push(i); + } + i++; + } + }); + } - const onMouseWheel = (event: React.WheelEvent) => { - if (tabStripRef.current) { - if (node.getChildren().length === 0) return; + return hidden; + }; - let delta = 0; - if (Math.abs(event.deltaY) > 0) { - delta = -event.deltaY; - if (event.deltaMode === 1) { - // DOM_DELTA_LINE 0x01 The delta values are specified in lines. - delta *= 40; + const onMouseWheel = (event: React.WheelEvent) => { + if (tabStripRef.current) { + if (node.getChildren().length === 0) return; + + let delta = 0; + if (Math.abs(event.deltaY) > 0) { + delta = -event.deltaY; + if (event.deltaMode === 1) { + // DOM_DELTA_LINE 0x01 The delta values are specified in lines. + delta *= 40; + } + const newPos = getScrollPosition(tabStripRef.current) - delta; + const maxScroll = getScrollSize(tabStripRef.current) - getElementSize(tabStripRef.current); + const p = Math.max(0, Math.min(maxScroll, newPos)); + setScrollPosition(p); + event.stopPropagation(); + } } - const newPos = getScrollPosition(tabStripRef.current) - delta; - const maxScroll = - getScrollSize(tabStripRef.current) - - getElementSize(tabStripRef.current); - const p = Math.max(0, Math.min(maxScroll, newPos)); - setScrollPosition(p); - event.stopPropagation(); - } - } - }; + }; - // orientation helpers: + // orientation helpers: - const getNear = (rect: DOMRect | Rect) => { - if (orientation === Orientation.HORZ) { - return rect.x; - } else { - return rect.y; - } - }; + const getNear = (rect: DOMRect | Rect) => { + if (orientation === Orientation.HORZ) { + return rect.x; + } else { + return rect.y; + } + }; - const getFar = (rect: DOMRect | Rect) => { - if (orientation === Orientation.HORZ) { - return rect.right; - } else { - return rect.bottom; - } - }; + const getFar = (rect: DOMRect | Rect) => { + if (orientation === Orientation.HORZ) { + return rect.right; + } else { + return rect.bottom; + } + }; - const getElementSize = (elm: Element) => { - if (orientation === Orientation.HORZ) { - return elm.clientWidth; - } else { - return elm.clientHeight; + const getElementSize = (elm: Element) => { + if (orientation === Orientation.HORZ) { + return elm.clientWidth; + } else { + return elm.clientHeight; + } } - }; - const getSize = (rect: DOMRect | Rect) => { - if (orientation === Orientation.HORZ) { - return rect.width; - } else { - return rect.height; + const getSize = (rect: DOMRect | Rect) => { + if (orientation === Orientation.HORZ) { + return rect.width; + } else { + return rect.height; + } } - }; - const getScrollSize = (elm: Element) => { - if (orientation === Orientation.HORZ) { - return elm.scrollWidth; - } else { - return elm.scrollHeight; + const getScrollSize = (elm: Element) => { + if (orientation === Orientation.HORZ) { + return elm.scrollWidth; + } else { + return elm.scrollHeight; + } } - }; - const setScrollPosition = (p: number) => { - if (orientation === Orientation.HORZ) { - tabStripRef.current!.scrollLeft = p; - } else { - tabStripRef.current!.scrollTop = p; + const setScrollPosition = (p: number) => { + if (orientation === Orientation.HORZ) { + tabStripRef.current!.scrollLeft = p; + } else { + tabStripRef.current!.scrollTop = p; + } } - }; - const getScrollPosition = (elm: Element) => { - if (orientation === Orientation.HORZ) { - return elm.scrollLeft; - } else { - return elm.scrollTop; + const getScrollPosition = (elm: Element) => { + if (orientation === Orientation.HORZ) { + return elm.scrollLeft; + } else { + return elm.scrollTop; + } } - }; - - return { - selfRef, - userControlledPositionRef, - onScroll, - onScrollPointerDown, - hiddenTabs, - onMouseWheel, - isDockStickyButtons, - isShowHiddenTabs, - }; + + return { selfRef, userControlledPositionRef, onScroll, onScrollPointerDown, hiddenTabs, onMouseWheel, isDockStickyButtons, isShowHiddenTabs }; }; function arraysEqual(arr1: number[], arr2: number[]) { - return ( - arr1.length === arr2.length && - arr1.every((val, index) => val === arr2[index]) - ); + return arr1.length === arr2.length && arr1.every((val, index) => val === arr2[index]); } diff --git a/src/view/TabSet.tsx b/src/view/TabSet.tsx index 9add762b..998127cd 100755 --- a/src/view/TabSet.tsx +++ b/src/view/TabSet.tsx @@ -1,605 +1,487 @@ -import * as React from 'react'; -import { I18nLabel } from '../I18nLabel'; -import { Actions } from '../model/Actions'; -import { TabNode } from '../model/TabNode'; -import { TabSetNode } from '../model/TabSetNode'; -import { showPopup } from './PopupMenu'; -import { LayoutInternal, ITabSetRenderValues } from './Layout'; -import { TabButton } from './TabButton'; -import { useTabOverflow } from './TabOverflowHook'; -import { Orientation } from '../Orientation'; -import { CLASSES } from '../Types'; -import { isAuxMouseEvent } from './Utils'; -import { createPortal } from 'react-dom'; -import { splitterDragging } from './Splitter'; +import * as React from "react"; +import { I18nLabel } from "../I18nLabel"; +import { Actions } from "../model/Actions"; +import { TabNode } from "../model/TabNode"; +import { TabSetNode } from "../model/TabSetNode"; +import { showPopup } from "./PopupMenu"; +import { LayoutInternal, ITabSetRenderValues } from "./Layout"; +import { TabButton } from "./TabButton"; +import { useTabOverflow } from "./TabOverflowHook"; +import { Orientation } from "../Orientation"; +import { CLASSES } from "../Types"; +import { isAuxMouseEvent } from "./Utils"; +import { createPortal } from "react-dom"; +import { splitterDragging } from "./Splitter"; /** @internal */ export interface ITabSetProps { - layout: LayoutInternal; - node: TabSetNode; + layout: LayoutInternal; + node: TabSetNode; } /** @internal */ export const TabSet = (props: ITabSetProps) => { - const { node, layout } = props; + const { node, layout } = props; - const tabStripRef = React.useRef(null); - const miniScrollRef = React.useRef(null); - const tabStripInnerRef = React.useRef(null); - const contentRef = React.useRef(null); - const buttonBarRef = React.useRef(null); - const overflowbuttonRef = React.useRef(null); - const stickyButtonsRef = React.useRef(null); - const timer = React.useRef(undefined); + const tabStripRef = React.useRef(null); + const miniScrollRef = React.useRef(null); + const tabStripInnerRef = React.useRef(null); + const contentRef = React.useRef(null); + const buttonBarRef = React.useRef(null); + const overflowbuttonRef = React.useRef(null); + const stickyButtonsRef = React.useRef(null); + const timer = React.useRef(undefined); - const icons = layout.getIcons(); + const icons = layout.getIcons(); - React.useLayoutEffect(() => { - node.setRect(layout.getBoundingClientRect(selfRef.current!)); + React.useLayoutEffect(() => { + node.setRect(layout.getBoundingClientRect(selfRef.current!)); - if (tabStripRef.current) { - node.setTabStripRect(layout.getBoundingClientRect(tabStripRef.current!)); - } + if (tabStripRef.current) { + node.setTabStripRect(layout.getBoundingClientRect(tabStripRef.current!)); + } - const newContentRect = layout.getBoundingClientRect(contentRef.current!); - if ( - !node.getContentRect().equals(newContentRect) && - !isNaN(newContentRect.x) - ) { - node.setContentRect(newContentRect); - if (splitterDragging) { - // next movement will draw tabs again, only redraw after pause/end - if (timer.current) { - clearTimeout(timer.current); + const newContentRect = layout.getBoundingClientRect(contentRef.current!); + if (!node.getContentRect().equals(newContentRect) && !isNaN(newContentRect.x)) { + node.setContentRect(newContentRect); + if (splitterDragging) { // next movement will draw tabs again, only redraw after pause/end + if (timer.current) { + clearTimeout(timer.current); + } + timer.current = setTimeout(() => { + layout.redrawInternal("border content rect " + newContentRect); + timer.current = undefined; + }, 50); + } else { + layout.redrawInternal("border content rect " + newContentRect); + } } - timer.current = setTimeout(() => { - layout.redrawInternal('border content rect ' + newContentRect); - timer.current = undefined; - }, 50); - } else { - layout.redrawInternal('border content rect ' + newContentRect); - } - } - }); - - // this must be after the useEffect, so the node rect is already set (else window popin will not position tabs correctly) - const { - selfRef, - userControlledPositionRef, - onScroll, - onScrollPointerDown, - hiddenTabs, - onMouseWheel, - isDockStickyButtons, - isShowHiddenTabs, - } = useTabOverflow( - layout, - node, - Orientation.HORZ, - tabStripInnerRef, - miniScrollRef, - layout.getClassName(CLASSES.FLEXLAYOUT__TAB_BUTTON), - ); - - const onOverflowClick = ( - event: React.MouseEvent, - ) => { - const callback = layout.getShowOverflowMenu(); - const items = hiddenTabs.map((h) => { - return { index: h, node: node.getChildren()[h] as TabNode }; }); - if (callback !== undefined) { - callback(node, event, items, onOverflowItemSelect); - } else { - const element = overflowbuttonRef.current!; - showPopup(element, node, items, onOverflowItemSelect, layout); - } - event.stopPropagation(); - }; - const onOverflowItemSelect = (item: { node: TabNode; index: number }) => { - layout.doAction(Actions.selectTab(item.node.getId())); - userControlledPositionRef.current = false; - }; + // this must be after the useEffect, so the node rect is already set (else window popin will not position tabs correctly) + const { selfRef, userControlledPositionRef, onScroll, onScrollPointerDown, hiddenTabs, onMouseWheel, isDockStickyButtons, isShowHiddenTabs } = + useTabOverflow(layout, node, Orientation.HORZ, tabStripInnerRef, miniScrollRef, + layout.getClassName(CLASSES.FLEXLAYOUT__TAB_BUTTON)); + + const onOverflowClick = (event: React.MouseEvent) => { + const callback = layout.getShowOverflowMenu(); + const items = hiddenTabs.map(h => { return { index: h, node: (node.getChildren()[h] as TabNode) }; }); + if (callback !== undefined) { + callback(node, event, items, onOverflowItemSelect); + } else { + const element = overflowbuttonRef.current!; + showPopup( + element, + node, + items, + onOverflowItemSelect, + layout + ); + } + event.stopPropagation(); + }; + + const onOverflowItemSelect = (item: { node: TabNode; index: number }) => { + layout.doAction(Actions.selectTab(item.node.getId())); + userControlledPositionRef.current = false; + }; + + const onDragStart = (event: React.DragEvent) => { + if (!layout.getEditingTab()) { + if (node.isEnableDrag()) { + event.stopPropagation(); + layout.setDragNode(event.nativeEvent, node as TabSetNode); + } else { + event.preventDefault(); + } + } else { + event.preventDefault(); + } + }; + + const onPointerDown = (event: React.PointerEvent) => { + if (!isAuxMouseEvent(event)) { + layout.doAction(Actions.setActiveTabset(node.getId(), layout.getWindowId())); + } + }; + + const onAuxMouseClick = (event: React.MouseEvent) => { + if (isAuxMouseEvent(event)) { + layout.auxMouseClick(node, event); + } + }; + + const onContextMenu = (event: React.MouseEvent) => { + layout.showContextMenu(node, event); + }; - const onDragStart = (event: React.DragEvent) => { - if (!layout.getEditingTab()) { - if (node.isEnableDrag()) { + const onInterceptPointerDown = (event: React.PointerEvent) => { event.stopPropagation(); - layout.setDragNode(event.nativeEvent, node as TabSetNode); - } else { - event.preventDefault(); - } - } else { - event.preventDefault(); + }; + + const onMaximizeToggle = (event: React.MouseEvent) => { + if (node.canMaximize()) { + layout.maximize(node); + } + event.stopPropagation(); + }; + + const onClose = (event: React.MouseEvent) => { + layout.doAction(Actions.deleteTabset(node.getId())); + event.stopPropagation(); + }; + + const onCloseTab = (event: React.MouseEvent) => { + layout.doAction(Actions.deleteTab(node.getChildren()[0].getId())); + event.stopPropagation(); + }; + + const onPopoutTab = (event: React.MouseEvent) => { + if (selectedTabNode !== undefined) { + layout.doAction(Actions.popoutTab(selectedTabNode.getId())); + // layout.doAction(Actions.popoutTabset(node.getId())); + } + event.stopPropagation(); + }; + + const onDoubleClick = (event: React.MouseEvent) => { + if (node.canMaximize()) { + layout.maximize(node); + } + }; + + // Start Render + + const cm = layout.getClassName; + const selectedTabNode: TabNode = node.getSelectedNode() as TabNode; + const path = node.getPath(); + + const tabs = []; + if (node.isEnableTabStrip()) { + for (let i = 0; i < node.getChildren().length; i++) { + const child = node.getChildren()[i] as TabNode; + const isSelected = node.getSelected() === i; + tabs.push( + ); + if (i < node.getChildren().length - 1) { + tabs.push( +
+ ); + } + } } - }; - const onPointerDown = (event: React.PointerEvent) => { - if (!isAuxMouseEvent(event)) { - layout.doAction( - Actions.setActiveTabset(node.getId(), layout.getWindowId()), - ); + let leading : React.ReactNode = undefined; + let stickyButtons: React.ReactNode[] = []; + let buttons: React.ReactNode[] = []; + + // allow customization of header contents and buttons + const renderState: ITabSetRenderValues = { leading, stickyButtons, buttons, overflowPosition: undefined }; + layout.customizeTabSet(node, renderState); + leading = renderState.leading; + stickyButtons = renderState.stickyButtons; + buttons = renderState.buttons; + + const isTabStretch = node.isEnableSingleTabStretch() && node.getChildren().length === 1; + const showClose = (isTabStretch && ((node.getChildren()[0] as TabNode).isEnableClose())) || node.isEnableClose(); + + if (renderState.overflowPosition === undefined) { + renderState.overflowPosition = stickyButtons.length; } - }; - const onAuxMouseClick = ( - event: React.MouseEvent, - ) => { - if (isAuxMouseEvent(event)) { - layout.auxMouseClick(node, event); + if (stickyButtons.length > 0) { + if (!node.isEnableTabWrap() && (isDockStickyButtons || isTabStretch)) { + buttons = [...stickyButtons, ...buttons]; + } else { + tabs.push(
{ e.preventDefault() }} + className={cm(CLASSES.FLEXLAYOUT__TAB_TOOLBAR_STICKY_BUTTONS_CONTAINER)} + > + {stickyButtons} +
); + } } - }; - const onContextMenu = (event: React.MouseEvent) => { - layout.showContextMenu(node, event); - }; + if (!node.isEnableTabWrap()) { + if (isShowHiddenTabs) { + const overflowTitle = layout.i18nName(I18nLabel.Overflow_Menu_Tooltip); + let overflowContent; + if (typeof icons.more === "function") { + const items = hiddenTabs.map(h => { return { index: h, node: (node.getChildren()[h] as TabNode) }; }); + overflowContent = icons.more(node, items); + } else { + overflowContent = (<> + {icons.more} +
{hiddenTabs.length > 0 ? hiddenTabs.length : ""}
+ ); + } + buttons.splice(Math.min(renderState.overflowPosition, buttons.length), 0, + + ); + } + } - const onInterceptPointerDown = (event: React.PointerEvent) => { - event.stopPropagation(); - }; + if (selectedTabNode !== undefined && + layout.isSupportsPopout() && + selectedTabNode.isEnablePopout() && + selectedTabNode.isEnablePopoutIcon()) { + + const popoutTitle = layout.i18nName(I18nLabel.Popout_Tab); + buttons.push( + + ); + } - const onMaximizeToggle = ( - event: React.MouseEvent, - ) => { if (node.canMaximize()) { - layout.maximize(node); - } - event.stopPropagation(); - }; - - const onClose = (event: React.MouseEvent) => { - layout.doAction(Actions.deleteTabset(node.getId())); - event.stopPropagation(); - }; - - const onCloseTab = (event: React.MouseEvent) => { - layout.doAction(Actions.deleteTab(node.getChildren()[0].getId())); - event.stopPropagation(); - }; - - const onPopoutTab = (event: React.MouseEvent) => { - if (selectedTabNode !== undefined) { - layout.doAction(Actions.popoutTab(selectedTabNode.getId())); - // layout.doAction(Actions.popoutTabset(node.getId())); + const minTitle = layout.i18nName(I18nLabel.Restore); + const maxTitle = layout.i18nName(I18nLabel.Maximize); + buttons.push( + + ); } - event.stopPropagation(); - }; - const onDoubleClick = (event: React.MouseEvent) => { - if (node.canMaximize()) { - layout.maximize(node); + if (!node.isMaximized() && showClose) { + const title = isTabStretch ? layout.i18nName(I18nLabel.Close_Tab) : layout.i18nName(I18nLabel.Close_Tabset); + buttons.push( + + ); } - }; - - // Start Render - - const cm = layout.getClassName; - const selectedTabNode: TabNode = node.getSelectedNode() as TabNode; - const path = node.getPath(); - - const tabs = []; - if (node.isEnableTabStrip()) { - for (let i = 0; i < node.getChildren().length; i++) { - const child = node.getChildren()[i] as TabNode; - const isSelected = node.getSelected() === i; - tabs.push( - , - ); - if (i < node.getChildren().length - 1) { - tabs.push( -
, + + if (node.isActive() && node.isEnableActiveIcon()) { + const title = layout.i18nName(I18nLabel.Active_Tabset); + buttons.push( +
+ {(typeof icons.activeTabset === "function") ? icons.activeTabset(node) : icons.activeTabset} +
); - } } - } - - let leading: React.ReactNode = undefined; - let stickyButtons: React.ReactNode[] = []; - let buttons: React.ReactNode[] = []; - - // allow customization of header contents and buttons - const renderState: ITabSetRenderValues = { - leading, - stickyButtons, - buttons, - overflowPosition: undefined, - }; - layout.customizeTabSet(node, renderState); - leading = renderState.leading; - stickyButtons = renderState.stickyButtons; - buttons = renderState.buttons; - - const isTabStretch = - node.isEnableSingleTabStretch() && node.getChildren().length === 1; - const showClose = - (isTabStretch && (node.getChildren()[0] as TabNode).isEnableClose()) || - node.isEnableClose(); - - if (renderState.overflowPosition === undefined) { - renderState.overflowPosition = stickyButtons.length; - } - - if (stickyButtons.length > 0) { - if (!node.isEnableTabWrap() && (isDockStickyButtons || isTabStretch)) { - buttons = [...stickyButtons, ...buttons]; - } else { - tabs.push( -
{ - e.preventDefault(); - }} - className={cm( - CLASSES.FLEXLAYOUT__TAB_TOOLBAR_STICKY_BUTTONS_CONTAINER, - )} + + const buttonbar = ( +
{ e.preventDefault() }} > - {stickyButtons} -
, - ); + {buttons} +
+ ); + + let tabStrip; + + let tabStripClasses = cm(CLASSES.FLEXLAYOUT__TABSET_TABBAR_OUTER); + if (node.getClassNameTabStrip() !== undefined) { + tabStripClasses += " " + node.getClassNameTabStrip(); + } + tabStripClasses += " " + CLASSES.FLEXLAYOUT__TABSET_TABBAR_OUTER_ + node.getTabLocation(); + + if (node.isActive()) { + tabStripClasses += " " + cm(CLASSES.FLEXLAYOUT__TABSET_SELECTED); + } + + if (node.isMaximized()) { + tabStripClasses += " " + cm(CLASSES.FLEXLAYOUT__TABSET_MAXIMIZED); + } + + if (isTabStretch) { + const tabNode = node.getChildren()[0] as TabNode; + if (tabNode.getTabSetClassName() !== undefined) { + tabStripClasses += " " + tabNode.getTabSetClassName(); + } } - } - - if (!node.isEnableTabWrap()) { - if (isShowHiddenTabs) { - const overflowTitle = layout.i18nName(I18nLabel.Overflow_Menu_Tooltip); - let overflowContent; - if (typeof icons.more === 'function') { - const items = hiddenTabs.map((h) => { - return { index: h, node: node.getChildren()[h] as TabNode }; - }); - overflowContent = icons.more(node, items); - } else { - overflowContent = ( - <> - {icons.more} -
- {hiddenTabs.length > 0 ? hiddenTabs.length : ''} + + let leadingContainer: React.ReactNode = undefined; + if (leading) { + leadingContainer = ( +
+ {leading}
- ); - } - buttons.splice( - Math.min(renderState.overflowPosition, buttons.length), - 0, - , - ); } - } - - if ( - selectedTabNode !== undefined && - layout.isSupportsPopout() && - selectedTabNode.isEnablePopout() && - selectedTabNode.isEnablePopoutIcon() - ) { - const popoutTitle = layout.i18nName(I18nLabel.Popout_Tab); - buttons.push( - , - ); - } - - if (node.canMaximize()) { - const minTitle = layout.i18nName(I18nLabel.Restore); - const maxTitle = layout.i18nName(I18nLabel.Maximize); - buttons.push( - , - ); - } - - if (!node.isMaximized() && showClose) { - const title = isTabStretch - ? layout.i18nName(I18nLabel.Close_Tab) - : layout.i18nName(I18nLabel.Close_Tabset); - buttons.push( - , - ); - } - - if (node.isActive() && node.isEnableActiveIcon()) { - const title = layout.i18nName(I18nLabel.Active_Tabset); - buttons.push( -
- {typeof icons.activeTabset === 'function' - ? icons.activeTabset(node) - : icons.activeTabset} -
, - ); - } - - const buttonbar = ( -
{ - e.preventDefault(); - }} - > - {buttons} -
- ); - - let tabStrip; - - let tabStripClasses = cm(CLASSES.FLEXLAYOUT__TABSET_TABBAR_OUTER); - if (node.getClassNameTabStrip() !== undefined) { - tabStripClasses += ' ' + node.getClassNameTabStrip(); - } - tabStripClasses += - ' ' + CLASSES.FLEXLAYOUT__TABSET_TABBAR_OUTER_ + node.getTabLocation(); + } - if (node.isActive()) { - tabStripClasses += ' ' + cm(CLASSES.FLEXLAYOUT__TABSET_SELECTED); - } + let emptyTabset: React.ReactNode; + if (node.getChildren().length === 0) { + const placeHolderCallback = layout.getTabSetPlaceHolderCallback(); + if (placeHolderCallback) { + emptyTabset = placeHolderCallback(node); + } + } - if (node.isMaximized()) { - tabStripClasses += ' ' + cm(CLASSES.FLEXLAYOUT__TABSET_MAXIMIZED); - } + let content =
+ {emptyTabset} +
- if (isTabStretch) { - const tabNode = node.getChildren()[0] as TabNode; - if (tabNode.getTabSetClassName() !== undefined) { - tabStripClasses += ' ' + tabNode.getTabSetClassName(); + if (node.getTabLocation() === "top") { + content = <>{tabStrip}{content}; + } else { + content = <>{content}{tabStrip}; } - } - let leadingContainer: React.ReactNode = undefined; - if (leading) { - leadingContainer = ( -
{leading}
- ); - } + const style: Record = { + flexGrow: Math.max(1, node.getWeight() * 1000), + minWidth: node.getMinWidth(), + minHeight: node.getMinHeight(), + maxWidth: node.getMaxWidth(), + maxHeight: node.getMaxHeight() + }; - if (node.isEnableTabWrap()) { - if (node.isEnableTabStrip()) { - tabStrip = ( -
- {leadingContainer} - {tabs} -
- {buttonbar} -
- ); + if (node.getModel().getMaximizedTabset(layout.getWindowId()) !== undefined && !node.isMaximized()) { + style.display = "none"; } - } else { - if (node.isEnableTabStrip()) { - let miniScrollbar = undefined; - if (node.isEnableTabScrollbar()) { - miniScrollbar = ( -
- ); - } - tabStrip = ( -
- {leadingContainer} -
-
-
- {tabs} -
+ {content}
- {miniScrollbar} -
- {buttonbar}
- ); - } - } - - let emptyTabset: React.ReactNode; - if (node.getChildren().length === 0) { - const placeHolderCallback = layout.getTabSetPlaceHolderCallback(); - if (placeHolderCallback) { - emptyTabset = placeHolderCallback(node); - } - } - - let content = ( -
- {emptyTabset} -
- ); - - if (node.getTabLocation() === 'top') { - content = ( - <> - {tabStrip} - {content} - - ); - } else { - content = ( - <> - {content} - {tabStrip} - ); - } - - const style: Record = { - flexGrow: Math.max(1, node.getWeight() * 1000), - minWidth: node.getMinWidth(), - minHeight: node.getMinHeight(), - maxWidth: node.getMaxWidth(), - maxHeight: node.getMaxHeight(), - }; - - if ( - node.getModel().getMaximizedTabset(layout.getWindowId()) !== undefined && - !node.isMaximized() - ) { - style.display = 'none'; - } - - // note: tabset container is needed to allow flexbox to size without border/padding/margin - // then inner tabset can have border/padding/margin for styling - const tabset = ( -
-
- {content} -
-
- ); - - if (node.isMaximized()) { - if (layout.getMainElement()) { - return createPortal( -
- {tabset} -
, - layout.getMainElement()!, - ); + + if (node.isMaximized()) { + if (layout.getMainElement()) { + return createPortal( +
+ {tabset} +
, layout.getMainElement()!); + } else { + return tabset; + } } else { - return tabset; + return tabset; } - } else { - return tabset; - } + }; + + diff --git a/src/view/Utils.tsx b/src/view/Utils.tsx index fb33364f..7c9654f5 100644 --- a/src/view/Utils.tsx +++ b/src/view/Utils.tsx @@ -1,182 +1,135 @@ -import * as React from 'react'; -import { Node } from '../model/Node'; -import { TabNode } from '../model/TabNode'; -import { LayoutInternal } from './Layout'; -import { TabSetNode } from '../model/TabSetNode'; +import * as React from "react"; +import { Node } from "../model/Node"; +import { TabNode } from "../model/TabNode"; +import { LayoutInternal } from "./Layout"; +import { TabSetNode } from "../model/TabSetNode"; /** @internal */ export function isDesktop() { - const desktop = - typeof window !== 'undefined' && - window.matchMedia && - window.matchMedia('(hover: hover) and (pointer: fine)').matches; - return desktop; + const desktop = typeof window !== "undefined" && window.matchMedia && window.matchMedia("(hover: hover) and (pointer: fine)").matches; + return desktop; } /** @internal */ export function getRenderStateEx( - layout: LayoutInternal, - node: TabNode, - iconAngle?: number, + layout: LayoutInternal, + node: TabNode, + iconAngle?: number ) { - let leadingContent = undefined; - const titleContent: React.ReactNode = node.getName(); - const name = node.getName(); - if (iconAngle === undefined) { - iconAngle = 0; - } + let leadingContent = undefined; + const titleContent: React.ReactNode = node.getName(); + const name = node.getName(); + if (iconAngle === undefined) { + iconAngle = 0; + } - if (leadingContent === undefined && node.getIcon() !== undefined) { - if (iconAngle !== 0) { - leadingContent = ( - leadingContent - ); - } else { - leadingContent = ( - leadingContent - ); + if (leadingContent === undefined && node.getIcon() !== undefined) { + if (iconAngle !== 0) { + leadingContent = leadingContent; + } else { + leadingContent = leadingContent; + } } - } - const buttons: any[] = []; + const buttons: any[] = []; - // allow customization of leading contents (icon) and contents - const renderState = { - leading: leadingContent, - content: titleContent, - name, - buttons, - }; - layout.customizeTab(node, renderState); + // allow customization of leading contents (icon) and contents + const renderState = { leading: leadingContent, content: titleContent, name, buttons }; + layout.customizeTab(node, renderState); - node.setRenderedName(renderState.name); + node.setRenderedName(renderState.name); - return renderState; + return renderState; } /** @internal */ -export function isAuxMouseEvent( - event: - | React.MouseEvent - | React.TouchEvent, -) { - let auxEvent = false; - if (event.nativeEvent instanceof MouseEvent) { - if ( - event.nativeEvent.button !== 0 || - event.ctrlKey || - event.altKey || - event.metaKey || - event.shiftKey - ) { - auxEvent = true; +export function isAuxMouseEvent(event: React.MouseEvent | React.TouchEvent) { + let auxEvent = false; + if (event.nativeEvent instanceof MouseEvent) { + if (event.nativeEvent.button !== 0 || event.ctrlKey || event.altKey || event.metaKey || event.shiftKey) { + auxEvent = true; + } } - } - return auxEvent; + return auxEvent; } -export function enablePointerOnIFrames( - enable: boolean, - currentDocument: Document, -) { - const iframes = [ - ...getElementsByTagName('iframe', currentDocument), - ...getElementsByTagName('webview', currentDocument), - ]; +export function enablePointerOnIFrames(enable: boolean, currentDocument: Document) { + const iframes = [ + ...getElementsByTagName('iframe', currentDocument), + ...getElementsByTagName('webview', currentDocument), + ]; - for (const iframe of iframes) { - (iframe as HTMLElement).style.pointerEvents = enable ? 'auto' : 'none'; - } -} + for (const iframe of iframes) { + (iframe as HTMLElement).style.pointerEvents = enable ? 'auto' : 'none'; + } +}; -export function getElementsByTagName( - tag: string, - currentDocument: Document, -): Element[] { - return [...currentDocument.getElementsByTagName(tag)]; +export function getElementsByTagName(tag: string, currentDocument: Document): Element[] { + return [...currentDocument.getElementsByTagName(tag)]; } export function startDrag( - doc: Document, - event: React.PointerEvent, - drag: (x: number, y: number) => void, - dragEnd: () => void, - dragCancel: () => void, -) { - event.preventDefault(); - - const pointerMove = (ev: PointerEvent) => { - ev.preventDefault(); - drag(ev.clientX, ev.clientY); - }; - - const pointerCancel = (ev: PointerEvent) => { - ev.preventDefault(); - dragCancel(); - }; - const pointerUp = () => { - doc.removeEventListener('pointermove', pointerMove); - doc.removeEventListener('pointerup', pointerUp); - doc.removeEventListener('pointercancel', pointerCancel); - dragEnd(); - }; - - doc.addEventListener('pointermove', pointerMove); - doc.addEventListener('pointerup', pointerUp); - doc.addEventListener('pointercancel', pointerCancel); + doc: Document, + event: React.PointerEvent, + drag: (x: number, y: number) => void, + dragEnd: () => void, + dragCancel: () => void) { + + event.preventDefault(); + + const pointerMove = (ev: PointerEvent) => { + ev.preventDefault(); + drag(ev.clientX, ev.clientY); + }; + + const pointerCancel = (ev: PointerEvent) => { + ev.preventDefault(); + dragCancel(); + }; + const pointerUp = () => { + doc.removeEventListener("pointermove", pointerMove); + doc.removeEventListener("pointerup", pointerUp); + doc.removeEventListener("pointercancel", pointerCancel); + dragEnd(); + }; + + doc.addEventListener("pointermove", pointerMove); + doc.addEventListener("pointerup", pointerUp); + doc.addEventListener('pointercancel', pointerCancel); } export function canDockToWindow(node: Node) { - if (node instanceof TabNode) { - return node.isEnablePopout(); - } else if (node instanceof TabSetNode) { - for (const child of node.getChildren()) { - if ((child as TabNode).isEnablePopout() === false) { - return false; - } + if (node instanceof TabNode) { + return node.isEnablePopout(); + } else if (node instanceof TabSetNode) { + for (const child of node.getChildren()) { + if ((child as TabNode).isEnablePopout() === false) { + return false; + } + } + return true; } - return true; - } - return false; + return false; } -export function copyInlineStyles( - source: HTMLElement, - target: HTMLElement, -): boolean { - // Get the inline style attribute from the source element - const sourceStyle = source.getAttribute('style'); - const targetStyle = target.getAttribute('style'); - if (sourceStyle === targetStyle) return false; - - // console.log("copyInlineStyles", sourceStyle); - - if (sourceStyle) { - // Set the style attribute on the target element - target.setAttribute('style', sourceStyle); - } else { - // If the source has no inline style, clear the target's style attribute - target.removeAttribute('style'); - } - return true; +export function copyInlineStyles(source: HTMLElement, target: HTMLElement): boolean { + // Get the inline style attribute from the source element + const sourceStyle = source.getAttribute('style'); + const targetStyle = target.getAttribute('style'); + if (sourceStyle === targetStyle) return false; + + // console.log("copyInlineStyles", sourceStyle); + + if (sourceStyle) { + // Set the style attribute on the target element + target.setAttribute('style', sourceStyle); + } else { + // If the source has no inline style, clear the target's style attribute + target.removeAttribute('style'); + } + return true; } export function isSafari() { - const userAgent = navigator.userAgent; - return ( - userAgent.includes('Safari') && - !userAgent.includes('Chrome') && - !userAgent.includes('Chromium') - ); -} + const userAgent = navigator.userAgent; + return userAgent.includes("Safari") && !userAgent.includes("Chrome") && !userAgent.includes("Chromium"); + }