mirror of
https://github.com/mathuo/dockview
synced 2025-05-08 04:28:24 +00:00
Compare commits
No commits in common. "master" and "v1.8.0" have entirely different histories.
@ -1,8 +1,6 @@
|
||||
{
|
||||
"packages": [
|
||||
"packages/dockview-core",
|
||||
"packages/dockview-vue",
|
||||
"packages/dockview-react",
|
||||
"packages/dockview"
|
||||
],
|
||||
"sandboxes": [
|
||||
@ -11,29 +9,18 @@
|
||||
"/packages/docs/sandboxes/demo-dockview",
|
||||
"/packages/docs/sandboxes/dnd-dockview",
|
||||
"/packages/docs/sandboxes/dockview-app",
|
||||
"/packages/docs/sandboxes/editor-gridview",
|
||||
"/packages/docs/sandboxes/events-dockview",
|
||||
"/packages/docs/sandboxes/externaldnd-dockview",
|
||||
"/packages/docs/sandboxes/floatinggroup-dockview",
|
||||
"/packages/docs/sandboxes/fullwidthtab-dockview",
|
||||
"/packages/docs/sandboxes/headeractions-dockview",
|
||||
"/packages/docs/sandboxes/groupcontol-dockview",
|
||||
"/packages/docs/sandboxes/iframe-dockview",
|
||||
"/packages/docs/sandboxes/keyboard-dockview",
|
||||
"/packages/docs/sandboxes/layout-dockview",
|
||||
"/packages/docs/sandboxes/lockedgroup-dockview",
|
||||
"/packages/docs/sandboxes/maximizegroup-dockview",
|
||||
"/packages/docs/sandboxes/nativeapp-dockview",
|
||||
"/packages/docs/sandboxes/nested-dockview",
|
||||
"/packages/docs/sandboxes/popoutgroup-dockview",
|
||||
"/packages/docs/sandboxes/rendering-dockview",
|
||||
"/packages/docs/sandboxes/rendermode-dockview",
|
||||
"/packages/docs/sandboxes/resize-dockview",
|
||||
"/packages/docs/sandboxes/resizecontainer-dockview",
|
||||
"/packages/docs/sandboxes/scrollbars-dockview",
|
||||
"/packages/docs/sandboxes/simple-dockview",
|
||||
"/packages/docs/sandboxes/simple-gridview",
|
||||
"/packages/docs/sandboxes/simple-paneview",
|
||||
"/packages/docs/sandboxes/tabheight-dockview",
|
||||
"/packages/docs/sandboxes/updatetitle-dockview",
|
||||
"/packages/docs/sandboxes/watermark-dockview",
|
||||
@ -42,5 +29,5 @@
|
||||
"/packages/docs/sandboxes/javascript/tabheight-dockview",
|
||||
"/packages/docs/sandboxes/javascript/vanilla-dockview"
|
||||
],
|
||||
"node": "18"
|
||||
}
|
||||
"node": "16"
|
||||
}
|
8
.github/workflows/codeql-analysis.yml
vendored
8
.github/workflows/codeql-analysis.yml
vendored
@ -30,11 +30,11 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
uses: github/codeql-action/init@v1
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
@ -45,7 +45,7 @@ jobs:
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
@ -59,4 +59,4 @@ jobs:
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
uses: github/codeql-action/analyze@v1
|
||||
|
15
.github/workflows/deploy-docs.yml
vendored
15
.github/workflows/deploy-docs.yml
vendored
@ -1,20 +1,21 @@
|
||||
name: Deploy Docs
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 3 * * *' # every day at 3 am UTC
|
||||
|
||||
jobs:
|
||||
deploy-nightly-demo-app:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout 🛎️
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: '20.x'
|
||||
node-version: '16.x'
|
||||
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
|
||||
@ -26,10 +27,6 @@ jobs:
|
||||
working-directory: packages/dockview-core
|
||||
- run: npm run build
|
||||
working-directory: packages/dockview
|
||||
- run: npm run build
|
||||
working-directory: packages/dockview-vue
|
||||
- run: npm run build
|
||||
working-directory: packages/dockview-react
|
||||
- run: npm run build
|
||||
working-directory: packages/docs
|
||||
- run: npm run docs
|
||||
|
11
.github/workflows/main.yml
vendored
11
.github/workflows/main.yml
vendored
@ -7,16 +7,16 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
# might be required for sonar to work correctly
|
||||
with:
|
||||
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: '20.x'
|
||||
node-version: '16.x'
|
||||
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
|
||||
@ -24,10 +24,11 @@ jobs:
|
||||
${{ runner.os }}-node-
|
||||
|
||||
- run: yarn
|
||||
- run: npm run bootstrap
|
||||
- run: npm run build
|
||||
- run: npm run test:cov
|
||||
- name: SonarCloud Scan
|
||||
uses: sonarsource/sonarqube-scan-action@v5
|
||||
uses: SonarSource/sonarcloud-github-action@master
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
|
||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||
|
78
.github/workflows/publish.yml
vendored
78
.github/workflows/publish.yml
vendored
@ -1,78 +0,0 @@
|
||||
name: Publish to npm
|
||||
|
||||
env:
|
||||
NPM_CONFIG_PROVENANCE: true
|
||||
on:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types: [published]
|
||||
jobs:
|
||||
publish:
|
||||
if: github.event_name == 'release'
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
permissions:
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20.x'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-node-
|
||||
|
||||
- run: yarn
|
||||
- name: Publish dockview-core
|
||||
run: npm publish --provenance
|
||||
working-directory: packages/dockview-core
|
||||
- name: Publish dockview
|
||||
run: npm publish --provenance
|
||||
working-directory: packages/dockview
|
||||
- name: Publish dockview-vue
|
||||
run: npm publish --provenance
|
||||
working-directory: packages/dockview-vue
|
||||
- name: Publish dockview-react
|
||||
run: npm publish --provenance
|
||||
working-directory: packages/dockview-react
|
||||
publish-experimental:
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
permissions:
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20.x'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-node-
|
||||
|
||||
- run: yarn
|
||||
- run: npm run set-experimental-versions
|
||||
- name: Publish dockview-core
|
||||
run: npm publish --provenance --tag experimental
|
||||
working-directory: packages/dockview-core
|
||||
- name: Publish dockview
|
||||
run: npm publish --provenance --tag experimental
|
||||
working-directory: packages/dockview
|
||||
- name: Publish dockview-vue
|
||||
run: npm publish --provenance --tag experimental
|
||||
working-directory: packages/dockview-vue
|
||||
- name: Publish dockview-react
|
||||
run: npm publish --provenance --tag experimental
|
||||
working-directory: packages/dockview-react
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -14,4 +14,3 @@ test-report.xml
|
||||
yarn-error.log
|
||||
/build
|
||||
/docs/
|
||||
/generated/
|
||||
|
3
.vscode/extensions.json
vendored
3
.vscode/extensions.json
vendored
@ -7,8 +7,7 @@
|
||||
"esbenp.prettier-vscode",
|
||||
"redhat.vscode-yaml",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"editorconfig.editorconfig",
|
||||
"vue.volar"
|
||||
"editorconfig.editorconfig"
|
||||
],
|
||||
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
|
||||
"unwantedRecommendations": []
|
||||
|
49
README.md
49
README.md
@ -1,18 +1,17 @@
|
||||
<div align="center">
|
||||
<h1>dockview</h1>
|
||||
|
||||
<p>Zero dependency layout manager supporting tabs, groups, grids and splitviews. Supports React, Vue and Vanilla TypeScript</p>
|
||||
<p>Zero dependency layout manager supporting tabs, grids and splitviews with ReactJS support written in TypeScript</p>
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
[](https://www.npmjs.com/package/dockview-core)
|
||||
[](https://www.npmjs.com/package/dockview-core)
|
||||
[](https://www.npmjs.com/package/dockview)
|
||||
[](https://github.com/mathuo/dockview/actions?query=workflow%3ACI)
|
||||
[](https://sonarcloud.io/summary/overall?id=mathuo_dockview)
|
||||
[](https://sonarcloud.io/summary/overall?id=mathuo_dockview)
|
||||
[](https://bundlephobia.com/result?p=dockview-core)
|
||||
[](https://bundlephobia.com/result?p=dockview)
|
||||
|
||||
##
|
||||
|
||||
@ -22,17 +21,33 @@ Please see the website: https://dockview.dev
|
||||
|
||||
## Features
|
||||
|
||||
- Serialization / deserialization with full layout management
|
||||
- Support for split-views, grid-views and 'dockable' views
|
||||
- Themeable and customizable
|
||||
- Tab and Group docking / Drag n' Drop
|
||||
- Popout Windows
|
||||
- Floating Groups
|
||||
- Extensive API
|
||||
- Supports Shadow DOMs
|
||||
- High test coverage
|
||||
- Documentation website with live examples
|
||||
- Transparent builds and Code Analysis
|
||||
- Security at mind - verifed publishing and builds through GitHub Actions
|
||||
- Simple splitviews, nested splitviews (i.e. gridviews) supporting full layout managment with
|
||||
dockable and tabular views
|
||||
- Extensive API support at the component level and view level
|
||||
- Themable and customizable
|
||||
- Serialization / deserialization support
|
||||
- Tabular docking and Drag and Drop support
|
||||
- Floating groups, customized header bars and tab
|
||||
- Documentation and examples
|
||||
|
||||
Want to verify our builds? Go [here](https://www.npmjs.com/package/dockview#user-content-provenance).
|
||||
Want to inspect the latest deployment? Go to https://unpkg.com/browse/dockview@latest/
|
||||
|
||||
## Quick start
|
||||
|
||||
Dockview has a peer dependency on `react >= 16.8.0` and `react-dom >= 16.8.0`. You can install dockview from [npm](https://www.npmjs.com/package/dockview).
|
||||
|
||||
```
|
||||
npm install --save dockview
|
||||
```
|
||||
|
||||
Within your project you must import or reference the stylesheet at `dockview/dist/styles/dockview.css` and attach a theme.
|
||||
|
||||
```css
|
||||
@import '~dockview/dist/styles/dockview.css';
|
||||
```
|
||||
|
||||
You should also attach a dockview theme to an element containing your components. For example:
|
||||
|
||||
```html
|
||||
<body classname="dockview-theme-dark"></body>
|
||||
```
|
||||
|
@ -1,8 +0,0 @@
|
||||
# Reporting a Vulnerability
|
||||
|
||||
- Dockview is an entirely open source project.
|
||||
- All build and publication scripts use public Github Action files found [here](https://github.com/mathuo/dockview/tree/master/.github/workflows).
|
||||
- All npm publications are verified through the use of [provenance statements](https://docs.npmjs.com/generating-provenance-statements/).
|
||||
- All builds are scanned with SonarCube and outputs can be found [here](https://sonarcloud.io/summary/overall?id=mathuo_dockview).
|
||||
|
||||
If you believe you have found a security or vulnerability issue please send a complete example to github.mathuo@gmail.com where it will be investigated.
|
@ -1 +0,0 @@
|
||||
import '@testing-library/jest-dom';
|
@ -8,7 +8,7 @@ const config: JestConfigWithTsJest = {
|
||||
collectCoverageFrom: ['<rootDir>/packages/*/src/**/*.{js,jsx,ts,tsx}'],
|
||||
coveragePathIgnorePatterns: [
|
||||
'/node_modules/',
|
||||
'<rootDir>/packages/*/src/__tests__/',
|
||||
'<rootDir>packages/*/src/__tests__/',
|
||||
],
|
||||
coverageDirectory: 'coverage',
|
||||
testResultsProcessor: 'jest-sonar-reporter',
|
||||
|
@ -2,11 +2,12 @@
|
||||
"packages": [
|
||||
"packages/*"
|
||||
],
|
||||
"version": "4.2.5",
|
||||
"useWorkspaces": true,
|
||||
"version": "1.8.0",
|
||||
"npmClient": "yarn",
|
||||
"command": {
|
||||
"publish": {
|
||||
"message": "chore(release): publish %s"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
116
package.json
116
package.json
@ -1,81 +1,71 @@
|
||||
{
|
||||
"name": "dockview-monorepo-root",
|
||||
"private": true,
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
"nohoist": [
|
||||
"**/babel-jest",
|
||||
"**/babel-jest/**"
|
||||
],
|
||||
"description": "Monorepo for https://github.com/mathuo/dockview",
|
||||
"homepage": "https://github.com/mathuo/dockview#readme",
|
||||
"bugs": {
|
||||
"url": "https://github.com/mathuo/dockview/issues"
|
||||
"scripts": {
|
||||
"test": "jest",
|
||||
"lint": "eslint packages/**/src/** --ext .ts,.tsx,.js,.jsx",
|
||||
"package": "node scripts/package.js",
|
||||
"package-all": "lerna run docs --scope '{dockview-core,dockview}' && node scripts/package.js",
|
||||
"build": "lerna run build --scope '{dockview-core,dockview}'",
|
||||
"clean": "lerna run clean",
|
||||
"bootstrap": "lerna bootstrap",
|
||||
"test:cov": "jest --coverage",
|
||||
"version-beta-build": "lerna version prerelease --preid beta",
|
||||
"publish-app": "lerna publish",
|
||||
"docs": "typedoc",
|
||||
"package-docs": "node scripts/package-docs.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/mathuo/dockview.git"
|
||||
},
|
||||
"license": "MIT",
|
||||
"author": "https://github.com/mathuo",
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "lerna run build --scope '{dockview-core,dockview,dockview-vue,dockview-react}'",
|
||||
"clean": "lerna run clean",
|
||||
"docs": "typedoc",
|
||||
"generate-docs": "node scripts/docs.mjs",
|
||||
"lint": "eslint packages/**/src/** --ext .ts,.tsx,.js,.jsx",
|
||||
"package": "node scripts/package.js",
|
||||
"package-docs": "node scripts/package-docs.js",
|
||||
"set-experimental-versions": "node scripts/set-experimental-versions",
|
||||
"test": "jest",
|
||||
"test:cov": "jest --coverage",
|
||||
"version": "lerna version"
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/react": "^18.2.46",
|
||||
"@types/react-dom": "^18.2.18"
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/mathuo/dockview/issues"
|
||||
},
|
||||
"homepage": "https://github.com/mathuo/dockview#readme",
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"@rollup/plugin-terser": "^0.4.4",
|
||||
"@rollup/plugin-typescript": "^11.1.5",
|
||||
"@testing-library/dom": "^9.3.3",
|
||||
"@testing-library/jest-dom": "^6.1.6",
|
||||
"@testing-library/react": "^14.1.2",
|
||||
"@total-typescript/shoehorn": "^0.1.1",
|
||||
"@types/jest": "^29.5.11",
|
||||
"@types/react": "^18.2.46",
|
||||
"@types/react-dom": "^18.2.18",
|
||||
"@typescript-eslint/eslint-plugin": "^6.17.0",
|
||||
"@typescript-eslint/parser": "^6.17.0",
|
||||
"@vitejs/plugin-vue": "^5.0.4",
|
||||
"@vue/tsconfig": "^0.5.1",
|
||||
"concurrently": "^8.2.2",
|
||||
"@testing-library/dom": "^8.20.0",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@types/jest": "^29.4.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.52.0",
|
||||
"@typescript-eslint/parser": "^5.52.0",
|
||||
"codecov": "^3.8.3",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8.56.0",
|
||||
"fs-extra": "^11.2.0",
|
||||
"css-loader": "^6.7.3",
|
||||
"eslint": "^8.34.0",
|
||||
"fs-extra": "^11.1.0",
|
||||
"gulp": "^4.0.2",
|
||||
"gulp-concat": "^2.6.1",
|
||||
"gulp-dart-sass": "^1.1.0",
|
||||
"jest": "^29.7.0",
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"gulp-dart-sass": "^1.0.2",
|
||||
"jest": "^29.5.0",
|
||||
"jest-environment-jsdom": "^29.4.3",
|
||||
"jest-sonar-reporter": "^2.0.0",
|
||||
"jsdom": "^23.0.1",
|
||||
"lerna": "^8.2.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"rimraf": "^5.0.5",
|
||||
"rollup": "^4.9.2",
|
||||
"rollup-plugin-postcss": "^4.0.2",
|
||||
"ts-jest": "^29.1.1",
|
||||
"ts-loader": "^9.5.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"tslib": "^2.6.2",
|
||||
"typedoc": "^0.25.6",
|
||||
"typescript": "^5.3.3",
|
||||
"vite": "^5.1.5",
|
||||
"vue": "^3.4.21",
|
||||
"vue-sfc-loader": "^0.1.0",
|
||||
"vue-tsc": "^2.0.5"
|
||||
"jsdom": "^21.1.0",
|
||||
"lerna": "^6.5.1",
|
||||
"merge2": "^1.4.1",
|
||||
"rimraf": "^4.1.2",
|
||||
"sass": "^1.58.1",
|
||||
"sass-loader": "^13.2.0",
|
||||
"style-loader": "^3.3.1",
|
||||
"ts-jest": "^29.0.5",
|
||||
"ts-loader": "^9.4.2",
|
||||
"ts-node": "^10.9.1",
|
||||
"tslib": "^2.5.0",
|
||||
"typedoc": "^0.24.7",
|
||||
"typescript": "^4.9.5",
|
||||
"webpack": "^5.75.0",
|
||||
"webpack-cli": "^5.0.1",
|
||||
"webpack-dev-server": "^4.11.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0"
|
||||
}
|
||||
"dependencies": {}
|
||||
}
|
||||
|
@ -1,56 +0,0 @@
|
||||
<div align="center">
|
||||
<h1>dockview</h1>
|
||||
|
||||
<p>Zero dependency layout manager supporting tabs, groups, grids and splitviews with ReactJS support written in TypeScript</p>
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
[](https://www.npmjs.com/package/dockview)
|
||||
[](https://www.npmjs.com/package/dockview)
|
||||
[](https://github.com/mathuo/dockview/actions?query=workflow%3ACI)
|
||||
[](https://sonarcloud.io/summary/overall?id=mathuo_dockview)
|
||||
[](https://sonarcloud.io/summary/overall?id=mathuo_dockview)
|
||||
[](https://bundlephobia.com/result?p=dockview)
|
||||
|
||||
##
|
||||
|
||||
Please see the website: https://dockview.dev
|
||||
|
||||
## Features
|
||||
|
||||
- Serialization / deserialization with full layout management
|
||||
- Support for split-views, grid-views and 'dockable' views
|
||||
- Themeable and customizable
|
||||
- Tab and Group docking / Drag n' Drop
|
||||
- Popout Windows
|
||||
- Floating Groups
|
||||
- Extensive API
|
||||
- Supports Shadow DOMs
|
||||
- High test coverage
|
||||
- Documentation website with live examples
|
||||
- Transparent builds and Code Analysis
|
||||
- Security at mind - verifed publishing and builds through GitHub Actions
|
||||
|
||||
Want to verify our builds? Go [here](https://www.npmjs.com/package/dockview#Provenance).
|
||||
|
||||
## Quick start
|
||||
|
||||
Dockview has a peer dependency on `react >= 16.8.0` and `react-dom >= 16.8.0`. You can install dockview from [npm](https://www.npmjs.com/package/dockview).
|
||||
|
||||
```
|
||||
npm install --save dockview
|
||||
```
|
||||
|
||||
Within your project you must import or reference the stylesheet at `dockview/dist/styles/dockview.css` and attach a theme.
|
||||
|
||||
```css
|
||||
@import '~dockview/dist/styles/dockview.css';
|
||||
```
|
||||
|
||||
You should also attach a dockview theme to an element containing your components. For example:
|
||||
|
||||
```html
|
||||
<body classname="dockview-theme-dark"></body>
|
||||
```
|
@ -1,34 +0,0 @@
|
||||
import { JestConfigWithTsJest } from 'ts-jest';
|
||||
|
||||
const config: JestConfigWithTsJest = {
|
||||
preset: 'ts-jest',
|
||||
roots: ['<rootDir>/packages/dockview-angular'],
|
||||
modulePaths: ['<rootDir>/packages/dockview-angular/src'],
|
||||
displayName: { name: 'dockview-angular', color: 'blue' },
|
||||
rootDir: '../../',
|
||||
collectCoverageFrom: [
|
||||
'<rootDir>/packages/dockview-angular/src/**/*.{js,jsx,ts,tsx}',
|
||||
],
|
||||
setupFiles: [
|
||||
// '<rootDir>/packages/dockview-angular/src/__tests__/__mocks__/resizeObserver.js',
|
||||
],
|
||||
setupFilesAfterEnv: ['<rootDir>/jest-setup.ts'],
|
||||
coveragePathIgnorePatterns: ['/node_modules/'],
|
||||
modulePathIgnorePatterns: [
|
||||
// '<rootDir>/packages/dockview-angular/src/__tests__/__mocks__',
|
||||
// '<rootDir>/packages/dockview-angular/src/__tests__/__test_utils__',
|
||||
],
|
||||
coverageDirectory: '<rootDir>/packages/dockview-angular/coverage/',
|
||||
testResultsProcessor: 'jest-sonar-reporter',
|
||||
testEnvironment: 'jsdom',
|
||||
transform: {
|
||||
'^.+\\.tsx?$': [
|
||||
'ts-jest',
|
||||
{
|
||||
tsconfig: '<rootDir>/tsconfig.test.json',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
@ -1,59 +0,0 @@
|
||||
{
|
||||
"name": "dockview-angular",
|
||||
"version": "4.2.5",
|
||||
"description": "Zero dependency layout manager supporting tabs, grids and splitviews",
|
||||
"keywords": [
|
||||
"splitview",
|
||||
"split-view",
|
||||
"gridview",
|
||||
"grid-view",
|
||||
"dockview",
|
||||
"dock-view",
|
||||
"grid",
|
||||
"tabs",
|
||||
"layout",
|
||||
"layout manager",
|
||||
"dock layout",
|
||||
"dock",
|
||||
"docking",
|
||||
"splitter",
|
||||
"drag-and-drop",
|
||||
"drag",
|
||||
"drop",
|
||||
"react",
|
||||
"react-component"
|
||||
],
|
||||
"homepage": "https://github.com/mathuo/dockview",
|
||||
"bugs": {
|
||||
"url": "https://github.com/mathuo/dockview/issues"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/mathuo/dockview.git"
|
||||
},
|
||||
"license": "MIT",
|
||||
"author": "https://github.com/mathuo",
|
||||
"main": "./dist/cjs/index.js",
|
||||
"module": "./dist/esm/index.js",
|
||||
"types": "./dist/cjs/index.d.ts",
|
||||
"files": [
|
||||
"dist",
|
||||
"README.md"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "npm run build:package && npm run build:bundles",
|
||||
"build:bundles": "rollup -c",
|
||||
"build:cjs": "cross-env ../../node_modules/.bin/tsc --build ./tsconfig.json --verbose --extendedDiagnostics",
|
||||
"build:css": "gulp sass",
|
||||
"build:esm": "cross-env ../../node_modules/.bin/tsc --build ./tsconfig.esm.json --verbose --extendedDiagnostics",
|
||||
"build:package": "npm run build:cjs && npm run build:esm && npm run build:css",
|
||||
"clean": "rimraf dist/ .build/ .rollup.cache/",
|
||||
"prepublishOnly": "npm run rebuild && npm run test",
|
||||
"rebuild": "npm run clean && npm run build",
|
||||
"test": "cross-env ../../node_modules/.bin/jest --selectProjects dockview",
|
||||
"test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview --coverage"
|
||||
},
|
||||
"dependencies": {
|
||||
"dockview-core": "^4.2.5"
|
||||
}
|
||||
}
|
@ -1,113 +0,0 @@
|
||||
/* eslint-disable */
|
||||
|
||||
const { join } = require('path');
|
||||
const typescript = require('@rollup/plugin-typescript');
|
||||
const terser = require('@rollup/plugin-terser');
|
||||
const postcss = require('rollup-plugin-postcss');
|
||||
const nodeResolve = require('@rollup/plugin-node-resolve');
|
||||
|
||||
const { name, version, homepage, license } = require('./package.json');
|
||||
const main = join(__dirname, './scripts/rollupEntryTarget.ts');
|
||||
const mainNoStyles = join(__dirname, './src/index.ts');
|
||||
const outputDir = join(__dirname, 'dist');
|
||||
|
||||
function outputFile(format, isMinified, withStyles) {
|
||||
let filename = join(outputDir, name);
|
||||
|
||||
if (format !== 'umd') {
|
||||
filename += `.${format}`;
|
||||
}
|
||||
if (isMinified) {
|
||||
filename += '.min';
|
||||
}
|
||||
if (!withStyles) {
|
||||
filename += '.noStyle';
|
||||
}
|
||||
|
||||
return `${filename}.js`;
|
||||
}
|
||||
|
||||
function getInput(options) {
|
||||
const { withStyles } = options;
|
||||
|
||||
if (withStyles) {
|
||||
return main;
|
||||
}
|
||||
|
||||
return mainNoStyles;
|
||||
}
|
||||
|
||||
function createBundle(format, options) {
|
||||
const { withStyles, isMinified } = options;
|
||||
const input = getInput(options);
|
||||
const file = outputFile(format, isMinified, withStyles);
|
||||
|
||||
const external = [];
|
||||
|
||||
const output = {
|
||||
file,
|
||||
format,
|
||||
sourcemap: true,
|
||||
globals: {},
|
||||
banner: [
|
||||
`/**`,
|
||||
` * ${name}`,
|
||||
` * @version ${version}`,
|
||||
` * @link ${homepage}`,
|
||||
` * @license ${license}`,
|
||||
` */`,
|
||||
].join('\n'),
|
||||
};
|
||||
|
||||
const plugins = [
|
||||
nodeResolve({
|
||||
include: ['node_modules/dockview-core/**'],
|
||||
}),
|
||||
typescript({
|
||||
tsconfig: 'tsconfig.esm.json',
|
||||
}),
|
||||
];
|
||||
|
||||
if (isMinified) {
|
||||
plugins.push(terser());
|
||||
}
|
||||
if (withStyles) {
|
||||
plugins.push(postcss());
|
||||
}
|
||||
|
||||
if (format === 'umd') {
|
||||
output['name'] = name;
|
||||
}
|
||||
|
||||
external.push('react', 'react-dom');
|
||||
|
||||
if (format === 'umd') {
|
||||
output.globals['react'] = 'React';
|
||||
output.globals['react-dom'] = 'ReactDOM';
|
||||
}
|
||||
|
||||
return {
|
||||
input,
|
||||
output,
|
||||
plugins,
|
||||
external,
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = [
|
||||
// amd
|
||||
createBundle('amd', { withStyles: false, isMinified: false }),
|
||||
createBundle('amd', { withStyles: true, isMinified: false }),
|
||||
createBundle('amd', { withStyles: false, isMinified: true }),
|
||||
createBundle('amd', { withStyles: true, isMinified: true }),
|
||||
// umd
|
||||
createBundle('umd', { withStyles: false, isMinified: false }),
|
||||
createBundle('umd', { withStyles: true, isMinified: false }),
|
||||
createBundle('umd', { withStyles: false, isMinified: true }),
|
||||
createBundle('umd', { withStyles: true, isMinified: true }),
|
||||
// cjs
|
||||
createBundle('cjs', { withStyles: true, isMinified: false }),
|
||||
// esm
|
||||
createBundle('esm', { withStyles: true, isMinified: false }),
|
||||
createBundle('esm', { withStyles: true, isMinified: true }),
|
||||
];
|
@ -1,2 +0,0 @@
|
||||
import '../dist/styles/dockview.css';
|
||||
export * from '../src/index';
|
@ -1,5 +0,0 @@
|
||||
describe('empty', () => {
|
||||
test('that passes', () => {
|
||||
expect(true).toBeTruthy();
|
||||
});
|
||||
});
|
@ -1 +0,0 @@
|
||||
export * from 'dockview-core';
|
@ -1,14 +0,0 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"module": "ES2020",
|
||||
"moduleResolution": "node",
|
||||
"target": "es6",
|
||||
"outDir": "dist/esm",
|
||||
"tsBuildInfoFile": ".build/tsconfig.tsbuildinfo.esm",
|
||||
"jsx": "react",
|
||||
"rootDir": "src"
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["**/node_modules", "src/__tests__"]
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist/cjs",
|
||||
"tsBuildInfoFile": ".build/tsconfig.tsbuildinfo.cjs",
|
||||
"jsx": "react",
|
||||
"rootDir": "src"
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["**/node_modules", "src/__tests__"]
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
{
|
||||
"extends": ["../../typedoc.base.json"],
|
||||
"entryPoints": ["src/index.ts"],
|
||||
"exclude": ["**/dist/**"]
|
||||
}
|
@ -1,14 +1,13 @@
|
||||
<div align="center">
|
||||
<h1>dockview</h1>
|
||||
|
||||
<p>Zero dependency layout manager supporting tabs, groups, grids and splitviews. Supports React, Vue and Vanilla TypeScript</p>
|
||||
<p>Zero dependency layout manager supporting tabs, groups, grids and splitviews written in TypeScript</p>
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
[](https://www.npmjs.com/package/dockview)
|
||||
[](https://www.npmjs.com/package/dockview)
|
||||
[](https://github.com/mathuo/dockview/actions?query=workflow%3ACI)
|
||||
[](https://sonarcloud.io/summary/overall?id=mathuo_dockview)
|
||||
[](https://sonarcloud.io/summary/overall?id=mathuo_dockview)
|
||||
@ -16,23 +15,37 @@
|
||||
|
||||
##
|
||||
|
||||

|
||||
|
||||
Please see the website: https://dockview.dev
|
||||
|
||||
## Features
|
||||
|
||||
- Serialization / deserialization with full layout management
|
||||
- Support for split-views, grid-views and 'dockable' views
|
||||
- Themeable and customizable
|
||||
- Tab and Group docking / Drag n' Drop
|
||||
- Popout Windows
|
||||
- Floating Groups
|
||||
- Extensive API
|
||||
- Supports Shadow DOMs
|
||||
- High test coverage
|
||||
- Documentation website with live examples
|
||||
- Transparent builds and Code Analysis
|
||||
- Security at mind - verifed publishing and builds through GitHub Actions
|
||||
- Simple splitviews, nested splitviews (i.e. gridviews) supporting full layout managment with
|
||||
dockable and tabular views
|
||||
- Extensive API support at the component level and view level
|
||||
- Themable and customizable
|
||||
- Serialization / deserialization support
|
||||
- Tabular docking and Drag and Drop support
|
||||
- Floating groups, customized header bars and tab
|
||||
- Documentation and examples
|
||||
|
||||
Want to verify our builds? Go [here](https://www.npmjs.com/package/dockview#Provenance).
|
||||
Want to inspect the latest deployment? Go to https://unpkg.com/browse/dockview-core@latest/
|
||||
|
||||
## Quick start
|
||||
|
||||
You can install dockview-core from [npm](https://www.npmjs.com/package/dockview-core).
|
||||
|
||||
```
|
||||
npm install --save dockview-core
|
||||
```
|
||||
|
||||
Within your project you must import or reference the stylesheet at `dockview-core/dist/styles/dockview.css` and attach a theme.
|
||||
|
||||
```css
|
||||
@import '~dockview-core/dist/styles/dockview.css';
|
||||
```
|
||||
|
||||
You should also attach a dockview theme to an element containing your components. For example:
|
||||
|
||||
```html
|
||||
<body classname="dockview-theme-dark"></body>
|
||||
```
|
||||
|
@ -1,13 +1,6 @@
|
||||
const gulp = require('gulp');
|
||||
const gulpSass = require('gulp-dart-sass');
|
||||
const concat = require('gulp-concat');
|
||||
const buildfile = require('../../scripts/build');
|
||||
|
||||
gulp.task('sass', () => {
|
||||
return gulp
|
||||
.src('./src/**/*.scss')
|
||||
.pipe(gulpSass().on('error', gulpSass.logError))
|
||||
.pipe(concat('dockview.css'))
|
||||
.pipe(gulp.dest('./dist/styles/'));
|
||||
});
|
||||
buildfile.init();
|
||||
|
||||
gulp.task('run', gulp.series(['sass']));
|
||||
|
@ -12,7 +12,6 @@ const config: JestConfigWithTsJest = {
|
||||
setupFiles: [
|
||||
'<rootDir>/packages/dockview-core/src/__tests__/__mocks__/resizeObserver.js',
|
||||
],
|
||||
setupFilesAfterEnv: ['<rootDir>/jest-setup.ts'],
|
||||
coveragePathIgnorePatterns: ['/node_modules/'],
|
||||
modulePathIgnorePatterns: [
|
||||
'<rootDir>/packages/dockview-core/src/__tests__/__mocks__',
|
||||
|
@ -1,7 +1,37 @@
|
||||
{
|
||||
"name": "dockview-core",
|
||||
"version": "4.2.5",
|
||||
"description": "Zero dependency layout manager supporting tabs, grids and splitviews",
|
||||
"version": "1.8.0",
|
||||
"description": "Zero dependency layout manager supporting tabs, grids and splitviews with ReactJS support",
|
||||
"main": "./dist/cjs/index.js",
|
||||
"types": "./dist/cjs/index.d.ts",
|
||||
"module": "./dist/esm/index.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/mathuo/dockview.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/mathuo/dockview/issues"
|
||||
},
|
||||
"homepage": "https://github.com/mathuo/dockview",
|
||||
"scripts": {
|
||||
"build:package": "npm run build:cjs && npm run build:esm && npm run build:css",
|
||||
"build:cjs": "cross-env ../../node_modules/.bin/tsc --project ./tsconfig.json --extendedDiagnostics",
|
||||
"build:css": "gulp sass",
|
||||
"build:esm": "cross-env ../../node_modules/.bin/tsc --project ./tsconfig.esm.json --extendedDiagnostics",
|
||||
"build:bundles": "rollup -c",
|
||||
"build": "npm run build:package && npm run build:bundles",
|
||||
"clean": "rimraf dist/ .build/ .rollup.cache/",
|
||||
"prepublishOnly": "npm run rebuild && npm run test",
|
||||
"docs": "typedoc",
|
||||
"rebuild": "npm run clean && npm run build",
|
||||
"test": "cross-env ../../node_modules/.bin/jest --selectProjects dockview-core",
|
||||
"test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview-core --coverage",
|
||||
"dev-publish": "node ./scripts/publishExperimental.js"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"README.md"
|
||||
],
|
||||
"keywords": [
|
||||
"splitview",
|
||||
"split-view",
|
||||
@ -23,34 +53,16 @@
|
||||
"react",
|
||||
"react-component"
|
||||
],
|
||||
"homepage": "https://github.com/mathuo/dockview",
|
||||
"bugs": {
|
||||
"url": "https://github.com/mathuo/dockview/issues"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/mathuo/dockview.git"
|
||||
},
|
||||
"license": "MIT",
|
||||
"author": "https://github.com/mathuo",
|
||||
"main": "./dist/cjs/index.js",
|
||||
"module": "./dist/esm/index.js",
|
||||
"types": "./dist/cjs/index.d.ts",
|
||||
"files": [
|
||||
"dist",
|
||||
"README.md"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "npm run build:package && npm run build:bundles",
|
||||
"build:bundles": "rollup -c",
|
||||
"build:cjs": "cross-env ../../node_modules/.bin/tsc --build ./tsconfig.json --verbose --extendedDiagnostics",
|
||||
"build:css": "gulp sass",
|
||||
"build:esm": "cross-env ../../node_modules/.bin/tsc --build ./tsconfig.esm.json --verbose --extendedDiagnostics",
|
||||
"build:package": "npm run build:cjs && npm run build:esm && npm run build:css",
|
||||
"clean": "rimraf dist/ .build/ .rollup.cache/",
|
||||
"prepublishOnly": "npm run rebuild && npm run test",
|
||||
"rebuild": "npm run clean && npm run build",
|
||||
"test": "cross-env ../../node_modules/.bin/jest --selectProjects dockview-core",
|
||||
"test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview-core --coverage"
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-terser": "^0.4.0",
|
||||
"@rollup/plugin-typescript": "^11.0.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"postcss": "^8.4.21",
|
||||
"rimraf": "^4.1.2",
|
||||
"rollup": "^3.15.0",
|
||||
"rollup-plugin-postcss": "^4.0.2",
|
||||
"typedoc": "^0.23.25"
|
||||
}
|
||||
}
|
||||
|
63
packages/dockview-core/scripts/publishExperimental.js
Normal file
63
packages/dockview-core/scripts/publishExperimental.js
Normal file
@ -0,0 +1,63 @@
|
||||
const cp = require('child_process');
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
|
||||
const rootDir = path.join(__dirname, '..');
|
||||
const publishDir = path.join(rootDir, '__publish__');
|
||||
|
||||
cp.execSync('npm run clean', { cwd: rootDir, stdio: 'inherit' });
|
||||
cp.execSync('npm run test', { cwd: __dirname, stdio: 'inherit' });
|
||||
cp.execSync('npm run build', { cwd: rootDir, stdio: 'inherit' });
|
||||
|
||||
if (fs.existsSync(publishDir)) {
|
||||
fs.removeSync(publishDir);
|
||||
}
|
||||
fs.mkdirSync(publishDir);
|
||||
|
||||
if (!fs.existsSync(path.join(publishDir, 'dist'))) {
|
||||
fs.mkdirSync(path.join(publishDir, 'dist'));
|
||||
}
|
||||
|
||||
const package = JSON.parse(
|
||||
fs.readFileSync(path.join(rootDir, 'package.json')).toString()
|
||||
);
|
||||
|
||||
for (const file of package.files) {
|
||||
fs.copySync(path.join(rootDir, file), path.join(publishDir, file));
|
||||
}
|
||||
|
||||
const result = cp
|
||||
.execSync('git rev-parse --short HEAD', {
|
||||
cwd: rootDir,
|
||||
})
|
||||
.toString()
|
||||
.replace(/\n/g, '');
|
||||
|
||||
function formatDate() {
|
||||
const date = new Date();
|
||||
|
||||
function pad(value) {
|
||||
if (value.toString().length === 1) {
|
||||
return `0${value}`;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
return `${date.getFullYear()}${pad(date.getMonth() + 1)}${pad(
|
||||
date.getDate()
|
||||
)}`;
|
||||
}
|
||||
|
||||
package.version = `0.0.0-experimental-${result}-${formatDate()}`;
|
||||
package.scripts = {};
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(publishDir, 'package.json'),
|
||||
JSON.stringify(package, null, 4)
|
||||
);
|
||||
|
||||
const command = 'npm publish --tag experimental';
|
||||
|
||||
cp.execSync(command, { cwd: publishDir, stdio: 'inherit' });
|
||||
|
||||
fs.removeSync(publishDir);
|
@ -1,28 +1,23 @@
|
||||
import { IDockviewPanelModel } from '../../dockview/dockviewPanelModel';
|
||||
import { DockviewGroupPanel } from '../../dockview/dockviewGroupPanel';
|
||||
import {
|
||||
TabPartInitParameters,
|
||||
GroupPanelPartInitParameters,
|
||||
IContentRenderer,
|
||||
ITabRenderer,
|
||||
} from '../../dockview/types';
|
||||
import { PanelUpdateEvent } from '../../panel/types';
|
||||
import { TabLocation } from '../../dockview/framework';
|
||||
|
||||
export class DockviewPanelModelMock implements IDockviewPanelModel {
|
||||
constructor(
|
||||
readonly contentComponent: string,
|
||||
readonly content: IContentRenderer,
|
||||
readonly tabComponent: string,
|
||||
readonly tab: ITabRenderer
|
||||
readonly tabComponent?: string,
|
||||
readonly tab?: ITabRenderer
|
||||
) {
|
||||
//
|
||||
}
|
||||
|
||||
createTabRenderer(tabLocation: TabLocation): ITabRenderer {
|
||||
return this.tab;
|
||||
}
|
||||
|
||||
init(params: TabPartInitParameters): void {
|
||||
init(params: GroupPanelPartInitParameters): void {
|
||||
//
|
||||
}
|
||||
|
@ -1,45 +0,0 @@
|
||||
import { fromPartial } from '@total-typescript/shoehorn';
|
||||
|
||||
export function setupMockWindow() {
|
||||
const listeners: Record<string, (() => void)[]> = {};
|
||||
|
||||
let width = 1000;
|
||||
let height = 2000;
|
||||
|
||||
return fromPartial<Window>({
|
||||
addEventListener: (type: string, listener: () => void) => {
|
||||
if (!listeners[type]) {
|
||||
listeners[type] = [];
|
||||
}
|
||||
listeners[type].push(listener);
|
||||
if (type === 'load') {
|
||||
listener();
|
||||
}
|
||||
},
|
||||
removeEventListener: (type: string, listener: () => void) => {
|
||||
if (listeners[type]) {
|
||||
const index = listeners[type].indexOf(listener);
|
||||
if (index > -1) {
|
||||
listeners[type].splice(index, 1);
|
||||
}
|
||||
}
|
||||
},
|
||||
dispatchEvent: (event: Event) => {
|
||||
const items = listeners[event.type];
|
||||
if (!items) {
|
||||
return;
|
||||
}
|
||||
items.forEach((item) => item());
|
||||
},
|
||||
document: document,
|
||||
close: () => {
|
||||
listeners['beforeunload']?.forEach((f) => f());
|
||||
},
|
||||
get innerWidth() {
|
||||
return width++;
|
||||
},
|
||||
get innerHeight() {
|
||||
return height++;
|
||||
},
|
||||
});
|
||||
}
|
@ -1,13 +1,4 @@
|
||||
import React from 'react';
|
||||
|
||||
/**
|
||||
* useful utility type to erase readonly signatures for testing purposes
|
||||
*
|
||||
* @see https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html#readonly-mapped-type-modifiers-and-readonly-arrays
|
||||
*/
|
||||
export type Writable<T> = T extends object
|
||||
? { -readonly [K in keyof T]: Writable<T[K]> }
|
||||
: T;
|
||||
import * as React from 'react';
|
||||
|
||||
export function setMockRefElement(node: Partial<HTMLElement>): void {
|
||||
const mockRef = {
|
||||
@ -21,53 +12,3 @@ export function setMockRefElement(node: Partial<HTMLElement>): void {
|
||||
|
||||
jest.spyOn(React, 'useRef').mockReturnValueOnce(mockRef);
|
||||
}
|
||||
|
||||
export function createOffsetDragOverEvent(params: {
|
||||
clientX: number;
|
||||
clientY: number;
|
||||
}): Event {
|
||||
const event = new Event('dragover', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
});
|
||||
Object.defineProperty(event, 'clientX', { get: () => params.clientX });
|
||||
Object.defineProperty(event, 'clientY', { get: () => params.clientY });
|
||||
return event;
|
||||
}
|
||||
|
||||
/**
|
||||
* `jest.runAllTicks` doesn't seem to exhaust all events in the micro-task queue so
|
||||
* as a **hacky** alternative we'll wait for an empty Promise to complete which runs
|
||||
* on the micro-task queue so will force a run-to-completion emptying the queue
|
||||
* of any pending micro-task
|
||||
*/
|
||||
export function exhaustMicrotaskQueue(): Promise<void> {
|
||||
return new Promise<void>((resolve) => resolve());
|
||||
}
|
||||
|
||||
export const mockGetBoundingClientRect = ({
|
||||
left,
|
||||
top,
|
||||
height,
|
||||
width,
|
||||
}: {
|
||||
left: number;
|
||||
top: number;
|
||||
height: number;
|
||||
width: number;
|
||||
}) => {
|
||||
const result = {
|
||||
left,
|
||||
top,
|
||||
height,
|
||||
width,
|
||||
right: left + width,
|
||||
bottom: top + height,
|
||||
x: left,
|
||||
y: top,
|
||||
};
|
||||
return {
|
||||
...result,
|
||||
toJSON: () => result,
|
||||
};
|
||||
};
|
||||
|
@ -5,7 +5,7 @@ describe('api', () => {
|
||||
let api: PanelApiImpl;
|
||||
|
||||
beforeEach(() => {
|
||||
api = new PanelApiImpl('dummy_id', 'fake-component');
|
||||
api = new PanelApiImpl('dummy_id');
|
||||
});
|
||||
|
||||
test('updateParameters', () => {
|
||||
|
@ -1,17 +1,15 @@
|
||||
import { DockviewPanelApiImpl } from '../../api/dockviewPanelApi';
|
||||
import { DockviewComponent } from '../../dockview/dockviewComponent';
|
||||
import { DockviewPanel } from '../../dockview/dockviewPanel';
|
||||
import { DockviewPanel, IDockviewPanel } from '../../dockview/dockviewPanel';
|
||||
import { DockviewGroupPanel } from '../../dockview/dockviewGroupPanel';
|
||||
import { fromPartial } from '@total-typescript/shoehorn';
|
||||
|
||||
describe('groupPanelApi', () => {
|
||||
test('title', () => {
|
||||
const accessor = fromPartial<DockviewComponent>({
|
||||
const accessor: Partial<DockviewComponent> = {
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
options: {},
|
||||
onDidOptionsChange: jest.fn(),
|
||||
});
|
||||
};
|
||||
|
||||
const panelMock = jest.fn<DockviewPanel, []>(() => {
|
||||
return {
|
||||
@ -19,21 +17,17 @@ describe('groupPanelApi', () => {
|
||||
setTitle: jest.fn(),
|
||||
} as any;
|
||||
});
|
||||
const groupMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||
return {} as any;
|
||||
});
|
||||
|
||||
const panel = new panelMock();
|
||||
const group = fromPartial<DockviewGroupPanel>({
|
||||
api: {
|
||||
onDidVisibilityChange: jest.fn(),
|
||||
onDidLocationChange: jest.fn(),
|
||||
onDidActiveChange: jest.fn(),
|
||||
},
|
||||
});
|
||||
const group = new groupMock();
|
||||
|
||||
const cut = new DockviewPanelApiImpl(
|
||||
panel,
|
||||
group,
|
||||
<DockviewComponent>accessor,
|
||||
'fake-component'
|
||||
<DockviewComponent>accessor
|
||||
);
|
||||
|
||||
cut.setTitle('test_title');
|
||||
@ -42,18 +36,16 @@ describe('groupPanelApi', () => {
|
||||
});
|
||||
|
||||
test('updateParameters', () => {
|
||||
const groupPanel: Partial<DockviewPanel> = {
|
||||
const groupPanel: Partial<IDockviewPanel> = {
|
||||
id: 'test_id',
|
||||
update: jest.fn(),
|
||||
};
|
||||
|
||||
const accessor = fromPartial<DockviewComponent>({
|
||||
const accessor: Partial<DockviewComponent> = {
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
options: {},
|
||||
onDidOptionsChange: jest.fn(),
|
||||
});
|
||||
|
||||
};
|
||||
const groupViewPanel = new DockviewGroupPanel(
|
||||
<DockviewComponent>accessor,
|
||||
'',
|
||||
@ -61,10 +53,9 @@ describe('groupPanelApi', () => {
|
||||
);
|
||||
|
||||
const cut = new DockviewPanelApiImpl(
|
||||
<DockviewPanel>groupPanel,
|
||||
<IDockviewPanel>groupPanel,
|
||||
<DockviewGroupPanel>groupViewPanel,
|
||||
<DockviewComponent>accessor,
|
||||
'fake-component'
|
||||
<DockviewComponent>accessor
|
||||
);
|
||||
|
||||
cut.updateParameters({ keyA: 'valueA' });
|
||||
@ -76,17 +67,15 @@ describe('groupPanelApi', () => {
|
||||
});
|
||||
|
||||
test('onDidGroupChange', () => {
|
||||
const groupPanel: Partial<DockviewPanel> = {
|
||||
const groupPanel: Partial<IDockviewPanel> = {
|
||||
id: 'test_id',
|
||||
};
|
||||
|
||||
const accessor = fromPartial<DockviewComponent>({
|
||||
const accessor: Partial<DockviewComponent> = {
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
options: {},
|
||||
onDidOptionsChange: jest.fn(),
|
||||
});
|
||||
|
||||
};
|
||||
const groupViewPanel = new DockviewGroupPanel(
|
||||
<DockviewComponent>accessor,
|
||||
'',
|
||||
@ -94,10 +83,9 @@ describe('groupPanelApi', () => {
|
||||
);
|
||||
|
||||
const cut = new DockviewPanelApiImpl(
|
||||
<DockviewPanel>groupPanel,
|
||||
<IDockviewPanel>groupPanel,
|
||||
<DockviewGroupPanel>groupViewPanel,
|
||||
<DockviewComponent>accessor,
|
||||
'fake-component'
|
||||
<DockviewComponent>accessor
|
||||
);
|
||||
|
||||
let events = 0;
|
||||
|
@ -70,8 +70,8 @@ describe('abstractDragHandler', () => {
|
||||
expect(span.style.pointerEvents).toBeFalsy();
|
||||
|
||||
fireEvent.dragEnd(element);
|
||||
expect(iframe.style.pointerEvents).toBe('');
|
||||
expect(webview.style.pointerEvents).toBe('');
|
||||
expect(iframe.style.pointerEvents).toBe('auto');
|
||||
expect(webview.style.pointerEvents).toBe('auto');
|
||||
expect(span.style.pointerEvents).toBeFalsy();
|
||||
|
||||
handler.dispose();
|
||||
@ -114,8 +114,8 @@ describe('abstractDragHandler', () => {
|
||||
expect(span.style.pointerEvents).toBeFalsy();
|
||||
|
||||
handler.dispose();
|
||||
expect(iframe.style.pointerEvents).toBe('');
|
||||
expect(webview.style.pointerEvents).toBe('');
|
||||
expect(iframe.style.pointerEvents).toBe('auto');
|
||||
expect(webview.style.pointerEvents).toBe('auto');
|
||||
expect(span.style.pointerEvents).toBeFalsy();
|
||||
});
|
||||
|
||||
@ -172,7 +172,7 @@ describe('abstractDragHandler', () => {
|
||||
const event = new Event('dragstart');
|
||||
const spy = jest.spyOn(event, 'preventDefault');
|
||||
fireEvent(element, event);
|
||||
expect(spy).toHaveBeenCalledTimes(0);
|
||||
expect(spy).toBeCalledTimes(0);
|
||||
|
||||
handler.dispose();
|
||||
});
|
||||
|
@ -7,7 +7,19 @@ import {
|
||||
positionToDirection,
|
||||
} from '../../dnd/droptarget';
|
||||
import { fireEvent } from '@testing-library/dom';
|
||||
import { createOffsetDragOverEvent } from '../__test_utils__/utils';
|
||||
|
||||
function createOffsetDragOverEvent(params: {
|
||||
clientX: number;
|
||||
clientY: number;
|
||||
}): Event {
|
||||
const event = new Event('dragover', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
});
|
||||
Object.defineProperty(event, 'clientX', { get: () => params.clientX });
|
||||
Object.defineProperty(event, 'clientY', { get: () => params.clientY });
|
||||
return event;
|
||||
}
|
||||
|
||||
describe('droptarget', () => {
|
||||
let element: HTMLElement;
|
||||
@ -16,10 +28,10 @@ describe('droptarget', () => {
|
||||
beforeEach(() => {
|
||||
element = document.createElement('div');
|
||||
|
||||
jest.spyOn(element, 'offsetHeight', 'get').mockImplementation(
|
||||
jest.spyOn(element, 'clientHeight', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
jest.spyOn(element, 'offsetWidth', 'get').mockImplementation(() => 200);
|
||||
jest.spyOn(element, 'clientWidth', 'get').mockImplementation(() => 200);
|
||||
});
|
||||
|
||||
test('that dragover events are marked', () => {
|
||||
@ -53,7 +65,7 @@ describe('droptarget', () => {
|
||||
fireEvent.dragOver(element);
|
||||
|
||||
const target = element.querySelector(
|
||||
'.dv-drop-target-dropzone'
|
||||
'.drop-target-dropzone'
|
||||
) as HTMLElement;
|
||||
fireEvent.drop(target);
|
||||
expect(position).toBe('center');
|
||||
@ -61,7 +73,7 @@ describe('droptarget', () => {
|
||||
const event = new Event('dragover');
|
||||
(event as any)['__dockview_droptarget_event_is_used__'] = true;
|
||||
fireEvent(element, event);
|
||||
expect(element.querySelector('.dv-drop-target-dropzone')).toBeNull();
|
||||
expect(element.querySelector('.drop-target-dropzone')).toBeNull();
|
||||
});
|
||||
|
||||
test('directionToPosition', () => {
|
||||
@ -102,7 +114,7 @@ describe('droptarget', () => {
|
||||
fireEvent.dragOver(element);
|
||||
|
||||
const target = element.querySelector(
|
||||
'.dv-drop-target-dropzone'
|
||||
'.drop-target-dropzone'
|
||||
) as HTMLElement;
|
||||
fireEvent.drop(target);
|
||||
expect(position).toBe('center');
|
||||
@ -124,7 +136,7 @@ describe('droptarget', () => {
|
||||
fireEvent.dragOver(element);
|
||||
|
||||
const target = element.querySelector(
|
||||
'.dv-drop-target-dropzone'
|
||||
'.drop-target-dropzone'
|
||||
) as HTMLElement;
|
||||
|
||||
jest.spyOn(target, 'clientHeight', 'get').mockImplementation(() => 100);
|
||||
@ -155,12 +167,12 @@ describe('droptarget', () => {
|
||||
fireEvent.dragOver(element);
|
||||
|
||||
let viewQuery = element.querySelectorAll(
|
||||
'.dv-drop-target > .dv-drop-target-dropzone > .dv-drop-target-selection'
|
||||
'.drop-target > .drop-target-dropzone > .drop-target-selection'
|
||||
);
|
||||
expect(viewQuery.length).toBe(1);
|
||||
|
||||
const target = element.querySelector(
|
||||
'.dv-drop-target-dropzone'
|
||||
'.drop-target-dropzone'
|
||||
) as HTMLElement;
|
||||
|
||||
jest.spyOn(target, 'clientHeight', 'get').mockImplementation(() => 100);
|
||||
@ -171,37 +183,18 @@ describe('droptarget', () => {
|
||||
createOffsetDragOverEvent({ clientX: 19, clientY: 0 })
|
||||
);
|
||||
|
||||
function check(
|
||||
element: HTMLElement,
|
||||
box: {
|
||||
left: string;
|
||||
top: string;
|
||||
width: string;
|
||||
height: string;
|
||||
}
|
||||
) {
|
||||
expect(element.style.top).toBe(box.top);
|
||||
expect(element.style.left).toBe(box.left);
|
||||
expect(element.style.width).toBe(box.width);
|
||||
expect(element.style.height).toBe(box.height);
|
||||
}
|
||||
|
||||
viewQuery = element.querySelectorAll(
|
||||
'.dv-drop-target > .dv-drop-target-dropzone > .dv-drop-target-selection'
|
||||
'.drop-target > .drop-target-dropzone > .drop-target-selection'
|
||||
);
|
||||
expect(viewQuery.length).toBe(1);
|
||||
expect(droptarget.state).toBe('left');
|
||||
check(
|
||||
element
|
||||
.getElementsByClassName('dv-drop-target-selection')
|
||||
.item(0) as HTMLDivElement,
|
||||
{
|
||||
top: '0px',
|
||||
left: '0px',
|
||||
width: '50%',
|
||||
height: '100%',
|
||||
}
|
||||
);
|
||||
expect(
|
||||
(
|
||||
element
|
||||
.getElementsByClassName('drop-target-selection')
|
||||
.item(0) as HTMLDivElement
|
||||
).style.transform
|
||||
).toBe('translateX(-25%) scaleX(0.5)');
|
||||
|
||||
fireEvent(
|
||||
target,
|
||||
@ -209,21 +202,17 @@ describe('droptarget', () => {
|
||||
);
|
||||
|
||||
viewQuery = element.querySelectorAll(
|
||||
'.dv-drop-target > .dv-drop-target-dropzone > .dv-drop-target-selection'
|
||||
'.drop-target > .drop-target-dropzone > .drop-target-selection'
|
||||
);
|
||||
expect(viewQuery.length).toBe(1);
|
||||
expect(droptarget.state).toBe('top');
|
||||
check(
|
||||
element
|
||||
.getElementsByClassName('dv-drop-target-selection')
|
||||
.item(0) as HTMLDivElement,
|
||||
{
|
||||
top: '0px',
|
||||
left: '0px',
|
||||
width: '100%',
|
||||
height: '50%',
|
||||
}
|
||||
);
|
||||
expect(
|
||||
(
|
||||
element
|
||||
.getElementsByClassName('drop-target-selection')
|
||||
.item(0) as HTMLDivElement
|
||||
).style.transform
|
||||
).toBe('translateY(-25%) scaleY(0.5)');
|
||||
|
||||
fireEvent(
|
||||
target,
|
||||
@ -231,21 +220,17 @@ describe('droptarget', () => {
|
||||
);
|
||||
|
||||
viewQuery = element.querySelectorAll(
|
||||
'.dv-drop-target > .dv-drop-target-dropzone > .dv-drop-target-selection'
|
||||
'.drop-target > .drop-target-dropzone > .drop-target-selection'
|
||||
);
|
||||
expect(viewQuery.length).toBe(1);
|
||||
expect(droptarget.state).toBe('bottom');
|
||||
check(
|
||||
element
|
||||
.getElementsByClassName('dv-drop-target-selection')
|
||||
.item(0) as HTMLDivElement,
|
||||
{
|
||||
top: '50%',
|
||||
left: '0px',
|
||||
width: '100%',
|
||||
height: '50%',
|
||||
}
|
||||
);
|
||||
expect(
|
||||
(
|
||||
element
|
||||
.getElementsByClassName('drop-target-selection')
|
||||
.item(0) as HTMLDivElement
|
||||
).style.transform
|
||||
).toBe('translateY(25%) scaleY(0.5)');
|
||||
|
||||
fireEvent(
|
||||
target,
|
||||
@ -253,21 +238,18 @@ describe('droptarget', () => {
|
||||
);
|
||||
|
||||
viewQuery = element.querySelectorAll(
|
||||
'.dv-drop-target > .dv-drop-target-dropzone > .dv-drop-target-selection'
|
||||
'.drop-target > .drop-target-dropzone > .drop-target-selection'
|
||||
);
|
||||
expect(viewQuery.length).toBe(1);
|
||||
expect(droptarget.state).toBe('right');
|
||||
check(
|
||||
element
|
||||
.getElementsByClassName('dv-drop-target-selection')
|
||||
.item(0) as HTMLDivElement,
|
||||
{
|
||||
top: '0px',
|
||||
left: '50%',
|
||||
width: '50%',
|
||||
height: '100%',
|
||||
}
|
||||
);
|
||||
expect(
|
||||
(
|
||||
element
|
||||
.getElementsByClassName('drop-target-selection')
|
||||
.item(0) as HTMLDivElement
|
||||
).style.transform
|
||||
).toBe('translateX(25%) scaleX(0.5)');
|
||||
|
||||
fireEvent(
|
||||
target,
|
||||
createOffsetDragOverEvent({ clientX: 100, clientY: 50 })
|
||||
@ -276,14 +258,14 @@ describe('droptarget', () => {
|
||||
expect(
|
||||
(
|
||||
element
|
||||
.getElementsByClassName('dv-drop-target-selection')
|
||||
.getElementsByClassName('drop-target-selection')
|
||||
.item(0) as HTMLDivElement
|
||||
).style.transform
|
||||
).toBe('');
|
||||
|
||||
fireEvent.dragLeave(target);
|
||||
expect(droptarget.state).toBe('center');
|
||||
viewQuery = element.querySelectorAll('.dv-drop-target');
|
||||
viewQuery = element.querySelectorAll('.drop-target');
|
||||
expect(viewQuery.length).toBe(0);
|
||||
});
|
||||
|
||||
|
@ -2,7 +2,6 @@ import { fireEvent } from '@testing-library/dom';
|
||||
import { GroupDragHandler } from '../../dnd/groupDragHandler';
|
||||
import { DockviewGroupPanel } from '../../dockview/dockviewGroupPanel';
|
||||
import { LocalSelectionTransfer, PanelTransfer } from '../../dnd/dataTransfer';
|
||||
import { DockviewComponent } from '../../dockview/dockviewComponent';
|
||||
|
||||
describe('groupDragHandler', () => {
|
||||
test('that the dnd transfer object is setup and torndown', () => {
|
||||
@ -11,17 +10,13 @@ describe('groupDragHandler', () => {
|
||||
const groupMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||
const partial: Partial<DockviewGroupPanel> = {
|
||||
id: 'test_group_id',
|
||||
api: { location: { type: 'grid' } } as any,
|
||||
api: { isFloating: false } as any,
|
||||
};
|
||||
return partial as DockviewGroupPanel;
|
||||
});
|
||||
const group = new groupMock();
|
||||
|
||||
const cut = new GroupDragHandler(
|
||||
element,
|
||||
{ id: 'test_accessor_id' } as DockviewComponent,
|
||||
group
|
||||
);
|
||||
const cut = new GroupDragHandler(element, 'test_accessor_id', group);
|
||||
|
||||
fireEvent.dragStart(element, new Event('dragstart'));
|
||||
|
||||
@ -48,22 +43,18 @@ describe('groupDragHandler', () => {
|
||||
|
||||
cut.dispose();
|
||||
});
|
||||
test('that the event is cancelled when floating and shiftKey=true', () => {
|
||||
test('that the event is cancelled when isFloating and shiftKey=true', () => {
|
||||
const element = document.createElement('div');
|
||||
|
||||
const groupMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||
const partial: Partial<DockviewGroupPanel> = {
|
||||
api: { location: { type: 'floating' } } as any,
|
||||
api: { isFloating: true } as any,
|
||||
};
|
||||
return partial as DockviewGroupPanel;
|
||||
});
|
||||
const group = new groupMock();
|
||||
|
||||
const cut = new GroupDragHandler(
|
||||
element,
|
||||
{ id: 'accessor_id' } as DockviewComponent,
|
||||
group
|
||||
);
|
||||
const cut = new GroupDragHandler(element, 'accessor_id', group);
|
||||
|
||||
const event = new KeyboardEvent('dragstart', { shiftKey: false });
|
||||
|
||||
@ -85,17 +76,13 @@ describe('groupDragHandler', () => {
|
||||
|
||||
const groupMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||
const partial: Partial<DockviewGroupPanel> = {
|
||||
api: { location: { type: 'grid' } } as any,
|
||||
api: { isFloating: false } as any,
|
||||
};
|
||||
return partial as DockviewGroupPanel;
|
||||
});
|
||||
const group = new groupMock();
|
||||
|
||||
const cut = new GroupDragHandler(
|
||||
element,
|
||||
{ id: 'accessor_id' } as DockviewComponent,
|
||||
group
|
||||
);
|
||||
const cut = new GroupDragHandler(element, 'accessor_id', group);
|
||||
|
||||
const event = new KeyboardEvent('dragstart', { shiftKey: false });
|
||||
|
||||
|
156
packages/dockview-core/src/__tests__/dnd/overlay.spec.ts
Normal file
156
packages/dockview-core/src/__tests__/dnd/overlay.spec.ts
Normal file
@ -0,0 +1,156 @@
|
||||
import { Overlay } from '../../dnd/overlay';
|
||||
|
||||
describe('overlay', () => {
|
||||
test('toJSON', () => {
|
||||
const container = document.createElement('div');
|
||||
const content = document.createElement('div');
|
||||
|
||||
document.body.appendChild(container);
|
||||
container.appendChild(content);
|
||||
|
||||
const cut = new Overlay({
|
||||
height: 200,
|
||||
width: 100,
|
||||
left: 10,
|
||||
top: 20,
|
||||
minimumInViewportWidth: 0,
|
||||
minimumInViewportHeight: 0,
|
||||
container,
|
||||
content,
|
||||
});
|
||||
|
||||
jest.spyOn(
|
||||
container.childNodes.item(0) as HTMLElement,
|
||||
'getBoundingClientRect'
|
||||
).mockImplementation(() => {
|
||||
return { left: 80, top: 100, width: 40, height: 50 } as any;
|
||||
});
|
||||
jest.spyOn(container, 'getBoundingClientRect').mockImplementation(
|
||||
() => {
|
||||
return { left: 20, top: 30, width: 100, height: 100 } as any;
|
||||
}
|
||||
);
|
||||
|
||||
expect(cut.toJSON()).toEqual({
|
||||
top: 70,
|
||||
left: 60,
|
||||
width: 40,
|
||||
height: 50,
|
||||
});
|
||||
});
|
||||
|
||||
test('that out-of-bounds dimensions are fixed', () => {
|
||||
const container = document.createElement('div');
|
||||
const content = document.createElement('div');
|
||||
|
||||
document.body.appendChild(container);
|
||||
container.appendChild(content);
|
||||
|
||||
const cut = new Overlay({
|
||||
height: 200,
|
||||
width: 100,
|
||||
left: -1000,
|
||||
top: -1000,
|
||||
minimumInViewportWidth: 0,
|
||||
minimumInViewportHeight: 0,
|
||||
container,
|
||||
content,
|
||||
});
|
||||
|
||||
jest.spyOn(
|
||||
container.childNodes.item(0) as HTMLElement,
|
||||
'getBoundingClientRect'
|
||||
).mockImplementation(() => {
|
||||
return { left: 80, top: 100, width: 40, height: 50 } as any;
|
||||
});
|
||||
jest.spyOn(container, 'getBoundingClientRect').mockImplementation(
|
||||
() => {
|
||||
return { left: 20, top: 30, width: 100, height: 100 } as any;
|
||||
}
|
||||
);
|
||||
|
||||
expect(cut.toJSON()).toEqual({
|
||||
top: 70,
|
||||
left: 60,
|
||||
width: 40,
|
||||
height: 50,
|
||||
});
|
||||
});
|
||||
|
||||
test('setBounds', () => {
|
||||
const container = document.createElement('div');
|
||||
const content = document.createElement('div');
|
||||
|
||||
document.body.appendChild(container);
|
||||
container.appendChild(content);
|
||||
|
||||
const cut = new Overlay({
|
||||
height: 1000,
|
||||
width: 1000,
|
||||
left: 0,
|
||||
top: 0,
|
||||
minimumInViewportWidth: 0,
|
||||
minimumInViewportHeight: 0,
|
||||
container,
|
||||
content,
|
||||
});
|
||||
|
||||
const element: HTMLElement = container.querySelector(
|
||||
'.dv-resize-container'
|
||||
)!;
|
||||
expect(element).toBeTruthy();
|
||||
|
||||
jest.spyOn(element, 'getBoundingClientRect').mockImplementation(() => {
|
||||
return { left: 300, top: 400, width: 1000, height: 1000 } as any;
|
||||
});
|
||||
jest.spyOn(container, 'getBoundingClientRect').mockImplementation(
|
||||
() => {
|
||||
return { left: 0, top: 0, width: 1000, height: 1000 } as any;
|
||||
}
|
||||
);
|
||||
|
||||
cut.setBounds({ height: 100, width: 200, left: 300, top: 400 });
|
||||
|
||||
expect(element.style.height).toBe('100px');
|
||||
expect(element.style.width).toBe('200px');
|
||||
expect(element.style.left).toBe('300px');
|
||||
expect(element.style.top).toBe('400px');
|
||||
});
|
||||
|
||||
test('that the resize handles are added', () => {
|
||||
const container = document.createElement('div');
|
||||
const content = document.createElement('div');
|
||||
|
||||
const cut = new Overlay({
|
||||
height: 500,
|
||||
width: 500,
|
||||
left: 100,
|
||||
top: 200,
|
||||
minimumInViewportWidth: 0,
|
||||
minimumInViewportHeight: 0,
|
||||
container,
|
||||
content,
|
||||
});
|
||||
|
||||
expect(container.querySelector('.dv-resize-handle-top')).toBeTruthy();
|
||||
expect(
|
||||
container.querySelector('.dv-resize-handle-bottom')
|
||||
).toBeTruthy();
|
||||
expect(container.querySelector('.dv-resize-handle-left')).toBeTruthy();
|
||||
expect(container.querySelector('.dv-resize-handle-right')).toBeTruthy();
|
||||
expect(
|
||||
container.querySelector('.dv-resize-handle-topleft')
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
container.querySelector('.dv-resize-handle-topright')
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
container.querySelector('.dv-resize-handle-bottomleft')
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
container.querySelector('.dv-resize-handle-bottomright')
|
||||
).toBeTruthy();
|
||||
|
||||
cut.dispose();
|
||||
});
|
||||
});
|
@ -1,17 +1,14 @@
|
||||
import { fireEvent } from '@testing-library/dom';
|
||||
import { Emitter, Event } from '../../../../events';
|
||||
import { ContentContainer } from '../../../../dockview/components/panel/content';
|
||||
import {
|
||||
GroupPanelPartInitParameters,
|
||||
GroupPanelContentPartInitParameters,
|
||||
IContentRenderer,
|
||||
} from '../../../../dockview/types';
|
||||
import { CompositeDisposable } from '../../../../lifecycle';
|
||||
import { PanelUpdateEvent } from '../../../../panel/types';
|
||||
import { IDockviewPanel } from '../../../../dockview/dockviewPanel';
|
||||
import { IDockviewPanelModel } from '../../../../dockview/dockviewPanelModel';
|
||||
import { DockviewComponent } from '../../../../dockview/dockviewComponent';
|
||||
import { fromPartial } from '@total-typescript/shoehorn';
|
||||
import { DockviewGroupPanelModel } from '../../../../dockview/dockviewGroupPanelModel';
|
||||
import { OverlayRenderContainer } from '../../../../overlay/overlayRenderContainer';
|
||||
|
||||
class TestContentRenderer
|
||||
extends CompositeDisposable
|
||||
@ -19,13 +16,17 @@ class TestContentRenderer
|
||||
{
|
||||
readonly element: HTMLElement;
|
||||
|
||||
readonly _onDidFocus = new Emitter<void>();
|
||||
readonly _onDidBlur = new Emitter<void>();
|
||||
readonly onDidFocus: Event<void> = this._onDidFocus.event;
|
||||
readonly onDidBlur: Event<void> = this._onDidBlur.event;
|
||||
|
||||
constructor(public id: string) {
|
||||
super();
|
||||
this.element = document.createElement('div');
|
||||
this.element.id = id;
|
||||
}
|
||||
|
||||
init(parameters: GroupPanelPartInitParameters): void {
|
||||
init(parameters: GroupPanelContentPartInitParameters): void {
|
||||
//
|
||||
}
|
||||
|
||||
@ -55,21 +56,7 @@ describe('contentContainer', () => {
|
||||
let blur = 0;
|
||||
|
||||
const disposable = new CompositeDisposable();
|
||||
|
||||
const overlayRenderContainer = new OverlayRenderContainer(
|
||||
document.createElement('div'),
|
||||
fromPartial<DockviewComponent>({})
|
||||
);
|
||||
|
||||
const cut = new ContentContainer(
|
||||
fromPartial<DockviewComponent>({
|
||||
renderer: 'onlyWhenVisible',
|
||||
overlayRenderContainer,
|
||||
}),
|
||||
fromPartial<DockviewGroupPanelModel>({
|
||||
renderContainer: overlayRenderContainer,
|
||||
})
|
||||
);
|
||||
const cut = new ContentContainer();
|
||||
|
||||
disposable.addDisposables(
|
||||
cut.onDidFocus(() => {
|
||||
@ -82,12 +69,11 @@ describe('contentContainer', () => {
|
||||
|
||||
const contentRenderer = new TestContentRenderer('id-1');
|
||||
|
||||
const panel = fromPartial<IDockviewPanel>({
|
||||
const panel = {
|
||||
view: {
|
||||
content: contentRenderer,
|
||||
},
|
||||
api: { renderer: 'onlyWhenVisible' },
|
||||
});
|
||||
} as Partial<IDockviewPanelModel>,
|
||||
} as Partial<IDockviewPanel>;
|
||||
|
||||
cut.openPanel(panel as IDockviewPanel);
|
||||
|
||||
@ -105,70 +91,45 @@ describe('contentContainer', () => {
|
||||
expect(focus).toBe(1);
|
||||
expect(blur).toBe(1);
|
||||
|
||||
// renderer explicitly asks for focus
|
||||
contentRenderer._onDidFocus.fire();
|
||||
expect(focus).toBe(2);
|
||||
expect(blur).toBe(1);
|
||||
|
||||
// renderer explicitly looses focus
|
||||
contentRenderer._onDidBlur.fire();
|
||||
expect(focus).toBe(2);
|
||||
expect(blur).toBe(2);
|
||||
|
||||
const contentRenderer2 = new TestContentRenderer('id-2');
|
||||
|
||||
const panel2 = {
|
||||
view: {
|
||||
content: contentRenderer2,
|
||||
} as Partial<IDockviewPanelModel>,
|
||||
api: { renderer: 'onlyWhenVisible' },
|
||||
} as Partial<IDockviewPanel>;
|
||||
|
||||
cut.openPanel(panel2 as IDockviewPanel);
|
||||
// expect(focus).toBe(2);
|
||||
// expect(blur).toBe(1);
|
||||
expect(focus).toBe(2);
|
||||
expect(blur).toBe(2);
|
||||
|
||||
// previous renderer events should no longer be attached to container
|
||||
contentRenderer._onDidFocus.fire();
|
||||
contentRenderer._onDidBlur.fire();
|
||||
expect(focus).toBe(2);
|
||||
expect(blur).toBe(2);
|
||||
|
||||
// new panel recieves focus
|
||||
fireEvent.focus(contentRenderer2.element);
|
||||
expect(focus).toBe(2);
|
||||
expect(blur).toBe(1);
|
||||
expect(focus).toBe(3);
|
||||
expect(blur).toBe(2);
|
||||
|
||||
// new panel looses focus
|
||||
fireEvent.blur(contentRenderer2.element);
|
||||
jest.runAllTimers();
|
||||
expect(focus).toBe(2);
|
||||
expect(blur).toBe(2);
|
||||
expect(focus).toBe(3);
|
||||
expect(blur).toBe(3);
|
||||
|
||||
disposable.dispose();
|
||||
});
|
||||
|
||||
test("that panels renderered as 'onlyWhenVisible' are removed when closed", () => {
|
||||
const overlayRenderContainer = fromPartial<OverlayRenderContainer>({
|
||||
detatch: jest.fn(),
|
||||
});
|
||||
|
||||
const cut = new ContentContainer(
|
||||
fromPartial<DockviewComponent>({
|
||||
overlayRenderContainer,
|
||||
}),
|
||||
fromPartial<DockviewGroupPanelModel>({
|
||||
renderContainer: overlayRenderContainer,
|
||||
})
|
||||
);
|
||||
|
||||
const panel1 = fromPartial<IDockviewPanel>({
|
||||
api: {
|
||||
renderer: 'onlyWhenVisible',
|
||||
},
|
||||
view: { content: new TestContentRenderer('panel_1') },
|
||||
});
|
||||
|
||||
const panel2 = fromPartial<IDockviewPanel>({
|
||||
api: {
|
||||
renderer: 'onlyWhenVisible',
|
||||
},
|
||||
view: { content: new TestContentRenderer('panel_2') },
|
||||
});
|
||||
|
||||
cut.openPanel(panel1);
|
||||
|
||||
expect(panel1.view.content.element.parentElement).toBe(cut.element);
|
||||
expect(cut.element.childNodes.length).toBe(1);
|
||||
|
||||
cut.openPanel(panel2);
|
||||
|
||||
expect(panel1.view.content.element.parentElement).toBeNull();
|
||||
expect(panel2.view.content.element.parentElement).toBe(cut.element);
|
||||
expect(cut.element.childNodes.length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
@ -1,44 +1,31 @@
|
||||
import { fireEvent } from '@testing-library/dom';
|
||||
import {
|
||||
LocalSelectionTransfer,
|
||||
PanelTransfer,
|
||||
} from '../../../dnd/dataTransfer';
|
||||
import { LocalSelectionTransfer, PanelTransfer } from '../../../dnd/dataTransfer';
|
||||
import { DockviewComponent } from '../../../dockview/dockviewComponent';
|
||||
import { DockviewGroupPanel } from '../../../dockview/dockviewGroupPanel';
|
||||
import { DockviewGroupPanelModel } from '../../../dockview/dockviewGroupPanelModel';
|
||||
import { Tab } from '../../../dockview/components/tab/tab';
|
||||
import { IDockviewPanel } from '../../../dockview/dockviewPanel';
|
||||
import { fromPartial } from '@total-typescript/shoehorn';
|
||||
|
||||
describe('tab', () => {
|
||||
test('that empty tab has inactive-tab class', () => {
|
||||
const accessorMock = jest.fn();
|
||||
const groupMock = jest.fn();
|
||||
|
||||
const cut = new Tab(
|
||||
{ id: 'panelId' } as IDockviewPanel,
|
||||
new accessorMock(),
|
||||
new groupMock()
|
||||
);
|
||||
const cut = new Tab('panelId', new accessorMock(), new groupMock());
|
||||
|
||||
expect(cut.element.className).toBe('dv-tab dv-inactive-tab');
|
||||
expect(cut.element.className).toBe('tab inactive-tab');
|
||||
});
|
||||
|
||||
test('that active tab has active-tab class', () => {
|
||||
const accessorMock = jest.fn();
|
||||
const groupMock = jest.fn();
|
||||
|
||||
const cut = new Tab(
|
||||
{ id: 'panelId' } as IDockviewPanel,
|
||||
new accessorMock(),
|
||||
new groupMock()
|
||||
);
|
||||
const cut = new Tab('panelId', new accessorMock(), new groupMock());
|
||||
|
||||
cut.setActive(true);
|
||||
expect(cut.element.className).toBe('dv-tab dv-active-tab');
|
||||
expect(cut.element.className).toBe('tab active-tab');
|
||||
|
||||
cut.setActive(false);
|
||||
expect(cut.element.className).toBe('dv-tab dv-inactive-tab');
|
||||
expect(cut.element.className).toBe('tab inactive-tab');
|
||||
});
|
||||
|
||||
test('that an external event does not render a drop target and calls through to the group model', () => {
|
||||
@ -47,10 +34,15 @@ describe('tab', () => {
|
||||
id: 'testcomponentid',
|
||||
};
|
||||
});
|
||||
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
|
||||
() => {
|
||||
return {
|
||||
canDisplayOverlay: jest.fn(),
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const groupView = fromPartial<DockviewGroupPanelModel>({
|
||||
canDisplayOverlay: jest.fn(),
|
||||
});
|
||||
const groupView = new groupviewMock() as DockviewGroupPanelModel;
|
||||
|
||||
const groupPanelMock = jest.fn<Partial<DockviewGroupPanel>, []>(() => {
|
||||
return {
|
||||
@ -62,39 +54,40 @@ describe('tab', () => {
|
||||
const accessor = new accessorMock() as DockviewComponent;
|
||||
const groupPanel = new groupPanelMock() as DockviewGroupPanel;
|
||||
|
||||
const cut = new Tab(
|
||||
{ id: 'panelId' } as IDockviewPanel,
|
||||
accessor,
|
||||
groupPanel
|
||||
);
|
||||
const cut = new Tab('panelId', accessor, groupPanel);
|
||||
|
||||
jest.spyOn(cut.element, 'offsetHeight', 'get').mockImplementation(
|
||||
jest.spyOn(cut.element, 'clientHeight', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
jest.spyOn(cut.element, 'offsetWidth', 'get').mockImplementation(
|
||||
jest.spyOn(cut.element, 'clientWidth', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
|
||||
fireEvent.dragEnter(cut.element);
|
||||
fireEvent.dragOver(cut.element);
|
||||
|
||||
expect(groupView.canDisplayOverlay).toHaveBeenCalled();
|
||||
expect(groupView.canDisplayOverlay).toBeCalled();
|
||||
|
||||
expect(
|
||||
cut.element.getElementsByClassName('dv-drop-target-dropzone').length
|
||||
cut.element.getElementsByClassName('drop-target-dropzone').length
|
||||
).toBe(0);
|
||||
});
|
||||
|
||||
test('that if you drag over yourself a drop target is shown', () => {
|
||||
test('that if you drag over yourself no drop target is shown', () => {
|
||||
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
|
||||
return {
|
||||
id: 'testcomponentid',
|
||||
};
|
||||
});
|
||||
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
|
||||
() => {
|
||||
return {
|
||||
canDisplayOverlay: jest.fn(),
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const groupView = fromPartial<DockviewGroupPanelModel>({
|
||||
canDisplayOverlay: jest.fn(),
|
||||
});
|
||||
const groupView = new groupviewMock() as DockviewGroupPanelModel;
|
||||
|
||||
const groupPanelMock = jest.fn<Partial<DockviewGroupPanel>, []>(() => {
|
||||
return {
|
||||
@ -106,16 +99,12 @@ describe('tab', () => {
|
||||
const accessor = new accessorMock() as DockviewComponent;
|
||||
const groupPanel = new groupPanelMock() as DockviewGroupPanel;
|
||||
|
||||
const cut = new Tab(
|
||||
{ id: 'panel1' } as IDockviewPanel,
|
||||
accessor,
|
||||
groupPanel
|
||||
);
|
||||
const cut = new Tab('panel1', accessor, groupPanel);
|
||||
|
||||
jest.spyOn(cut.element, 'offsetHeight', 'get').mockImplementation(
|
||||
jest.spyOn(cut.element, 'clientHeight', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
jest.spyOn(cut.element, 'offsetWidth', 'get').mockImplementation(
|
||||
jest.spyOn(cut.element, 'clientWidth', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
|
||||
@ -127,11 +116,11 @@ describe('tab', () => {
|
||||
fireEvent.dragEnter(cut.element);
|
||||
fireEvent.dragOver(cut.element);
|
||||
|
||||
expect(groupView.canDisplayOverlay).toHaveBeenCalledTimes(0);
|
||||
expect(groupView.canDisplayOverlay).toBeCalledTimes(0);
|
||||
|
||||
expect(
|
||||
cut.element.getElementsByClassName('dv-drop-target-dropzone').length
|
||||
).toBe(1);
|
||||
cut.element.getElementsByClassName('drop-target-dropzone').length
|
||||
).toBe(0);
|
||||
});
|
||||
|
||||
test('that if you drag over another tab a drop target is shown', () => {
|
||||
@ -160,16 +149,12 @@ describe('tab', () => {
|
||||
const accessor = new accessorMock() as DockviewComponent;
|
||||
const groupPanel = new groupPanelMock() as DockviewGroupPanel;
|
||||
|
||||
const cut = new Tab(
|
||||
{ id: 'panel1' } as IDockviewPanel,
|
||||
accessor,
|
||||
groupPanel
|
||||
);
|
||||
const cut = new Tab('panel1', accessor, groupPanel);
|
||||
|
||||
jest.spyOn(cut.element, 'offsetHeight', 'get').mockImplementation(
|
||||
jest.spyOn(cut.element, 'clientHeight', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
jest.spyOn(cut.element, 'offsetWidth', 'get').mockImplementation(
|
||||
jest.spyOn(cut.element, 'clientWidth', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
|
||||
@ -184,7 +169,7 @@ describe('tab', () => {
|
||||
expect(groupView.canDisplayOverlay).toBeCalledTimes(0);
|
||||
|
||||
expect(
|
||||
cut.element.getElementsByClassName('dv-drop-target-dropzone').length
|
||||
cut.element.getElementsByClassName('drop-target-dropzone').length
|
||||
).toBe(1);
|
||||
});
|
||||
|
||||
@ -214,16 +199,12 @@ describe('tab', () => {
|
||||
const accessor = new accessorMock() as DockviewComponent;
|
||||
const groupPanel = new groupPanelMock() as DockviewGroupPanel;
|
||||
|
||||
const cut = new Tab(
|
||||
{ id: 'panel1' } as IDockviewPanel,
|
||||
accessor,
|
||||
groupPanel
|
||||
);
|
||||
const cut = new Tab('panel1', accessor, groupPanel);
|
||||
|
||||
jest.spyOn(cut.element, 'offsetHeight', 'get').mockImplementation(
|
||||
jest.spyOn(cut.element, 'clientHeight', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
jest.spyOn(cut.element, 'offsetWidth', 'get').mockImplementation(
|
||||
jest.spyOn(cut.element, 'clientWidth', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
|
||||
@ -244,7 +225,7 @@ describe('tab', () => {
|
||||
expect(groupView.canDisplayOverlay).toBeCalledTimes(1);
|
||||
|
||||
expect(
|
||||
cut.element.getElementsByClassName('dv-drop-target-dropzone').length
|
||||
cut.element.getElementsByClassName('drop-target-dropzone').length
|
||||
).toBe(0);
|
||||
});
|
||||
|
||||
@ -274,16 +255,12 @@ describe('tab', () => {
|
||||
const accessor = new accessorMock() as DockviewComponent;
|
||||
const groupPanel = new groupPanelMock() as DockviewGroupPanel;
|
||||
|
||||
const cut = new Tab(
|
||||
{ id: 'panel1' } as IDockviewPanel,
|
||||
accessor,
|
||||
groupPanel
|
||||
);
|
||||
const cut = new Tab('panel1', accessor, groupPanel);
|
||||
|
||||
jest.spyOn(cut.element, 'offsetHeight', 'get').mockImplementation(
|
||||
jest.spyOn(cut.element, 'clientHeight', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
jest.spyOn(cut.element, 'offsetWidth', 'get').mockImplementation(
|
||||
jest.spyOn(cut.element, 'clientWidth', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
|
||||
@ -304,7 +281,7 @@ describe('tab', () => {
|
||||
expect(groupView.canDisplayOverlay).toBeCalledTimes(1);
|
||||
|
||||
expect(
|
||||
cut.element.getElementsByClassName('dv-drop-target-dropzone').length
|
||||
cut.element.getElementsByClassName('drop-target-dropzone').length
|
||||
).toBe(0);
|
||||
});
|
||||
});
|
||||
|
@ -1,63 +0,0 @@
|
||||
import { DockviewApi } from '../../../../api/component.api';
|
||||
import { DockviewPanelApi, TitleEvent } from '../../../../api/dockviewPanelApi';
|
||||
import { DefaultTab } from '../../../../dockview/components/tab/defaultTab';
|
||||
import { fromPartial } from '@total-typescript/shoehorn';
|
||||
import { Emitter } from '../../../../events';
|
||||
import { fireEvent } from '@testing-library/dom';
|
||||
|
||||
describe('defaultTab', () => {
|
||||
test('that title updates', () => {
|
||||
const cut = new DefaultTab();
|
||||
|
||||
let el = cut.element.querySelector('.dv-default-tab-content');
|
||||
expect(el).toBeTruthy();
|
||||
expect(el!.textContent).toBe('');
|
||||
|
||||
const onDidTitleChange = new Emitter<TitleEvent>();
|
||||
|
||||
const api = fromPartial<DockviewPanelApi>({
|
||||
onDidTitleChange: onDidTitleChange.event,
|
||||
});
|
||||
const containerApi = fromPartial<DockviewApi>({});
|
||||
|
||||
cut.init({
|
||||
api,
|
||||
containerApi,
|
||||
params: {},
|
||||
title: 'title_abc',
|
||||
});
|
||||
|
||||
el = cut.element.querySelector('.dv-default-tab-content');
|
||||
expect(el).toBeTruthy();
|
||||
expect(el!.textContent).toBe('title_abc');
|
||||
|
||||
onDidTitleChange.fire({ title: 'title_def' });
|
||||
|
||||
expect(el!.textContent).toBe('title_def');
|
||||
});
|
||||
|
||||
test('that click closes tab', () => {
|
||||
const cut = new DefaultTab();
|
||||
|
||||
const api = fromPartial<DockviewPanelApi>({
|
||||
onDidTitleChange: jest.fn(),
|
||||
close: jest.fn(),
|
||||
});
|
||||
const containerApi = fromPartial<DockviewApi>({});
|
||||
|
||||
cut.init({
|
||||
api,
|
||||
containerApi,
|
||||
params: {},
|
||||
title: 'title_abc',
|
||||
});
|
||||
|
||||
let el = cut.element.querySelector('.dv-default-tab-action');
|
||||
|
||||
fireEvent.pointerDown(el!);
|
||||
expect(api.close).toHaveBeenCalledTimes(0);
|
||||
|
||||
fireEvent.click(el!);
|
||||
expect(api.close).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
@ -1,66 +0,0 @@
|
||||
import { Tabs } from '../../../../dockview/components/titlebar/tabs';
|
||||
import { fromPartial } from '@total-typescript/shoehorn';
|
||||
import { DockviewGroupPanel } from '../../../../dockview/dockviewGroupPanel';
|
||||
import { DockviewComponent } from '../../../../dockview/dockviewComponent';
|
||||
|
||||
describe('tabs', () => {
|
||||
describe('disableCustomScrollbars', () => {
|
||||
test('enabled by default', () => {
|
||||
const cut = new Tabs(
|
||||
fromPartial<DockviewGroupPanel>({}),
|
||||
fromPartial<DockviewComponent>({
|
||||
options: {},
|
||||
}),
|
||||
{
|
||||
showTabsOverflowControl: true,
|
||||
}
|
||||
);
|
||||
|
||||
expect(
|
||||
cut.element.querySelectorAll(
|
||||
'.dv-scrollable > .dv-tabs-container'
|
||||
).length
|
||||
).toBe(1);
|
||||
});
|
||||
|
||||
test('enabled when disabled flag is false', () => {
|
||||
const cut = new Tabs(
|
||||
fromPartial<DockviewGroupPanel>({}),
|
||||
fromPartial<DockviewComponent>({
|
||||
options: {
|
||||
scrollbars: 'custom',
|
||||
},
|
||||
}),
|
||||
{
|
||||
showTabsOverflowControl: true,
|
||||
}
|
||||
);
|
||||
|
||||
expect(
|
||||
cut.element.querySelectorAll(
|
||||
'.dv-scrollable > .dv-tabs-container'
|
||||
).length
|
||||
).toBe(1);
|
||||
});
|
||||
|
||||
test('disabled when disabled flag is true', () => {
|
||||
const cut = new Tabs(
|
||||
fromPartial<DockviewGroupPanel>({}),
|
||||
fromPartial<DockviewComponent>({
|
||||
options: {
|
||||
scrollbars: 'native',
|
||||
},
|
||||
}),
|
||||
{
|
||||
showTabsOverflowControl: true,
|
||||
}
|
||||
);
|
||||
|
||||
expect(
|
||||
cut.element.querySelectorAll(
|
||||
'.dv-scrollable > .dv-tabs-container'
|
||||
).length
|
||||
).toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
@ -9,18 +9,16 @@ import { DockviewGroupPanelModel } from '../../../../dockview/dockviewGroupPanel
|
||||
import { fireEvent } from '@testing-library/dom';
|
||||
import { TestPanel } from '../../dockviewGroupPanelModel.spec';
|
||||
import { IDockviewPanel } from '../../../../dockview/dockviewPanel';
|
||||
import { fromPartial } from '@total-typescript/shoehorn';
|
||||
import { DockviewPanelApi } from '../../../../api/dockviewPanelApi';
|
||||
|
||||
describe('tabsContainer', () => {
|
||||
test('that an external event does not render a drop target and calls through to the group mode', () => {
|
||||
const accessor = fromPartial<DockviewComponent>({
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
options: {},
|
||||
onDidOptionsChange: jest.fn(),
|
||||
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
|
||||
return {
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
options: {},
|
||||
};
|
||||
});
|
||||
|
||||
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
|
||||
() => {
|
||||
return {
|
||||
@ -37,52 +35,54 @@ describe('tabsContainer', () => {
|
||||
};
|
||||
});
|
||||
|
||||
const accessor = new accessorMock() as DockviewComponent;
|
||||
const groupPanel = new groupPanelMock() as DockviewGroupPanel;
|
||||
|
||||
const cut = new TabsContainer(accessor, groupPanel);
|
||||
|
||||
const emptySpace = cut.element
|
||||
.getElementsByClassName('dv-void-container')
|
||||
.item(0) as HTMLElement;
|
||||
.getElementsByClassName('void-container')
|
||||
.item(0);
|
||||
|
||||
if (!emptySpace!) {
|
||||
if (!emptySpace) {
|
||||
fail('element not found');
|
||||
}
|
||||
|
||||
jest.spyOn(emptySpace!, 'offsetHeight', 'get').mockImplementation(
|
||||
jest.spyOn(emptySpace, 'clientHeight', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
jest.spyOn(emptySpace!, 'offsetWidth', 'get').mockImplementation(
|
||||
jest.spyOn(emptySpace, 'clientWidth', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
|
||||
fireEvent.dragEnter(emptySpace!);
|
||||
fireEvent.dragOver(emptySpace!);
|
||||
fireEvent.dragEnter(emptySpace);
|
||||
fireEvent.dragOver(emptySpace);
|
||||
|
||||
expect(groupView.canDisplayOverlay).toHaveBeenCalled();
|
||||
expect(groupView.canDisplayOverlay).toBeCalled();
|
||||
|
||||
expect(
|
||||
cut.element.getElementsByClassName('dv-drop-target-dropzone').length
|
||||
cut.element.getElementsByClassName('drop-target-dropzone').length
|
||||
).toBe(0);
|
||||
});
|
||||
|
||||
test('that a drag over event from another tab should render a drop target', () => {
|
||||
const accessor = fromPartial<DockviewComponent>({
|
||||
id: 'testcomponentid',
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
options: {},
|
||||
onDidOptionsChange: jest.fn(),
|
||||
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
|
||||
return {
|
||||
id: 'testcomponentid',
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
options: {},
|
||||
};
|
||||
});
|
||||
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
|
||||
() => {
|
||||
return {
|
||||
canDisplayOverlay: jest.fn(),
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const dropTargetContainer = document.createElement('div');
|
||||
|
||||
const groupView = fromPartial<DockviewGroupPanelModel>({
|
||||
canDisplayOverlay: jest.fn(),
|
||||
// dropTargetContainer: new DropTargetAnchorContainer(
|
||||
// dropTargetContainer
|
||||
// ),
|
||||
});
|
||||
const groupView = new groupviewMock() as DockviewGroupPanelModel;
|
||||
|
||||
const groupPanelMock = jest.fn<Partial<DockviewGroupPanel>, []>(() => {
|
||||
return {
|
||||
@ -92,22 +92,23 @@ describe('tabsContainer', () => {
|
||||
};
|
||||
});
|
||||
|
||||
const accessor = new accessorMock() as DockviewComponent;
|
||||
const groupPanel = new groupPanelMock() as DockviewGroupPanel;
|
||||
|
||||
const cut = new TabsContainer(accessor, groupPanel);
|
||||
|
||||
const emptySpace = cut.element
|
||||
.getElementsByClassName('dv-void-container')
|
||||
.item(0) as HTMLElement;
|
||||
.getElementsByClassName('void-container')
|
||||
.item(0);
|
||||
|
||||
if (!emptySpace!) {
|
||||
if (!emptySpace) {
|
||||
fail('element not found');
|
||||
}
|
||||
|
||||
jest.spyOn(emptySpace!, 'offsetHeight', 'get').mockImplementation(
|
||||
jest.spyOn(emptySpace, 'clientHeight', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
jest.spyOn(emptySpace!, 'offsetWidth', 'get').mockImplementation(
|
||||
jest.spyOn(emptySpace, 'clientWidth', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
|
||||
@ -122,29 +123,25 @@ describe('tabsContainer', () => {
|
||||
PanelTransfer.prototype
|
||||
);
|
||||
|
||||
fireEvent.dragEnter(emptySpace!);
|
||||
fireEvent.dragOver(emptySpace!);
|
||||
fireEvent.dragEnter(emptySpace);
|
||||
fireEvent.dragOver(emptySpace);
|
||||
|
||||
expect(groupView.canDisplayOverlay).toHaveBeenCalledTimes(0);
|
||||
expect(groupView.canDisplayOverlay).toBeCalledTimes(0);
|
||||
|
||||
expect(
|
||||
cut.element.getElementsByClassName('dv-drop-target-dropzone').length
|
||||
cut.element.getElementsByClassName('drop-target-dropzone').length
|
||||
).toBe(1);
|
||||
// expect(
|
||||
// dropTargetContainer.getElementsByClassName('dv-drop-target-anchor')
|
||||
// .length
|
||||
// ).toBe(1);
|
||||
});
|
||||
|
||||
test('that dropping over the empty space should render a drop target', () => {
|
||||
const accessor = fromPartial<DockviewComponent>({
|
||||
id: 'testcomponentid',
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
options: {},
|
||||
onDidOptionsChange: jest.fn(),
|
||||
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
|
||||
return {
|
||||
id: 'testcomponentid',
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
options: {},
|
||||
};
|
||||
});
|
||||
|
||||
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
|
||||
() => {
|
||||
return {
|
||||
@ -163,6 +160,7 @@ describe('tabsContainer', () => {
|
||||
};
|
||||
});
|
||||
|
||||
const accessor = new accessorMock() as DockviewComponent;
|
||||
const groupPanel = new groupPanelMock() as DockviewGroupPanel;
|
||||
|
||||
const cut = new TabsContainer(accessor, groupPanel);
|
||||
@ -171,17 +169,17 @@ describe('tabsContainer', () => {
|
||||
cut.openPanel(new TestPanel('panel2', jest.fn() as any));
|
||||
|
||||
const emptySpace = cut.element
|
||||
.getElementsByClassName('dv-void-container')
|
||||
.item(0) as HTMLElement;
|
||||
.getElementsByClassName('void-container')
|
||||
.item(0);
|
||||
|
||||
if (!emptySpace!) {
|
||||
if (!emptySpace) {
|
||||
fail('element not found');
|
||||
}
|
||||
|
||||
jest.spyOn(emptySpace!, 'offsetHeight', 'get').mockImplementation(
|
||||
jest.spyOn(emptySpace, 'clientHeight', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
jest.spyOn(emptySpace!, 'offsetWidth', 'get').mockImplementation(
|
||||
jest.spyOn(emptySpace, 'clientWidth', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
|
||||
@ -190,25 +188,25 @@ describe('tabsContainer', () => {
|
||||
PanelTransfer.prototype
|
||||
);
|
||||
|
||||
fireEvent.dragEnter(emptySpace!);
|
||||
fireEvent.dragOver(emptySpace!);
|
||||
fireEvent.dragEnter(emptySpace);
|
||||
fireEvent.dragOver(emptySpace);
|
||||
|
||||
expect(groupView.canDisplayOverlay).toHaveBeenCalledTimes(0);
|
||||
expect(groupView.canDisplayOverlay).toBeCalledTimes(0);
|
||||
|
||||
expect(
|
||||
cut.element.getElementsByClassName('dv-drop-target-dropzone').length
|
||||
cut.element.getElementsByClassName('drop-target-dropzone').length
|
||||
).toBe(1);
|
||||
});
|
||||
|
||||
test('that dropping the first tab should render a drop target', () => {
|
||||
const accessor = fromPartial<DockviewComponent>({
|
||||
id: 'testcomponentid',
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
options: {},
|
||||
onDidOptionsChange: jest.fn(),
|
||||
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
|
||||
return {
|
||||
id: 'testcomponentid',
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
options: {},
|
||||
};
|
||||
});
|
||||
|
||||
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
|
||||
() => {
|
||||
return {
|
||||
@ -227,6 +225,7 @@ describe('tabsContainer', () => {
|
||||
};
|
||||
});
|
||||
|
||||
const accessor = new accessorMock() as DockviewComponent;
|
||||
const groupPanel = new groupPanelMock() as DockviewGroupPanel;
|
||||
|
||||
const cut = new TabsContainer(accessor, groupPanel);
|
||||
@ -235,17 +234,17 @@ describe('tabsContainer', () => {
|
||||
cut.openPanel(new TestPanel('panel2', jest.fn() as any));
|
||||
|
||||
const emptySpace = cut.element
|
||||
.getElementsByClassName('dv-void-container')
|
||||
.item(0) as HTMLElement;
|
||||
.getElementsByClassName('void-container')
|
||||
.item(0);
|
||||
|
||||
if (!emptySpace!) {
|
||||
if (!emptySpace) {
|
||||
fail('element not found');
|
||||
}
|
||||
|
||||
jest.spyOn(emptySpace!, 'offsetHeight', 'get').mockImplementation(
|
||||
jest.spyOn(emptySpace, 'clientHeight', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
jest.spyOn(emptySpace!, 'offsetWidth', 'get').mockImplementation(
|
||||
jest.spyOn(emptySpace, 'clientWidth', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
|
||||
@ -254,25 +253,25 @@ describe('tabsContainer', () => {
|
||||
PanelTransfer.prototype
|
||||
);
|
||||
|
||||
fireEvent.dragEnter(emptySpace!);
|
||||
fireEvent.dragOver(emptySpace!);
|
||||
fireEvent.dragEnter(emptySpace);
|
||||
fireEvent.dragOver(emptySpace);
|
||||
|
||||
expect(groupView.canDisplayOverlay).toHaveBeenCalledTimes(0);
|
||||
expect(groupView.canDisplayOverlay).toBeCalledTimes(0);
|
||||
|
||||
expect(
|
||||
cut.element.getElementsByClassName('dv-drop-target-dropzone').length
|
||||
cut.element.getElementsByClassName('drop-target-dropzone').length
|
||||
).toBe(1);
|
||||
});
|
||||
|
||||
test('that dropping a tab from another component should not render a drop target', () => {
|
||||
const accessor = fromPartial<DockviewComponent>({
|
||||
id: 'testcomponentid',
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
options: {},
|
||||
onDidOptionsChange: jest.fn(),
|
||||
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
|
||||
return {
|
||||
id: 'testcomponentid',
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
options: {},
|
||||
};
|
||||
});
|
||||
|
||||
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
|
||||
() => {
|
||||
return {
|
||||
@ -290,6 +289,7 @@ describe('tabsContainer', () => {
|
||||
};
|
||||
});
|
||||
|
||||
const accessor = new accessorMock() as DockviewComponent;
|
||||
const groupPanel = new groupPanelMock() as DockviewGroupPanel;
|
||||
|
||||
const cut = new TabsContainer(accessor, groupPanel);
|
||||
@ -298,17 +298,17 @@ describe('tabsContainer', () => {
|
||||
cut.openPanel(new TestPanel('panel2', jest.fn() as any));
|
||||
|
||||
const emptySpace = cut.element
|
||||
.getElementsByClassName('dv-void-container')
|
||||
.item(0) as HTMLElement;
|
||||
.getElementsByClassName('void-container')
|
||||
.item(0);
|
||||
|
||||
if (!emptySpace!) {
|
||||
if (!emptySpace) {
|
||||
fail('element not found');
|
||||
}
|
||||
|
||||
jest.spyOn(emptySpace!, 'offsetHeight', 'get').mockImplementation(
|
||||
jest.spyOn(emptySpace, 'clientHeight', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
jest.spyOn(emptySpace!, 'offsetWidth', 'get').mockImplementation(
|
||||
jest.spyOn(emptySpace, 'clientWidth', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
|
||||
@ -323,35 +323,36 @@ describe('tabsContainer', () => {
|
||||
PanelTransfer.prototype
|
||||
);
|
||||
|
||||
fireEvent.dragEnter(emptySpace!);
|
||||
fireEvent.dragOver(emptySpace!);
|
||||
fireEvent.dragEnter(emptySpace);
|
||||
fireEvent.dragOver(emptySpace);
|
||||
|
||||
expect(groupView.canDisplayOverlay).toHaveBeenCalledTimes(1);
|
||||
expect(groupView.canDisplayOverlay).toBeCalledTimes(1);
|
||||
|
||||
expect(
|
||||
cut.element.getElementsByClassName('dv-drop-target-dropzone').length
|
||||
cut.element.getElementsByClassName('drop-target-dropzone').length
|
||||
).toBe(0);
|
||||
});
|
||||
|
||||
test('left actions', () => {
|
||||
const accessor = fromPartial<DockviewComponent>({
|
||||
id: 'testcomponentid',
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
options: {},
|
||||
onDidOptionsChange: jest.fn(),
|
||||
const accessorMock = jest.fn<DockviewComponent, []>(() => {
|
||||
return (<Partial<DockviewComponent>>{
|
||||
options: {},
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
}) as DockviewComponent;
|
||||
});
|
||||
|
||||
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||
return (<Partial<DockviewGroupPanel>>{}) as DockviewGroupPanel;
|
||||
});
|
||||
|
||||
const accessor = new accessorMock();
|
||||
const groupPanel = new groupPanelMock();
|
||||
|
||||
const cut = new TabsContainer(accessor, groupPanel);
|
||||
|
||||
let query = cut.element.querySelectorAll(
|
||||
'.dv-tabs-and-actions-container > .dv-left-actions-container'
|
||||
'.tabs-and-actions-container > .left-actions-container'
|
||||
);
|
||||
|
||||
expect(query.length).toBe(1);
|
||||
@ -364,7 +365,7 @@ describe('tabsContainer', () => {
|
||||
cut.setLeftActionsElement(left);
|
||||
|
||||
query = cut.element.querySelectorAll(
|
||||
'.dv-tabs-and-actions-container > .dv-left-actions-container'
|
||||
'.tabs-and-actions-container > .left-actions-container'
|
||||
);
|
||||
expect(query.length).toBe(1);
|
||||
expect(query[0].children.item(0)?.className).toBe(
|
||||
@ -379,7 +380,7 @@ describe('tabsContainer', () => {
|
||||
cut.setLeftActionsElement(left2);
|
||||
|
||||
query = cut.element.querySelectorAll(
|
||||
'.dv-tabs-and-actions-container > .dv-left-actions-container'
|
||||
'.tabs-and-actions-container > .left-actions-container'
|
||||
);
|
||||
expect(query.length).toBe(1);
|
||||
expect(query[0].children.item(0)?.className).toBe(
|
||||
@ -391,7 +392,7 @@ describe('tabsContainer', () => {
|
||||
|
||||
cut.setLeftActionsElement(undefined);
|
||||
query = cut.element.querySelectorAll(
|
||||
'.dv-tabs-and-actions-container > .dv-left-actions-container'
|
||||
'.tabs-and-actions-container > .left-actions-container'
|
||||
);
|
||||
|
||||
expect(query.length).toBe(1);
|
||||
@ -399,24 +400,25 @@ describe('tabsContainer', () => {
|
||||
});
|
||||
|
||||
test('right actions', () => {
|
||||
const accessor = fromPartial<DockviewComponent>({
|
||||
id: 'testcomponentid',
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
options: {},
|
||||
onDidOptionsChange: jest.fn(),
|
||||
const accessorMock = jest.fn<DockviewComponent, []>(() => {
|
||||
return (<Partial<DockviewComponent>>{
|
||||
options: {},
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
}) as DockviewComponent;
|
||||
});
|
||||
|
||||
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||
return (<Partial<DockviewGroupPanel>>{}) as DockviewGroupPanel;
|
||||
});
|
||||
|
||||
const accessor = new accessorMock();
|
||||
const groupPanel = new groupPanelMock();
|
||||
|
||||
const cut = new TabsContainer(accessor, groupPanel);
|
||||
|
||||
let query = cut.element.querySelectorAll(
|
||||
'.dv-tabs-and-actions-container > .dv-right-actions-container'
|
||||
'.tabs-and-actions-container > .right-actions-container'
|
||||
);
|
||||
|
||||
expect(query.length).toBe(1);
|
||||
@ -429,7 +431,7 @@ describe('tabsContainer', () => {
|
||||
cut.setRightActionsElement(right);
|
||||
|
||||
query = cut.element.querySelectorAll(
|
||||
'.dv-tabs-and-actions-container > .dv-right-actions-container'
|
||||
'.tabs-and-actions-container > .right-actions-container'
|
||||
);
|
||||
expect(query.length).toBe(1);
|
||||
expect(query[0].children.item(0)?.className).toBe(
|
||||
@ -444,7 +446,7 @@ describe('tabsContainer', () => {
|
||||
cut.setRightActionsElement(right2);
|
||||
|
||||
query = cut.element.querySelectorAll(
|
||||
'.dv-tabs-and-actions-container > .dv-right-actions-container'
|
||||
'.tabs-and-actions-container > .right-actions-container'
|
||||
);
|
||||
expect(query.length).toBe(1);
|
||||
expect(query[0].children.item(0)?.className).toBe(
|
||||
@ -456,7 +458,7 @@ describe('tabsContainer', () => {
|
||||
|
||||
cut.setRightActionsElement(undefined);
|
||||
query = cut.element.querySelectorAll(
|
||||
'.dv-tabs-and-actions-container > .dv-right-actions-container'
|
||||
'.tabs-and-actions-container > .right-actions-container'
|
||||
);
|
||||
|
||||
expect(query.length).toBe(1);
|
||||
@ -464,27 +466,28 @@ describe('tabsContainer', () => {
|
||||
});
|
||||
|
||||
test('that a tab will become floating when clicked if not floating and shift is selected', () => {
|
||||
const accessor = fromPartial<DockviewComponent>({
|
||||
options: {},
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
element: document.createElement('div'),
|
||||
addFloatingGroup: jest.fn(),
|
||||
doSetGroupActive: jest.fn(),
|
||||
onDidOptionsChange: jest.fn(),
|
||||
const accessorMock = jest.fn<DockviewComponent, []>(() => {
|
||||
return (<Partial<DockviewComponent>>{
|
||||
options: {},
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
element: document.createElement('div'),
|
||||
addFloatingGroup: jest.fn(),
|
||||
}) as DockviewComponent;
|
||||
});
|
||||
|
||||
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||
return (<Partial<DockviewGroupPanel>>{
|
||||
api: { location: { type: 'grid' } } as any,
|
||||
api: { isFloating: false } as any,
|
||||
}) as DockviewGroupPanel;
|
||||
});
|
||||
|
||||
const accessor = new accessorMock();
|
||||
const groupPanel = new groupPanelMock();
|
||||
|
||||
const cut = new TabsContainer(accessor, groupPanel);
|
||||
|
||||
const container = cut.element.querySelector('.dv-void-container')!;
|
||||
const container = cut.element.querySelector('.void-container')!;
|
||||
expect(container).toBeTruthy();
|
||||
|
||||
jest.spyOn(cut.element, 'getBoundingClientRect').mockImplementation(
|
||||
@ -499,49 +502,52 @@ describe('tabsContainer', () => {
|
||||
return { top: 10, left: 20, width: 0, height: 0 } as any;
|
||||
});
|
||||
|
||||
const event = new KeyboardEvent('pointerdown', { shiftKey: true });
|
||||
const event = new KeyboardEvent('mousedown', { shiftKey: true });
|
||||
const eventPreventDefaultSpy = jest.spyOn(event, 'preventDefault');
|
||||
fireEvent(container, event);
|
||||
|
||||
expect(accessor.doSetGroupActive).toHaveBeenCalledWith(groupPanel);
|
||||
expect(accessor.addFloatingGroup).toHaveBeenCalledWith(groupPanel, {
|
||||
x: 100,
|
||||
y: 60,
|
||||
inDragMode: true,
|
||||
});
|
||||
expect(accessor.addFloatingGroup).toHaveBeenCalledTimes(1);
|
||||
expect(eventPreventDefaultSpy).toHaveBeenCalledTimes(1);
|
||||
expect(accessor.addFloatingGroup).toBeCalledWith(
|
||||
groupPanel,
|
||||
{
|
||||
x: 100,
|
||||
y: 60,
|
||||
},
|
||||
{ inDragMode: true }
|
||||
);
|
||||
expect(accessor.addFloatingGroup).toBeCalledTimes(1);
|
||||
expect(eventPreventDefaultSpy).toBeCalledTimes(1);
|
||||
|
||||
const event2 = new KeyboardEvent('pointerdown', { shiftKey: false });
|
||||
const event2 = new KeyboardEvent('mousedown', { shiftKey: false });
|
||||
const eventPreventDefaultSpy2 = jest.spyOn(event2, 'preventDefault');
|
||||
fireEvent(container, event2);
|
||||
|
||||
expect(accessor.addFloatingGroup).toHaveBeenCalledTimes(1);
|
||||
expect(eventPreventDefaultSpy2).toHaveBeenCalledTimes(0);
|
||||
expect(accessor.addFloatingGroup).toBeCalledTimes(1);
|
||||
expect(eventPreventDefaultSpy2).toBeCalledTimes(0);
|
||||
});
|
||||
|
||||
test('that a tab that is already floating cannot be floated again', () => {
|
||||
const accessor = fromPartial<DockviewComponent>({
|
||||
options: {},
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
element: document.createElement('div'),
|
||||
addFloatingGroup: jest.fn(),
|
||||
doSetGroupActive: jest.fn(),
|
||||
onDidOptionsChange: jest.fn(),
|
||||
const accessorMock = jest.fn<DockviewComponent, []>(() => {
|
||||
return (<Partial<DockviewComponent>>{
|
||||
options: {},
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
element: document.createElement('div'),
|
||||
addFloatingGroup: jest.fn(),
|
||||
}) as DockviewComponent;
|
||||
});
|
||||
|
||||
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||
return (<Partial<DockviewGroupPanel>>{
|
||||
api: { location: { type: 'floating' } } as any,
|
||||
api: { isFloating: true } as any,
|
||||
}) as DockviewGroupPanel;
|
||||
});
|
||||
|
||||
const accessor = new accessorMock();
|
||||
const groupPanel = new groupPanelMock();
|
||||
|
||||
const cut = new TabsContainer(accessor, groupPanel);
|
||||
|
||||
const container = cut.element.querySelector('.dv-void-container')!;
|
||||
const container = cut.element.querySelector('.void-container')!;
|
||||
expect(container).toBeTruthy();
|
||||
|
||||
jest.spyOn(cut.element, 'getBoundingClientRect').mockImplementation(
|
||||
@ -556,312 +562,80 @@ describe('tabsContainer', () => {
|
||||
return { top: 10, left: 20, width: 0, height: 0 } as any;
|
||||
});
|
||||
|
||||
const event = new KeyboardEvent('pointerdown', { shiftKey: true });
|
||||
const event = new KeyboardEvent('mousedown', { shiftKey: true });
|
||||
const eventPreventDefaultSpy = jest.spyOn(event, 'preventDefault');
|
||||
fireEvent(container, event);
|
||||
|
||||
expect(accessor.doSetGroupActive).toHaveBeenCalledWith(groupPanel);
|
||||
expect(accessor.addFloatingGroup).toHaveBeenCalledTimes(0);
|
||||
expect(eventPreventDefaultSpy).toHaveBeenCalledTimes(0);
|
||||
expect(accessor.addFloatingGroup).toBeCalledTimes(0);
|
||||
expect(eventPreventDefaultSpy).toBeCalledTimes(0);
|
||||
|
||||
const event2 = new KeyboardEvent('pointerdown', { shiftKey: false });
|
||||
const event2 = new KeyboardEvent('mousedown', { shiftKey: false });
|
||||
const eventPreventDefaultSpy2 = jest.spyOn(event2, 'preventDefault');
|
||||
fireEvent(container, event2);
|
||||
|
||||
expect(accessor.addFloatingGroup).toHaveBeenCalledTimes(0);
|
||||
expect(eventPreventDefaultSpy2).toHaveBeenCalledTimes(0);
|
||||
expect(accessor.addFloatingGroup).toBeCalledTimes(0);
|
||||
expect(eventPreventDefaultSpy2).toBeCalledTimes(0);
|
||||
});
|
||||
|
||||
test('that selecting a tab with shift down will move that tab into a new floating group', () => {
|
||||
const accessor = fromPartial<DockviewComponent>({
|
||||
options: {},
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
element: document.createElement('div'),
|
||||
addFloatingGroup: jest.fn(),
|
||||
getGroupPanel: jest.fn(),
|
||||
onDidOptionsChange: jest.fn(),
|
||||
const accessorMock = jest.fn<DockviewComponent, []>(() => {
|
||||
return (<Partial<DockviewComponent>>{
|
||||
options: {},
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
element: document.createElement('div'),
|
||||
addFloatingGroup: jest.fn(),
|
||||
getGroupPanel: jest.fn(),
|
||||
}) as DockviewComponent;
|
||||
});
|
||||
|
||||
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||
return (<Partial<DockviewGroupPanel>>{
|
||||
api: { location: { type: 'floating' } } as any,
|
||||
api: { isFloating: true } as any,
|
||||
model: {} as any,
|
||||
}) as DockviewGroupPanel;
|
||||
});
|
||||
|
||||
const accessor = new accessorMock();
|
||||
const groupPanel = new groupPanelMock();
|
||||
|
||||
const cut = new TabsContainer(accessor, groupPanel);
|
||||
|
||||
const createPanel = (id: string) =>
|
||||
fromPartial<IDockviewPanel>({
|
||||
const panelMock = jest.fn<IDockviewPanel, [string]>((id: string) => {
|
||||
const partial: Partial<IDockviewPanel> = {
|
||||
id,
|
||||
|
||||
view: {
|
||||
tab: {
|
||||
element: document.createElement('div'),
|
||||
},
|
||||
} as any,
|
||||
content: {
|
||||
element: document.createElement('div'),
|
||||
},
|
||||
},
|
||||
});
|
||||
} as any,
|
||||
} as any,
|
||||
};
|
||||
return partial as IDockviewPanel;
|
||||
});
|
||||
|
||||
const panel = createPanel('test_id');
|
||||
const panel = new panelMock('test_id');
|
||||
cut.openPanel(panel);
|
||||
|
||||
const el = cut.element.querySelector('.dv-tab')!;
|
||||
const el = cut.element.querySelector('.tab')!;
|
||||
expect(el).toBeTruthy();
|
||||
|
||||
const event = new KeyboardEvent('pointerdown', { shiftKey: true });
|
||||
const event = new KeyboardEvent('mousedown', { shiftKey: true });
|
||||
const preventDefaultSpy = jest.spyOn(event, 'preventDefault');
|
||||
fireEvent(el, event);
|
||||
|
||||
// a floating group with a single tab shouldn't be eligible
|
||||
expect(preventDefaultSpy).toHaveBeenCalledTimes(0);
|
||||
expect(accessor.addFloatingGroup).toHaveBeenCalledTimes(0);
|
||||
expect(preventDefaultSpy).toBeCalledTimes(0);
|
||||
expect(accessor.addFloatingGroup).toBeCalledTimes(0);
|
||||
|
||||
const panel2 = createPanel('test_id_2');
|
||||
const panel2 = new panelMock('test_id_2');
|
||||
cut.openPanel(panel2);
|
||||
fireEvent(el, event);
|
||||
|
||||
expect(preventDefaultSpy).toHaveBeenCalledTimes(1);
|
||||
expect(accessor.addFloatingGroup).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('pre header actions', () => {
|
||||
const accessor = fromPartial<DockviewComponent>({
|
||||
options: {},
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
element: document.createElement('div'),
|
||||
addFloatingGroup: jest.fn(),
|
||||
getGroupPanel: jest.fn(),
|
||||
onDidOptionsChange: jest.fn(),
|
||||
});
|
||||
|
||||
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||
return (<Partial<DockviewGroupPanel>>{
|
||||
api: { location: { type: 'grid' } } as any,
|
||||
model: {} as any,
|
||||
}) as DockviewGroupPanel;
|
||||
});
|
||||
|
||||
const groupPanel = new groupPanelMock();
|
||||
|
||||
const cut = new TabsContainer(accessor, groupPanel);
|
||||
|
||||
const panelMock = jest.fn<IDockviewPanel, [string]>((id: string) => {
|
||||
const partial: Partial<IDockviewPanel> = {
|
||||
id,
|
||||
|
||||
view: {
|
||||
tab: {
|
||||
element: document.createElement('div'),
|
||||
} as any,
|
||||
content: {
|
||||
element: document.createElement('div'),
|
||||
} as any,
|
||||
} as any,
|
||||
};
|
||||
return partial as IDockviewPanel;
|
||||
});
|
||||
|
||||
const panel = new panelMock('test_id');
|
||||
cut.openPanel(panel);
|
||||
|
||||
let result = cut.element.querySelector('.dv-pre-actions-container');
|
||||
expect(result).toBeTruthy();
|
||||
expect(result!.childNodes.length).toBe(0);
|
||||
|
||||
const actions = document.createElement('div');
|
||||
cut.setPrefixActionsElement(actions);
|
||||
|
||||
result = cut.element.querySelector('.dv-pre-actions-container');
|
||||
expect(result).toBeTruthy();
|
||||
expect(result!.childNodes.length).toBe(1);
|
||||
expect(result!.childNodes.item(0)).toBe(actions);
|
||||
|
||||
const updatedActions = document.createElement('div');
|
||||
cut.setPrefixActionsElement(updatedActions);
|
||||
|
||||
result = cut.element.querySelector('.dv-pre-actions-container');
|
||||
expect(result).toBeTruthy();
|
||||
expect(result!.childNodes.length).toBe(1);
|
||||
expect(result!.childNodes.item(0)).toBe(updatedActions);
|
||||
|
||||
cut.setPrefixActionsElement(undefined);
|
||||
|
||||
result = cut.element.querySelector('.dv-pre-actions-container');
|
||||
expect(result).toBeTruthy();
|
||||
expect(result!.childNodes.length).toBe(0);
|
||||
});
|
||||
|
||||
test('left header actions', () => {
|
||||
const accessor = fromPartial<DockviewComponent>({
|
||||
options: {},
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
element: document.createElement('div'),
|
||||
addFloatingGroup: jest.fn(),
|
||||
getGroupPanel: jest.fn(),
|
||||
onDidOptionsChange: jest.fn(),
|
||||
});
|
||||
|
||||
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||
return (<Partial<DockviewGroupPanel>>{
|
||||
api: { location: { type: 'grid' } } as any,
|
||||
model: {} as any,
|
||||
}) as DockviewGroupPanel;
|
||||
});
|
||||
|
||||
const groupPanel = new groupPanelMock();
|
||||
|
||||
const cut = new TabsContainer(accessor, groupPanel);
|
||||
|
||||
const panelMock = jest.fn<IDockviewPanel, [string]>((id: string) => {
|
||||
const partial: Partial<IDockviewPanel> = {
|
||||
id,
|
||||
|
||||
view: {
|
||||
tab: {
|
||||
element: document.createElement('div'),
|
||||
} as any,
|
||||
content: {
|
||||
element: document.createElement('div'),
|
||||
} as any,
|
||||
} as any,
|
||||
};
|
||||
return partial as IDockviewPanel;
|
||||
});
|
||||
|
||||
const panel = new panelMock('test_id');
|
||||
cut.openPanel(panel);
|
||||
|
||||
let result = cut.element.querySelector('.dv-left-actions-container');
|
||||
expect(result).toBeTruthy();
|
||||
expect(result!.childNodes.length).toBe(0);
|
||||
|
||||
const actions = document.createElement('div');
|
||||
cut.setLeftActionsElement(actions);
|
||||
|
||||
result = cut.element.querySelector('.dv-left-actions-container');
|
||||
expect(result).toBeTruthy();
|
||||
expect(result!.childNodes.length).toBe(1);
|
||||
expect(result!.childNodes.item(0)).toBe(actions);
|
||||
|
||||
const updatedActions = document.createElement('div');
|
||||
cut.setLeftActionsElement(updatedActions);
|
||||
|
||||
result = cut.element.querySelector('.dv-left-actions-container');
|
||||
expect(result).toBeTruthy();
|
||||
expect(result!.childNodes.length).toBe(1);
|
||||
expect(result!.childNodes.item(0)).toBe(updatedActions);
|
||||
|
||||
cut.setLeftActionsElement(undefined);
|
||||
|
||||
result = cut.element.querySelector('.dv-left-actions-container');
|
||||
expect(result).toBeTruthy();
|
||||
expect(result!.childNodes.length).toBe(0);
|
||||
});
|
||||
|
||||
test('right header actions', () => {
|
||||
const accessor = fromPartial<DockviewComponent>({
|
||||
options: {},
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
element: document.createElement('div'),
|
||||
addFloatingGroup: jest.fn(),
|
||||
getGroupPanel: jest.fn(),
|
||||
onDidOptionsChange: jest.fn(),
|
||||
});
|
||||
|
||||
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||
return (<Partial<DockviewGroupPanel>>{
|
||||
api: { location: { type: 'grid' } } as any,
|
||||
model: {} as any,
|
||||
}) as DockviewGroupPanel;
|
||||
});
|
||||
|
||||
const groupPanel = new groupPanelMock();
|
||||
|
||||
const cut = new TabsContainer(accessor, groupPanel);
|
||||
|
||||
const panelMock = jest.fn<IDockviewPanel, [string]>((id: string) => {
|
||||
const partial: Partial<IDockviewPanel> = {
|
||||
id,
|
||||
|
||||
view: {
|
||||
tab: {
|
||||
element: document.createElement('div'),
|
||||
} as any,
|
||||
content: {
|
||||
element: document.createElement('div'),
|
||||
} as any,
|
||||
} as any,
|
||||
};
|
||||
return partial as IDockviewPanel;
|
||||
});
|
||||
|
||||
const panel = new panelMock('test_id');
|
||||
cut.openPanel(panel);
|
||||
|
||||
let result = cut.element.querySelector('.dv-right-actions-container');
|
||||
expect(result).toBeTruthy();
|
||||
expect(result!.childNodes.length).toBe(0);
|
||||
|
||||
const actions = document.createElement('div');
|
||||
cut.setRightActionsElement(actions);
|
||||
|
||||
result = cut.element.querySelector('.dv-right-actions-container');
|
||||
expect(result).toBeTruthy();
|
||||
expect(result!.childNodes.length).toBe(1);
|
||||
expect(result!.childNodes.item(0)).toBe(actions);
|
||||
|
||||
const updatedActions = document.createElement('div');
|
||||
cut.setRightActionsElement(updatedActions);
|
||||
|
||||
result = cut.element.querySelector('.dv-right-actions-container');
|
||||
expect(result).toBeTruthy();
|
||||
expect(result!.childNodes.length).toBe(1);
|
||||
expect(result!.childNodes.item(0)).toBe(updatedActions);
|
||||
|
||||
cut.setRightActionsElement(undefined);
|
||||
|
||||
result = cut.element.querySelector('.dv-right-actions-container');
|
||||
expect(result).toBeTruthy();
|
||||
expect(result!.childNodes.length).toBe(0);
|
||||
});
|
||||
|
||||
test('class dv-single-tab is present when only one tab exists`', () => {
|
||||
const cut = new TabsContainer(
|
||||
fromPartial<DockviewComponent>({
|
||||
options: {},
|
||||
onDidOptionsChange: jest.fn(),
|
||||
}),
|
||||
fromPartial<DockviewGroupPanel>({})
|
||||
);
|
||||
|
||||
expect(cut.element.classList.contains('dv-single-tab')).toBeFalsy();
|
||||
|
||||
const panel1 = new TestPanel(
|
||||
'panel_1',
|
||||
fromPartial<DockviewPanelApi>({})
|
||||
);
|
||||
cut.openPanel(panel1);
|
||||
expect(cut.element.classList.contains('dv-single-tab')).toBeTruthy();
|
||||
|
||||
const panel2 = new TestPanel(
|
||||
'panel_2',
|
||||
fromPartial<DockviewPanelApi>({})
|
||||
);
|
||||
cut.openPanel(panel2);
|
||||
expect(cut.element.classList.contains('dv-single-tab')).toBeFalsy();
|
||||
|
||||
cut.closePanel(panel1);
|
||||
expect(cut.element.classList.contains('dv-single-tab')).toBeTruthy();
|
||||
|
||||
cut.closePanel(panel2);
|
||||
expect(cut.element.classList.contains('dv-single-tab')).toBeFalsy();
|
||||
expect(preventDefaultSpy).toBeCalledTimes(1);
|
||||
expect(accessor.addFloatingGroup).toBeCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
@ -1,20 +0,0 @@
|
||||
import { VoidContainer } from '../../../../dockview/components/titlebar/voidContainer';
|
||||
import { fromPartial } from '@total-typescript/shoehorn';
|
||||
import { DockviewComponent } from '../../../../dockview/dockviewComponent';
|
||||
import { DockviewGroupPanel } from '../../../../dockview/dockviewGroupPanel';
|
||||
import { fireEvent } from '@testing-library/dom';
|
||||
|
||||
describe('voidContainer', () => {
|
||||
test('that `pointerDown` triggers activation', () => {
|
||||
const accessor = fromPartial<DockviewComponent>({
|
||||
doSetGroupActive: jest.fn(),
|
||||
});
|
||||
const group = fromPartial<DockviewGroupPanel>({});
|
||||
const cut = new VoidContainer(accessor, group);
|
||||
|
||||
expect(accessor.doSetGroupActive).not.toHaveBeenCalled();
|
||||
|
||||
fireEvent.pointerDown(cut.element);
|
||||
expect(accessor.doSetGroupActive).toHaveBeenCalledWith(group);
|
||||
});
|
||||
});
|
@ -0,0 +1,27 @@
|
||||
import { DockviewApi } from '../../../../api/component.api';
|
||||
import { Watermark } from '../../../../dockview/components/watermark/watermark';
|
||||
|
||||
describe('watermark', () => {
|
||||
test('that the group is closed when the close button is clicked', () => {
|
||||
const cut = new Watermark();
|
||||
|
||||
const mockApi = jest.fn<Partial<DockviewApi>, any[]>(() => {
|
||||
return {
|
||||
removeGroup: jest.fn(),
|
||||
};
|
||||
});
|
||||
const api = <DockviewApi>new mockApi();
|
||||
const group = jest.fn() as any;
|
||||
|
||||
cut.init({ containerApi: api });
|
||||
cut.updateParentGroup(group, true);
|
||||
|
||||
const closeEl = cut.element.querySelector('.close-action')!;
|
||||
|
||||
expect(closeEl).toBeTruthy();
|
||||
|
||||
closeEl.dispatchEvent(new Event('click'));
|
||||
|
||||
expect(api.removeGroup).toHaveBeenCalledWith(group);
|
||||
});
|
||||
});
|
File diff suppressed because it is too large
Load Diff
@ -1,190 +0,0 @@
|
||||
import { DockviewComponent } from '../../dockview/dockviewComponent';
|
||||
import { DockviewGroupPanel } from '../../dockview/dockviewGroupPanel';
|
||||
import { fromPartial } from '@total-typescript/shoehorn';
|
||||
import { GroupOptions } from '../../dockview/dockviewGroupPanelModel';
|
||||
import { DockviewPanel, IDockviewPanel } from '../../dockview/dockviewPanel';
|
||||
import { DockviewPanelModelMock } from '../__mocks__/mockDockviewPanelModel';
|
||||
import { IContentRenderer, ITabRenderer } from '../../dockview/types';
|
||||
import { OverlayRenderContainer } from '../../overlay/overlayRenderContainer';
|
||||
import { IDockviewPanelModel } from '../../dockview/dockviewPanelModel';
|
||||
import { ContentContainer } from '../../dockview/components/panel/content';
|
||||
|
||||
describe('dockviewGroupPanel', () => {
|
||||
test('default minimum/maximium width/height', () => {
|
||||
const accessor = fromPartial<DockviewComponent>({
|
||||
onDidActivePanelChange: jest.fn(),
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
options: {},
|
||||
onDidOptionsChange: jest.fn(),
|
||||
});
|
||||
const options = fromPartial<GroupOptions>({});
|
||||
const cut = new DockviewGroupPanel(accessor, 'test_id', options);
|
||||
|
||||
expect(cut.minimumWidth).toBe(100);
|
||||
expect(cut.minimumHeight).toBe(100);
|
||||
expect(cut.maximumHeight).toBe(Number.MAX_SAFE_INTEGER);
|
||||
expect(cut.maximumWidth).toBe(Number.MAX_SAFE_INTEGER);
|
||||
});
|
||||
|
||||
test('that onDidActivePanelChange is configured at inline', () => {
|
||||
const accessor = fromPartial<DockviewComponent>({
|
||||
onDidActivePanelChange: jest.fn(),
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
options: {},
|
||||
api: {},
|
||||
renderer: 'always',
|
||||
overlayRenderContainer: {
|
||||
attach: jest.fn(),
|
||||
detatch: jest.fn(),
|
||||
},
|
||||
doSetGroupActive: jest.fn(),
|
||||
onDidOptionsChange: jest.fn(),
|
||||
});
|
||||
const options = fromPartial<GroupOptions>({});
|
||||
|
||||
const cut = new DockviewGroupPanel(accessor, 'test_id', options);
|
||||
|
||||
let counter = 0;
|
||||
|
||||
cut.api.onDidActivePanelChange((event) => {
|
||||
counter++;
|
||||
});
|
||||
|
||||
cut.model.openPanel(
|
||||
fromPartial<IDockviewPanel>({
|
||||
updateParentGroup: jest.fn(),
|
||||
view: {
|
||||
tab: { element: document.createElement('div') },
|
||||
content: new ContentContainer(accessor, cut.model),
|
||||
},
|
||||
api: {
|
||||
renderer: 'onlyWhenVisible',
|
||||
onDidTitleChange: jest.fn(),
|
||||
onDidParametersChange: jest.fn(),
|
||||
},
|
||||
layout: jest.fn(),
|
||||
runEvents: jest.fn(),
|
||||
})
|
||||
);
|
||||
|
||||
expect(counter).toBe(1);
|
||||
});
|
||||
|
||||
test('group constraints', () => {
|
||||
const accessor = fromPartial<DockviewComponent>({
|
||||
onDidActivePanelChange: jest.fn(),
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
doSetGroupActive: jest.fn(),
|
||||
overlayRenderContainer: fromPartial<OverlayRenderContainer>({
|
||||
attach: jest.fn(),
|
||||
detatch: jest.fn(),
|
||||
}),
|
||||
options: {},
|
||||
onDidOptionsChange: jest.fn(),
|
||||
});
|
||||
const options = fromPartial<GroupOptions>({});
|
||||
const cut = new DockviewGroupPanel(accessor, 'test_id', options);
|
||||
|
||||
cut.api.setConstraints({
|
||||
minimumHeight: 10,
|
||||
maximumHeight: 100,
|
||||
minimumWidth: 20,
|
||||
maximumWidth: 200,
|
||||
});
|
||||
|
||||
// initial constraints
|
||||
|
||||
expect(cut.minimumWidth).toBe(20);
|
||||
expect(cut.minimumHeight).toBe(10);
|
||||
expect(cut.maximumHeight).toBe(100);
|
||||
expect(cut.maximumWidth).toBe(200);
|
||||
|
||||
const panelModel = new DockviewPanelModelMock(
|
||||
'content_component',
|
||||
fromPartial<IContentRenderer>({
|
||||
element: document.createElement('div'),
|
||||
}),
|
||||
'tab_component',
|
||||
fromPartial<ITabRenderer>({
|
||||
element: document.createElement('div'),
|
||||
})
|
||||
);
|
||||
|
||||
const panel = new DockviewPanel(
|
||||
'panel_id',
|
||||
'component_id',
|
||||
undefined,
|
||||
accessor,
|
||||
accessor.api,
|
||||
cut,
|
||||
panelModel,
|
||||
{
|
||||
renderer: 'onlyWhenVisible',
|
||||
minimumWidth: 21,
|
||||
minimumHeight: 11,
|
||||
maximumHeight: 101,
|
||||
maximumWidth: 201,
|
||||
}
|
||||
);
|
||||
|
||||
cut.model.openPanel(panel);
|
||||
|
||||
// active panel constraints
|
||||
|
||||
expect(cut.minimumWidth).toBe(21);
|
||||
expect(cut.minimumHeight).toBe(11);
|
||||
expect(cut.maximumHeight).toBe(101);
|
||||
expect(cut.maximumWidth).toBe(201);
|
||||
|
||||
const panel2 = new DockviewPanel(
|
||||
'panel_id',
|
||||
'component_id',
|
||||
undefined,
|
||||
accessor,
|
||||
accessor.api,
|
||||
cut,
|
||||
panelModel,
|
||||
{
|
||||
renderer: 'onlyWhenVisible',
|
||||
minimumWidth: 22,
|
||||
minimumHeight: 12,
|
||||
maximumHeight: 102,
|
||||
maximumWidth: 202,
|
||||
}
|
||||
);
|
||||
|
||||
cut.model.openPanel(panel2);
|
||||
|
||||
// active panel constraints
|
||||
|
||||
expect(cut.minimumWidth).toBe(22);
|
||||
expect(cut.minimumHeight).toBe(12);
|
||||
expect(cut.maximumHeight).toBe(102);
|
||||
expect(cut.maximumWidth).toBe(202);
|
||||
|
||||
const panel3 = new DockviewPanel(
|
||||
'panel_id',
|
||||
'component_id',
|
||||
undefined,
|
||||
accessor,
|
||||
accessor.api,
|
||||
cut,
|
||||
panelModel,
|
||||
{
|
||||
renderer: 'onlyWhenVisible',
|
||||
}
|
||||
);
|
||||
|
||||
cut.model.openPanel(panel3);
|
||||
|
||||
// active panel without specified constraints so falls back to group constraints
|
||||
|
||||
expect(cut.minimumWidth).toBe(20);
|
||||
expect(cut.minimumHeight).toBe(10);
|
||||
expect(cut.maximumHeight).toBe(100);
|
||||
expect(cut.maximumWidth).toBe(200);
|
||||
});
|
||||
});
|
@ -7,7 +7,7 @@ import {
|
||||
ITabRenderer,
|
||||
IWatermarkRenderer,
|
||||
} from '../../dockview/types';
|
||||
import { PanelUpdateEvent, Parameters } from '../../panel/types';
|
||||
import { PanelUpdateEvent } from '../../panel/types';
|
||||
import {
|
||||
DockviewGroupPanelModel,
|
||||
GroupOptions,
|
||||
@ -20,11 +20,6 @@ import { IDockviewPanel } from '../../dockview/dockviewPanel';
|
||||
import { IDockviewPanelModel } from '../../dockview/dockviewPanelModel';
|
||||
import { DockviewGroupPanel } from '../../dockview/dockviewGroupPanel';
|
||||
import { WatermarkRendererInitParameters } from '../../dockview/types';
|
||||
import { createOffsetDragOverEvent } from '../__test_utils__/utils';
|
||||
import { OverlayRenderContainer } from '../../overlay/overlayRenderContainer';
|
||||
import { Emitter } from '../../events';
|
||||
import { fromPartial } from '@total-typescript/shoehorn';
|
||||
import { TabLocation } from '../../dockview/framework';
|
||||
|
||||
enum GroupChangeKind2 {
|
||||
ADD_PANEL,
|
||||
@ -37,16 +32,12 @@ class TestModel implements IDockviewPanelModel {
|
||||
readonly contentComponent: string;
|
||||
readonly tab: ITabRenderer;
|
||||
|
||||
constructor(readonly id: string) {
|
||||
constructor(id: string) {
|
||||
this.content = new TestHeaderPart(id);
|
||||
this.contentComponent = id;
|
||||
this.tab = new TestContentPart(id);
|
||||
}
|
||||
|
||||
createTabRenderer(tabLocation: TabLocation): ITabRenderer {
|
||||
return new TestHeaderPart(this.id);
|
||||
}
|
||||
|
||||
update(event: PanelUpdateEvent): void {
|
||||
//
|
||||
}
|
||||
@ -102,6 +93,10 @@ class Watermark implements IWatermarkRenderer {
|
||||
return {};
|
||||
}
|
||||
|
||||
updateParentGroup() {
|
||||
//
|
||||
}
|
||||
|
||||
dispose() {
|
||||
//
|
||||
}
|
||||
@ -183,7 +178,7 @@ export class TestPanel implements IDockviewPanel {
|
||||
return this._group!;
|
||||
}
|
||||
|
||||
get params(): Parameters {
|
||||
get params(): Record<string, any> {
|
||||
return {};
|
||||
}
|
||||
|
||||
@ -199,11 +194,7 @@ export class TestPanel implements IDockviewPanel {
|
||||
this._params = params;
|
||||
}
|
||||
|
||||
updateParentGroup(group: DockviewGroupPanel): void {
|
||||
//
|
||||
}
|
||||
|
||||
runEvents(): void {
|
||||
updateParentGroup(group: DockviewGroupPanel, isGroupActive: boolean): void {
|
||||
//
|
||||
}
|
||||
|
||||
@ -235,7 +226,7 @@ export class TestPanel implements IDockviewPanel {
|
||||
}
|
||||
}
|
||||
|
||||
describe('dockviewGroupPanelModel', () => {
|
||||
describe('groupview', () => {
|
||||
let groupview: DockviewGroupPanel;
|
||||
let dockview: DockviewComponent;
|
||||
let options: GroupOptions;
|
||||
@ -243,21 +234,13 @@ describe('dockviewGroupPanelModel', () => {
|
||||
let removePanelMock: jest.Mock;
|
||||
let removeGroupMock: jest.Mock;
|
||||
|
||||
let panelApi: DockviewPanelApi;
|
||||
|
||||
beforeEach(() => {
|
||||
removePanelMock = jest.fn();
|
||||
removeGroupMock = jest.fn();
|
||||
|
||||
options = {};
|
||||
|
||||
panelApi = fromPartial<DockviewPanelApi>({
|
||||
renderer: 'onlyWhenVisible',
|
||||
onDidTitleChange: new Emitter().event,
|
||||
onDidParametersChange: new Emitter().event,
|
||||
});
|
||||
|
||||
dockview = fromPartial<DockviewComponent>({
|
||||
dockview = (<Partial<DockviewComponent>>{
|
||||
options: {},
|
||||
createWatermarkComponent: () => new Watermark(),
|
||||
doSetGroupActive: jest.fn(),
|
||||
@ -266,21 +249,16 @@ describe('dockviewGroupPanelModel', () => {
|
||||
removeGroup: removeGroupMock,
|
||||
onDidAddPanel: () => ({ dispose: jest.fn() }),
|
||||
onDidRemovePanel: () => ({ dispose: jest.fn() }),
|
||||
overlayRenderContainer: new OverlayRenderContainer(
|
||||
document.createElement('div'),
|
||||
fromPartial<DockviewComponent>({})
|
||||
),
|
||||
onDidOptionsChange: () => ({ dispose: jest.fn() }),
|
||||
});
|
||||
}) as DockviewComponent;
|
||||
|
||||
groupview = new DockviewGroupPanel(dockview, 'groupview-1', options);
|
||||
groupview.initialize();
|
||||
});
|
||||
|
||||
test('panel events are captured during de-serialization', () => {
|
||||
const panel1 = new TestPanel('panel1', panelApi);
|
||||
const panel2 = new TestPanel('panel2', panelApi);
|
||||
const panel3 = new TestPanel('panel3', panelApi);
|
||||
const panel1 = new TestPanel('panel1', jest.fn() as any);
|
||||
const panel2 = new TestPanel('panel2', jest.fn() as any);
|
||||
const panel3 = new TestPanel('panel3', jest.fn() as any);
|
||||
|
||||
const groupview2 = new DockviewGroupPanel(dockview, 'groupview-2', {
|
||||
panels: [panel1, panel2, panel3],
|
||||
@ -364,9 +342,9 @@ describe('dockviewGroupPanelModel', () => {
|
||||
})
|
||||
);
|
||||
|
||||
const panel1 = new TestPanel('panel1', panelApi);
|
||||
const panel2 = new TestPanel('panel2', panelApi);
|
||||
const panel3 = new TestPanel('panel3', panelApi);
|
||||
const panel1 = new TestPanel('panel1', jest.fn() as any);
|
||||
const panel2 = new TestPanel('panel2', jest.fn() as any);
|
||||
const panel3 = new TestPanel('panel3', jest.fn() as any);
|
||||
|
||||
expect(events.length).toBe(0);
|
||||
|
||||
@ -444,9 +422,9 @@ describe('dockviewGroupPanelModel', () => {
|
||||
});
|
||||
|
||||
test('moveToPrevious and moveToNext', () => {
|
||||
const panel1 = new TestPanel('panel1', panelApi);
|
||||
const panel2 = new TestPanel('panel2', panelApi);
|
||||
const panel3 = new TestPanel('panel3', panelApi);
|
||||
const panel1 = new TestPanel('panel1', jest.fn() as any);
|
||||
const panel2 = new TestPanel('panel2', jest.fn() as any);
|
||||
const panel3 = new TestPanel('panel3', jest.fn() as any);
|
||||
|
||||
groupview.model.openPanel(panel1);
|
||||
groupview.model.openPanel(panel2);
|
||||
@ -479,20 +457,20 @@ describe('dockviewGroupPanelModel', () => {
|
||||
|
||||
test('default', () => {
|
||||
let viewQuery = groupview.element.querySelectorAll(
|
||||
'.dv-groupview > .dv-tabs-and-actions-container'
|
||||
'.groupview > .tabs-and-actions-container'
|
||||
);
|
||||
expect(viewQuery).toBeTruthy();
|
||||
|
||||
viewQuery = groupview.element.querySelectorAll(
|
||||
'.dv-groupview > .dv-content-container'
|
||||
'.groupview > .content-container'
|
||||
);
|
||||
expect(viewQuery).toBeTruthy();
|
||||
});
|
||||
|
||||
test('closeAllPanels with panels', () => {
|
||||
const panel1 = new TestPanel('panel1', panelApi);
|
||||
const panel2 = new TestPanel('panel2', panelApi);
|
||||
const panel3 = new TestPanel('panel3', panelApi);
|
||||
const panel1 = new TestPanel('panel1', jest.fn() as any);
|
||||
const panel2 = new TestPanel('panel2', jest.fn() as any);
|
||||
const panel3 = new TestPanel('panel3', jest.fn() as any);
|
||||
|
||||
groupview.model.openPanel(panel1);
|
||||
groupview.model.openPanel(panel2);
|
||||
@ -500,25 +478,21 @@ describe('dockviewGroupPanelModel', () => {
|
||||
|
||||
groupview.model.closeAllPanels();
|
||||
|
||||
expect(removePanelMock).toHaveBeenCalledWith(panel1, undefined);
|
||||
expect(removePanelMock).toHaveBeenCalledWith(panel2, undefined);
|
||||
expect(removePanelMock).toHaveBeenCalledWith(panel3, undefined);
|
||||
expect(removePanelMock).toBeCalledWith(panel1);
|
||||
expect(removePanelMock).toBeCalledWith(panel2);
|
||||
expect(removePanelMock).toBeCalledWith(panel3);
|
||||
});
|
||||
|
||||
test('closeAllPanels with no panels', () => {
|
||||
groupview.model.closeAllPanels();
|
||||
expect(removeGroupMock).toHaveBeenCalledWith(groupview);
|
||||
expect(removeGroupMock).toBeCalledWith(groupview);
|
||||
});
|
||||
|
||||
test('that group is set on panel during onDidAddPanel event', () => {
|
||||
const cut = new DockviewComponent(document.createElement('div'), {
|
||||
createComponent(options) {
|
||||
switch (options.name) {
|
||||
case 'component':
|
||||
return new TestContentPart(options.id);
|
||||
default:
|
||||
throw new Error(`unsupported`);
|
||||
}
|
||||
const cut = new DockviewComponent({
|
||||
parentElement: document.createElement('div'),
|
||||
components: {
|
||||
component: TestContentPart,
|
||||
},
|
||||
});
|
||||
|
||||
@ -531,19 +505,12 @@ describe('dockviewGroupPanelModel', () => {
|
||||
});
|
||||
|
||||
test('toJSON() default', () => {
|
||||
const dockviewComponent = new DockviewComponent(
|
||||
document.createElement('div'),
|
||||
{
|
||||
createComponent(options) {
|
||||
switch (options.name) {
|
||||
case 'component':
|
||||
return new TestContentPart(options.id);
|
||||
default:
|
||||
throw new Error(`unsupported`);
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
const dockviewComponent = new DockviewComponent({
|
||||
parentElement: document.createElement('div'),
|
||||
components: {
|
||||
component: TestContentPart,
|
||||
},
|
||||
});
|
||||
|
||||
const cut = new DockviewGroupPanelModel(
|
||||
document.createElement('div'),
|
||||
@ -561,19 +528,12 @@ describe('dockviewGroupPanelModel', () => {
|
||||
});
|
||||
|
||||
test('toJSON() locked and hideHeader', () => {
|
||||
const dockviewComponent = new DockviewComponent(
|
||||
document.createElement('div'),
|
||||
{
|
||||
createComponent(options) {
|
||||
switch (options.name) {
|
||||
case 'component':
|
||||
return new TestContentPart(options.id);
|
||||
default:
|
||||
throw new Error(`unsupported`);
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
const dockviewComponent = new DockviewComponent({
|
||||
parentElement: document.createElement('div'),
|
||||
components: {
|
||||
component: TestContentPart,
|
||||
},
|
||||
});
|
||||
|
||||
const cut = new DockviewGroupPanelModel(
|
||||
document.createElement('div'),
|
||||
@ -596,19 +556,12 @@ describe('dockviewGroupPanelModel', () => {
|
||||
});
|
||||
|
||||
test("that openPanel with skipSetActive doesn't set panel to active", () => {
|
||||
const dockviewComponent = new DockviewComponent(
|
||||
document.createElement('div'),
|
||||
{
|
||||
createComponent(options) {
|
||||
switch (options.name) {
|
||||
case 'component':
|
||||
return new TestContentPart(options.id);
|
||||
default:
|
||||
throw new Error(`unsupported`);
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
const dockviewComponent = new DockviewComponent({
|
||||
parentElement: document.createElement('div'),
|
||||
components: {
|
||||
component: TestContentPart,
|
||||
},
|
||||
});
|
||||
|
||||
const groupviewContainer = document.createElement('div');
|
||||
const cut = new DockviewGroupPanelModel(
|
||||
@ -619,24 +572,24 @@ describe('dockviewGroupPanelModel', () => {
|
||||
null as any
|
||||
);
|
||||
const contentContainer = groupviewContainer
|
||||
.getElementsByClassName('dv-content-container')
|
||||
.getElementsByClassName('content-container')
|
||||
.item(0)!.childNodes;
|
||||
|
||||
const panel1 = new TestPanel('id_1', panelApi);
|
||||
const panel1 = new TestPanel('id_1', null as any);
|
||||
|
||||
cut.openPanel(panel1);
|
||||
expect(contentContainer.length).toBe(1);
|
||||
expect(contentContainer.item(0)).toBe(panel1.view.content.element);
|
||||
|
||||
const panel2 = new TestPanel('id_2', panelApi);
|
||||
const panel2 = new TestPanel('id_2', null as any);
|
||||
|
||||
cut.openPanel(panel2);
|
||||
expect(contentContainer.length).toBe(1);
|
||||
expect(contentContainer.item(0)).toBe(panel2.view.content.element);
|
||||
|
||||
const panel3 = new TestPanel('id_2', panelApi);
|
||||
const panel3 = new TestPanel('id_2', null as any);
|
||||
|
||||
cut.openPanel(panel3, { skipSetActive: true });
|
||||
cut.openPanel(panel3, { skipSetPanelActive: true });
|
||||
expect(contentContainer.length).toBe(1);
|
||||
expect(contentContainer.item(0)).toBe(panel2.view.content.element);
|
||||
|
||||
@ -646,15 +599,18 @@ describe('dockviewGroupPanelModel', () => {
|
||||
});
|
||||
|
||||
test('that should not show drop target is external event', () => {
|
||||
const accessor = fromPartial<DockviewComponent>({
|
||||
id: 'testcomponentid',
|
||||
options: {},
|
||||
getPanel: jest.fn(),
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
onDidOptionsChange: jest.fn(),
|
||||
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
|
||||
return {
|
||||
id: 'testcomponentid',
|
||||
options: {
|
||||
showDndOverlay: jest.fn(),
|
||||
},
|
||||
getPanel: jest.fn(),
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
const accessor = new accessorMock() as DockviewComponent;
|
||||
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
|
||||
() => {
|
||||
return {
|
||||
@ -683,205 +639,39 @@ describe('dockviewGroupPanelModel', () => {
|
||||
new groupPanelMock() as DockviewGroupPanel
|
||||
);
|
||||
|
||||
let counter = 0;
|
||||
|
||||
cut.onUnhandledDragOverEvent(() => {
|
||||
counter++;
|
||||
});
|
||||
|
||||
const element = container
|
||||
.getElementsByClassName('dv-content-container')
|
||||
.item(0)! as HTMLElement;
|
||||
.getElementsByClassName('content-container')
|
||||
.item(0)!;
|
||||
|
||||
jest.spyOn(element, 'offsetHeight', 'get').mockImplementation(
|
||||
jest.spyOn(element, 'clientHeight', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
jest.spyOn(element, 'offsetWidth', 'get').mockImplementation(() => 100);
|
||||
jest.spyOn(element, 'clientWidth', 'get').mockImplementation(() => 100);
|
||||
|
||||
fireEvent.dragEnter(element);
|
||||
fireEvent.dragOver(element);
|
||||
|
||||
expect(counter).toBe(1);
|
||||
expect(accessor.options.showDndOverlay).toBeCalledTimes(1);
|
||||
|
||||
expect(
|
||||
element.getElementsByClassName('dv-drop-target-dropzone').length
|
||||
element.getElementsByClassName('drop-target-dropzone').length
|
||||
).toBe(0);
|
||||
});
|
||||
|
||||
test('that the .locked behaviour is as', () => {
|
||||
const accessor = fromPartial<DockviewComponent>({
|
||||
id: 'testcomponentid',
|
||||
options: {},
|
||||
getPanel: jest.fn(),
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
onDidOptionsChange: jest.fn(),
|
||||
});
|
||||
|
||||
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
|
||||
() => {
|
||||
return {
|
||||
canDisplayOverlay: jest.fn(),
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const groupView = new groupviewMock() as DockviewGroupPanelModel;
|
||||
|
||||
const groupPanelMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
|
||||
() => {
|
||||
return {
|
||||
id: 'testgroupid',
|
||||
model: groupView,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const container = document.createElement('div');
|
||||
const cut = new DockviewGroupPanelModel(
|
||||
container,
|
||||
accessor,
|
||||
'groupviewid',
|
||||
{},
|
||||
new groupPanelMock() as DockviewGroupPanel
|
||||
);
|
||||
|
||||
cut.onUnhandledDragOverEvent((e) => {
|
||||
e.accept();
|
||||
});
|
||||
|
||||
const element = container
|
||||
.getElementsByClassName('dv-content-container')
|
||||
.item(0)! as HTMLElement;
|
||||
|
||||
jest.spyOn(element, 'offsetHeight', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
jest.spyOn(element, 'offsetWidth', 'get').mockImplementation(() => 100);
|
||||
|
||||
function run(value: number) {
|
||||
fireEvent.dragEnter(element);
|
||||
fireEvent(
|
||||
element,
|
||||
createOffsetDragOverEvent({ clientX: value, clientY: value })
|
||||
);
|
||||
}
|
||||
|
||||
// base case - not locked
|
||||
cut.locked = false;
|
||||
run(10);
|
||||
expect(
|
||||
element.getElementsByClassName('dv-drop-target-dropzone').length
|
||||
).toBe(1);
|
||||
fireEvent.dragEnd(element);
|
||||
|
||||
// special case - locked with no possible target
|
||||
cut.locked = 'no-drop-target';
|
||||
run(10);
|
||||
expect(
|
||||
element.getElementsByClassName('dv-drop-target-dropzone').length
|
||||
).toBe(0);
|
||||
fireEvent.dragEnd(element);
|
||||
|
||||
// standard locked - only show if not center target
|
||||
cut.locked = true;
|
||||
run(10);
|
||||
expect(
|
||||
element.getElementsByClassName('dv-drop-target-dropzone').length
|
||||
).toBe(1);
|
||||
fireEvent.dragEnd(element);
|
||||
|
||||
// standard locked but for center target - expect not shown
|
||||
cut.locked = true;
|
||||
run(25);
|
||||
expect(
|
||||
element.getElementsByClassName('dv-drop-target-dropzone').length
|
||||
).toBe(0);
|
||||
fireEvent.dragEnd(element);
|
||||
});
|
||||
|
||||
test('that should show drop target if dropping on self', () => {
|
||||
const accessor = fromPartial<DockviewComponent>({
|
||||
id: 'testcomponentid',
|
||||
options: {},
|
||||
getPanel: jest.fn(),
|
||||
doSetGroupActive: jest.fn(),
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
overlayRenderContainer: new OverlayRenderContainer(
|
||||
document.createElement('div'),
|
||||
fromPartial<DockviewComponent>({})
|
||||
),
|
||||
onDidOptionsChange: jest.fn(),
|
||||
});
|
||||
|
||||
const groupView = fromPartial<DockviewGroupPanelModel>({
|
||||
canDisplayOverlay: jest.fn(),
|
||||
});
|
||||
|
||||
const groupPanelMock = jest.fn<Partial<DockviewGroupPanel>, []>(() => {
|
||||
test('that should not show drop target if dropping on self', () => {
|
||||
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
|
||||
return {
|
||||
id: 'testgroupid',
|
||||
model: groupView,
|
||||
id: 'testcomponentid',
|
||||
options: {
|
||||
showDndOverlay: jest.fn(),
|
||||
},
|
||||
getPanel: jest.fn(),
|
||||
doSetGroupActive: jest.fn(),
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
const container = document.createElement('div');
|
||||
const cut = new DockviewGroupPanelModel(
|
||||
container,
|
||||
accessor,
|
||||
'groupviewid',
|
||||
{},
|
||||
new groupPanelMock() as DockviewGroupPanel
|
||||
);
|
||||
|
||||
let counter = 0;
|
||||
|
||||
cut.onUnhandledDragOverEvent(() => {
|
||||
counter++;
|
||||
});
|
||||
|
||||
cut.openPanel(new TestPanel('panel1', panelApi));
|
||||
|
||||
const element = container
|
||||
.getElementsByClassName('dv-content-container')
|
||||
.item(0)! as HTMLElement;
|
||||
|
||||
jest.spyOn(element, 'offsetHeight', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
jest.spyOn(element, 'offsetWidth', 'get').mockImplementation(() => 100);
|
||||
|
||||
LocalSelectionTransfer.getInstance().setData(
|
||||
[new PanelTransfer('testcomponentid', 'groupviewid', 'panel1')],
|
||||
PanelTransfer.prototype
|
||||
);
|
||||
|
||||
fireEvent.dragEnter(element);
|
||||
fireEvent.dragOver(element);
|
||||
|
||||
expect(counter).toBe(0);
|
||||
|
||||
expect(
|
||||
element.getElementsByClassName('dv-drop-target-dropzone').length
|
||||
).toBe(1);
|
||||
});
|
||||
|
||||
test('that should allow drop when dropping on self for same component id', () => {
|
||||
const accessor = fromPartial<DockviewComponent>({
|
||||
id: 'testcomponentid',
|
||||
options: {},
|
||||
getPanel: jest.fn(),
|
||||
doSetGroupActive: jest.fn(),
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
overlayRenderContainer: new OverlayRenderContainer(
|
||||
document.createElement('div'),
|
||||
fromPartial<DockviewComponent>({})
|
||||
),
|
||||
onDidOptionsChange: jest.fn(),
|
||||
});
|
||||
|
||||
const accessor = new accessorMock() as DockviewComponent;
|
||||
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
|
||||
() => {
|
||||
return {
|
||||
@ -908,23 +698,16 @@ describe('dockviewGroupPanelModel', () => {
|
||||
new groupPanelMock() as DockviewGroupPanel
|
||||
);
|
||||
|
||||
let counter = 0;
|
||||
|
||||
cut.onUnhandledDragOverEvent(() => {
|
||||
counter++;
|
||||
});
|
||||
|
||||
cut.openPanel(new TestPanel('panel1', panelApi));
|
||||
cut.openPanel(new TestPanel('panel2', panelApi));
|
||||
cut.openPanel(new TestPanel('panel1', jest.fn() as any));
|
||||
|
||||
const element = container
|
||||
.getElementsByClassName('dv-content-container')
|
||||
.item(0) as HTMLElement;
|
||||
.getElementsByClassName('content-container')
|
||||
.item(0)!;
|
||||
|
||||
jest.spyOn(element, 'offsetHeight', 'get').mockImplementation(
|
||||
jest.spyOn(element, 'clientHeight', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
jest.spyOn(element, 'offsetWidth', 'get').mockImplementation(() => 100);
|
||||
jest.spyOn(element, 'clientWidth', 'get').mockImplementation(() => 100);
|
||||
|
||||
LocalSelectionTransfer.getInstance().setData(
|
||||
[new PanelTransfer('testcomponentid', 'groupviewid', 'panel1')],
|
||||
@ -934,28 +717,94 @@ describe('dockviewGroupPanelModel', () => {
|
||||
fireEvent.dragEnter(element);
|
||||
fireEvent.dragOver(element);
|
||||
|
||||
expect(counter).toBe(0);
|
||||
expect(accessor.options.showDndOverlay).toBeCalledTimes(0);
|
||||
|
||||
expect(
|
||||
element.getElementsByClassName('dv-drop-target-dropzone').length
|
||||
).toBe(1);
|
||||
element.getElementsByClassName('drop-target-dropzone').length
|
||||
).toBe(0);
|
||||
});
|
||||
|
||||
test('that should not allow drop when dropping on self for same component id', () => {
|
||||
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
|
||||
return {
|
||||
id: 'testcomponentid',
|
||||
options: {
|
||||
showDndOverlay: jest.fn(),
|
||||
},
|
||||
getPanel: jest.fn(),
|
||||
doSetGroupActive: jest.fn(),
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
};
|
||||
});
|
||||
const accessor = new accessorMock() as DockviewComponent;
|
||||
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
|
||||
() => {
|
||||
return {
|
||||
canDisplayOverlay: jest.fn(),
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const groupView = new groupviewMock() as DockviewGroupPanelModel;
|
||||
|
||||
const groupPanelMock = jest.fn<Partial<DockviewGroupPanel>, []>(() => {
|
||||
return {
|
||||
id: 'testgroupid',
|
||||
model: groupView,
|
||||
};
|
||||
});
|
||||
|
||||
const container = document.createElement('div');
|
||||
const cut = new DockviewGroupPanelModel(
|
||||
container,
|
||||
accessor,
|
||||
'groupviewid',
|
||||
{},
|
||||
new groupPanelMock() as DockviewGroupPanel
|
||||
);
|
||||
|
||||
cut.openPanel(new TestPanel('panel1', jest.fn() as any));
|
||||
cut.openPanel(new TestPanel('panel2', jest.fn() as any));
|
||||
|
||||
const element = container
|
||||
.getElementsByClassName('content-container')
|
||||
.item(0)!;
|
||||
|
||||
jest.spyOn(element, 'clientHeight', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
jest.spyOn(element, 'clientWidth', 'get').mockImplementation(() => 100);
|
||||
|
||||
LocalSelectionTransfer.getInstance().setData(
|
||||
[new PanelTransfer('testcomponentid', 'groupviewid', 'panel1')],
|
||||
PanelTransfer.prototype
|
||||
);
|
||||
|
||||
fireEvent.dragEnter(element);
|
||||
fireEvent.dragOver(element);
|
||||
|
||||
expect(accessor.options.showDndOverlay).toBeCalledTimes(0);
|
||||
|
||||
expect(
|
||||
element.getElementsByClassName('drop-target-dropzone').length
|
||||
).toBe(0);
|
||||
});
|
||||
|
||||
test('that should not allow drop when not dropping for different component id', () => {
|
||||
const accessor = fromPartial<DockviewComponent>({
|
||||
id: 'testcomponentid',
|
||||
options: {},
|
||||
getPanel: jest.fn(),
|
||||
doSetGroupActive: jest.fn(),
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
overlayRenderContainer: new OverlayRenderContainer(
|
||||
document.createElement('div'),
|
||||
fromPartial<DockviewComponent>({})
|
||||
),
|
||||
onDidOptionsChange: jest.fn(),
|
||||
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
|
||||
return {
|
||||
id: 'testcomponentid',
|
||||
options: {
|
||||
showDndOverlay: jest.fn(),
|
||||
},
|
||||
getPanel: jest.fn(),
|
||||
doSetGroupActive: jest.fn(),
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
const accessor = new accessorMock() as DockviewComponent;
|
||||
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
|
||||
() => {
|
||||
return {
|
||||
@ -982,23 +831,17 @@ describe('dockviewGroupPanelModel', () => {
|
||||
new groupPanelMock() as DockviewGroupPanel
|
||||
);
|
||||
|
||||
let counter = 0;
|
||||
|
||||
cut.onUnhandledDragOverEvent(() => {
|
||||
counter++;
|
||||
});
|
||||
|
||||
cut.openPanel(new TestPanel('panel1', panelApi));
|
||||
cut.openPanel(new TestPanel('panel2', panelApi));
|
||||
cut.openPanel(new TestPanel('panel1', jest.fn() as any));
|
||||
cut.openPanel(new TestPanel('panel2', jest.fn() as any));
|
||||
|
||||
const element = container
|
||||
.getElementsByClassName('dv-content-container')
|
||||
.item(0) as HTMLElement;
|
||||
.getElementsByClassName('content-container')
|
||||
.item(0)!;
|
||||
|
||||
jest.spyOn(element, 'offsetHeight', 'get').mockImplementation(
|
||||
jest.spyOn(element, 'clientHeight', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
jest.spyOn(element, 'offsetWidth', 'get').mockImplementation(() => 100);
|
||||
jest.spyOn(element, 'clientWidth', 'get').mockImplementation(() => 100);
|
||||
|
||||
LocalSelectionTransfer.getInstance().setData(
|
||||
[new PanelTransfer('anothercomponentid', 'groupviewid', 'panel1')],
|
||||
@ -1008,10 +851,10 @@ describe('dockviewGroupPanelModel', () => {
|
||||
fireEvent.dragEnter(element);
|
||||
fireEvent.dragOver(element);
|
||||
|
||||
expect(counter).toBe(1);
|
||||
expect(accessor.options.showDndOverlay).toBeCalledTimes(1);
|
||||
|
||||
expect(
|
||||
element.getElementsByClassName('dv-drop-target-dropzone').length
|
||||
element.getElementsByClassName('drop-target-dropzone').length
|
||||
).toBe(0);
|
||||
});
|
||||
|
||||
@ -1090,17 +933,17 @@ describe('dockviewGroupPanelModel', () => {
|
||||
container.getElementsByClassName('watermark-test-container').length
|
||||
).toBe(1);
|
||||
|
||||
cut.openPanel(new TestPanel('panel1', panelApi));
|
||||
cut.openPanel(new TestPanel('panel1', jest.fn() as any));
|
||||
|
||||
expect(
|
||||
container.getElementsByClassName('watermark-test-container').length
|
||||
).toBe(0);
|
||||
expect(
|
||||
container.getElementsByClassName('dv-tabs-and-actions-container')
|
||||
container.getElementsByClassName('tabs-and-actions-container')
|
||||
.length
|
||||
).toBe(1);
|
||||
|
||||
cut.openPanel(new TestPanel('panel2', panelApi));
|
||||
cut.openPanel(new TestPanel('panel2', jest.fn() as any));
|
||||
|
||||
expect(
|
||||
container.getElementsByClassName('watermark-test-container').length
|
||||
@ -1118,7 +961,7 @@ describe('dockviewGroupPanelModel', () => {
|
||||
container.getElementsByClassName('watermark-test-container').length
|
||||
).toBe(1);
|
||||
|
||||
cut.openPanel(new TestPanel('panel1', panelApi));
|
||||
cut.openPanel(new TestPanel('panel1', jest.fn() as any));
|
||||
|
||||
expect(
|
||||
container.getElementsByClassName('watermark-test-container').length
|
||||
|
@ -3,37 +3,33 @@ import { DockviewApi } from '../../api/component.api';
|
||||
import { DockviewPanel } from '../../dockview/dockviewPanel';
|
||||
import { IDockviewPanelModel } from '../../dockview/dockviewPanelModel';
|
||||
import { DockviewGroupPanel } from '../../dockview/dockviewGroupPanel';
|
||||
import { fromPartial } from '@total-typescript/shoehorn';
|
||||
|
||||
describe('dockviewPanel', () => {
|
||||
test('update title', () => {
|
||||
const api = fromPartial<DockviewApi>({});
|
||||
const accessor = fromPartial<DockviewComponent>({});
|
||||
const group = fromPartial<DockviewGroupPanel>({
|
||||
api: {
|
||||
onDidVisibilityChange: jest.fn(),
|
||||
onDidLocationChange: jest.fn(),
|
||||
const dockviewApiMock = jest.fn<DockviewApi, []>(() => {
|
||||
return {
|
||||
onDidActiveChange: jest.fn(),
|
||||
},
|
||||
} as any;
|
||||
});
|
||||
const model = fromPartial<IDockviewPanelModel>({
|
||||
update: jest.fn(),
|
||||
init: jest.fn(),
|
||||
dispose: jest.fn(),
|
||||
const accessorMock = jest.fn<DockviewComponent, []>(() => {
|
||||
return {} as any;
|
||||
});
|
||||
const groupMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||
return {} as any;
|
||||
});
|
||||
const panelModelMock = jest.fn<Partial<IDockviewPanelModel>, []>(() => {
|
||||
return {
|
||||
update: jest.fn(),
|
||||
init: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
const cut = new DockviewPanel(
|
||||
'fake-id',
|
||||
'fake-component',
|
||||
undefined,
|
||||
accessor,
|
||||
api,
|
||||
group,
|
||||
model,
|
||||
{
|
||||
renderer: 'onlyWhenVisible',
|
||||
}
|
||||
);
|
||||
const api = new dockviewApiMock();
|
||||
const accessor = new accessorMock();
|
||||
const group = new groupMock();
|
||||
const model = <IDockviewPanelModel>new panelModelMock();
|
||||
|
||||
const cut = new DockviewPanel('fake-id', accessor, api, group, model);
|
||||
|
||||
let latestTitle: string | undefined = undefined;
|
||||
|
||||
@ -55,32 +51,30 @@ describe('dockviewPanel', () => {
|
||||
});
|
||||
|
||||
test('that .setTitle updates the title', () => {
|
||||
const api = fromPartial<DockviewApi>({});
|
||||
const accessor = fromPartial<DockviewComponent>({});
|
||||
const group = fromPartial<DockviewGroupPanel>({
|
||||
api: {
|
||||
onDidVisibilityChange: jest.fn(),
|
||||
onDidLocationChange: jest.fn(),
|
||||
const dockviewApiMock = jest.fn<DockviewApi, []>(() => {
|
||||
return {
|
||||
onDidActiveChange: jest.fn(),
|
||||
},
|
||||
} as any;
|
||||
});
|
||||
const model = fromPartial<IDockviewPanelModel>({
|
||||
update: jest.fn(),
|
||||
init: jest.fn(),
|
||||
const accessorMock = jest.fn<DockviewComponent, []>(() => {
|
||||
return {} as any;
|
||||
});
|
||||
const groupMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||
return {} as any;
|
||||
});
|
||||
const panelModelMock = jest.fn<Partial<IDockviewPanelModel>, []>(() => {
|
||||
return {
|
||||
update: jest.fn(),
|
||||
init: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
const cut = new DockviewPanel(
|
||||
'fake-id',
|
||||
'fake-component',
|
||||
undefined,
|
||||
accessor,
|
||||
api,
|
||||
group,
|
||||
model,
|
||||
{
|
||||
renderer: 'onlyWhenVisible',
|
||||
}
|
||||
);
|
||||
const api = new dockviewApiMock();
|
||||
const accessor = new accessorMock();
|
||||
const group = new groupMock();
|
||||
const model = <IDockviewPanelModel>new panelModelMock();
|
||||
|
||||
const cut = new DockviewPanel('fake-id', accessor, api, group, model);
|
||||
|
||||
cut.init({ title: 'myTitle', params: {} });
|
||||
expect(cut.title).toBe('myTitle');
|
||||
@ -93,39 +87,29 @@ describe('dockviewPanel', () => {
|
||||
});
|
||||
|
||||
test('dispose cleanup', () => {
|
||||
const api = fromPartial<DockviewApi>({});
|
||||
const accessor = fromPartial<DockviewComponent>({});
|
||||
const group = fromPartial<DockviewGroupPanel>({
|
||||
api: {
|
||||
onDidVisibilityChange: jest
|
||||
.fn()
|
||||
.mockReturnValue({ dispose: jest.fn() }),
|
||||
onDidLocationChange: jest
|
||||
.fn()
|
||||
.mockReturnValue({ dispose: jest.fn() }),
|
||||
onDidActiveChange: jest
|
||||
.fn()
|
||||
.mockReturnValue({ dispose: jest.fn() }),
|
||||
},
|
||||
const dockviewApiMock = jest.fn<DockviewApi, []>(() => {
|
||||
return {} as any;
|
||||
});
|
||||
const model = fromPartial<IDockviewPanelModel>({
|
||||
update: jest.fn(),
|
||||
init: jest.fn(),
|
||||
dispose: jest.fn(),
|
||||
const accessorMock = jest.fn<DockviewComponent, []>(() => {
|
||||
return {} as any;
|
||||
});
|
||||
const groupMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||
return {} as any;
|
||||
});
|
||||
const panelModelMock = jest.fn<Partial<IDockviewPanelModel>, []>(() => {
|
||||
return {
|
||||
update: jest.fn(),
|
||||
init: jest.fn(),
|
||||
dispose: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
const cut = new DockviewPanel(
|
||||
'fake-id',
|
||||
'fake-component',
|
||||
undefined,
|
||||
accessor,
|
||||
api,
|
||||
group,
|
||||
model,
|
||||
{
|
||||
renderer: 'onlyWhenVisible',
|
||||
}
|
||||
);
|
||||
const api = new dockviewApiMock();
|
||||
const accessor = new accessorMock();
|
||||
const group = new groupMock();
|
||||
const model = <IDockviewPanelModel>new panelModelMock();
|
||||
|
||||
const cut = new DockviewPanel('fake-id', accessor, api, group, model);
|
||||
|
||||
cut.init({ params: {}, title: 'title' });
|
||||
|
||||
@ -135,33 +119,29 @@ describe('dockviewPanel', () => {
|
||||
});
|
||||
|
||||
test('get params', () => {
|
||||
const api = fromPartial<DockviewApi>({});
|
||||
const accessor = fromPartial<DockviewComponent>({});
|
||||
const group = fromPartial<DockviewGroupPanel>({
|
||||
api: {
|
||||
onDidVisibilityChange: jest.fn(),
|
||||
onDidLocationChange: jest.fn(),
|
||||
onDidActiveChange: jest.fn(),
|
||||
},
|
||||
const dockviewApiMock = jest.fn<DockviewApi, []>(() => {
|
||||
return {} as any;
|
||||
});
|
||||
const model = fromPartial<IDockviewPanelModel>({
|
||||
update: jest.fn(),
|
||||
init: jest.fn(),
|
||||
dispose: jest.fn(),
|
||||
const accessorMock = jest.fn<DockviewComponent, []>(() => {
|
||||
return {} as any;
|
||||
});
|
||||
const groupMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||
return {} as any;
|
||||
});
|
||||
const panelModelMock = jest.fn<Partial<IDockviewPanelModel>, []>(() => {
|
||||
return {
|
||||
update: jest.fn(),
|
||||
init: jest.fn(),
|
||||
dispose: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
const cut = new DockviewPanel(
|
||||
'fake-id',
|
||||
'fake-component',
|
||||
undefined,
|
||||
accessor,
|
||||
api,
|
||||
group,
|
||||
model,
|
||||
{
|
||||
renderer: 'onlyWhenVisible',
|
||||
}
|
||||
);
|
||||
const api = new dockviewApiMock();
|
||||
const accessor = new accessorMock();
|
||||
const group = new groupMock();
|
||||
const model = <IDockviewPanelModel>new panelModelMock();
|
||||
|
||||
const cut = new DockviewPanel('fake-id', accessor, api, group, model);
|
||||
|
||||
expect(cut.params).toEqual(undefined);
|
||||
|
||||
@ -171,72 +151,64 @@ describe('dockviewPanel', () => {
|
||||
});
|
||||
|
||||
test('setSize propagates to underlying group', () => {
|
||||
const api = fromPartial<DockviewApi>({});
|
||||
const accessor = fromPartial<DockviewComponent>({});
|
||||
const group = fromPartial<DockviewGroupPanel>({
|
||||
api: {
|
||||
onDidVisibilityChange: jest.fn(),
|
||||
onDidLocationChange: jest.fn(),
|
||||
onDidActiveChange: jest.fn(),
|
||||
setSize: jest.fn(),
|
||||
},
|
||||
const dockviewApiMock = jest.fn<DockviewApi, []>(() => {
|
||||
return {} as any;
|
||||
});
|
||||
const model = fromPartial<IDockviewPanelModel>({
|
||||
update: jest.fn(),
|
||||
init: jest.fn(),
|
||||
dispose: jest.fn(),
|
||||
const accessorMock = jest.fn<DockviewComponent, []>(() => {
|
||||
return {} as any;
|
||||
});
|
||||
const groupMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||
return {
|
||||
api: {
|
||||
setSize: jest.fn(),
|
||||
},
|
||||
} as any;
|
||||
});
|
||||
const panelModelMock = jest.fn<Partial<IDockviewPanelModel>, []>(() => {
|
||||
return {
|
||||
update: jest.fn(),
|
||||
init: jest.fn(),
|
||||
dispose: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
const cut = new DockviewPanel(
|
||||
'fake-id',
|
||||
'fake-component',
|
||||
undefined,
|
||||
accessor,
|
||||
api,
|
||||
group,
|
||||
model,
|
||||
{
|
||||
renderer: 'onlyWhenVisible',
|
||||
}
|
||||
);
|
||||
const api = new dockviewApiMock();
|
||||
const accessor = new accessorMock();
|
||||
const group = new groupMock();
|
||||
const model = <IDockviewPanelModel>new panelModelMock();
|
||||
|
||||
const cut = new DockviewPanel('fake-id', accessor, api, group, model);
|
||||
|
||||
cut.api.setSize({ height: 123, width: 456 });
|
||||
|
||||
expect(group.api.setSize).toHaveBeenCalledWith({
|
||||
height: 123,
|
||||
width: 456,
|
||||
});
|
||||
expect(group.api.setSize).toHaveBeenCalledTimes(1);
|
||||
expect(group.api.setSize).toBeCalledWith({ height: 123, width: 456 });
|
||||
expect(group.api.setSize).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
test('updateParameter', () => {
|
||||
const api = fromPartial<DockviewApi>({});
|
||||
const accessor = fromPartial<DockviewComponent>({});
|
||||
const group = fromPartial<DockviewGroupPanel>({
|
||||
api: {
|
||||
onDidVisibilityChange: jest.fn(),
|
||||
onDidLocationChange: jest.fn(),
|
||||
onDidActiveChange: jest.fn(),
|
||||
},
|
||||
const dockviewApiMock = jest.fn<DockviewApi, []>(() => {
|
||||
return {} as any;
|
||||
});
|
||||
const model = fromPartial<IDockviewPanelModel>({
|
||||
update: jest.fn(),
|
||||
init: jest.fn(),
|
||||
dispose: jest.fn(),
|
||||
const accessorMock = jest.fn<DockviewComponent, []>(() => {
|
||||
return {} as any;
|
||||
});
|
||||
const groupMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||
return {} as any;
|
||||
});
|
||||
const panelModelMock = jest.fn<Partial<IDockviewPanelModel>, []>(() => {
|
||||
return {
|
||||
update: jest.fn(),
|
||||
init: jest.fn(),
|
||||
dispose: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
const cut = new DockviewPanel(
|
||||
'fake-id',
|
||||
'fake-component',
|
||||
undefined,
|
||||
accessor,
|
||||
api,
|
||||
group,
|
||||
model,
|
||||
{
|
||||
renderer: 'onlyWhenVisible',
|
||||
}
|
||||
);
|
||||
const api = new dockviewApiMock();
|
||||
const accessor = new accessorMock();
|
||||
const group = new groupMock();
|
||||
const model = <IDockviewPanelModel>new panelModelMock();
|
||||
|
||||
const cut = new DockviewPanel('fake-id', accessor, api, group, model);
|
||||
|
||||
cut.init({ params: { a: '1', b: '2' }, title: 'A title' });
|
||||
expect(cut.params).toEqual({ a: '1', b: '2' });
|
||||
@ -244,9 +216,6 @@ describe('dockviewPanel', () => {
|
||||
// update 'a' and add 'c'
|
||||
cut.update({ params: { a: '-1', c: '3' } });
|
||||
expect(cut.params).toEqual({ a: '-1', b: '2', c: '3' });
|
||||
expect(model.update).toHaveBeenCalledWith({
|
||||
params: { a: '-1', b: '2', c: '3' },
|
||||
});
|
||||
|
||||
cut.update({ params: { d: '4', e: '5', f: '6' } });
|
||||
expect(cut.params).toEqual({
|
||||
@ -257,9 +226,6 @@ describe('dockviewPanel', () => {
|
||||
e: '5',
|
||||
f: '6',
|
||||
});
|
||||
expect(model.update).toHaveBeenCalledWith({
|
||||
params: { a: '-1', b: '2', c: '3', d: '4', e: '5', f: '6' },
|
||||
});
|
||||
|
||||
cut.update({
|
||||
params: {
|
||||
@ -280,8 +246,5 @@ describe('dockviewPanel', () => {
|
||||
g: '',
|
||||
h: null,
|
||||
});
|
||||
expect(model.update).toHaveBeenCalledWith({
|
||||
params: { a: '-1', b: '2', c: '3', d: '', e: null, g: '', h: null },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,13 +1,16 @@
|
||||
import { DockviewComponent } from '../../dockview/dockviewComponent';
|
||||
import {
|
||||
DockviewComponent,
|
||||
IDockviewComponent,
|
||||
} from '../../dockview/dockviewComponent';
|
||||
import { DockviewPanelModel } from '../../dockview/dockviewPanelModel';
|
||||
import { IContentRenderer, ITabRenderer } from '../../dockview/types';
|
||||
import { GroupPanelFrameworkComponentFactory } from '../../dockview/options';
|
||||
import { DefaultTab } from '../../dockview/components/tab/defaultTab';
|
||||
import { fromPartial } from '@total-typescript/shoehorn';
|
||||
|
||||
describe('dockviewGroupPanel', () => {
|
||||
let contentMock: jest.Mock<IContentRenderer>;
|
||||
let tabMock: jest.Mock<ITabRenderer>;
|
||||
let accessorMock: DockviewComponent;
|
||||
let accessorMock: jest.Mock<IDockviewComponent>;
|
||||
|
||||
beforeEach(() => {
|
||||
contentMock = jest.fn<IContentRenderer, []>(() => {
|
||||
@ -15,6 +18,8 @@ describe('dockviewGroupPanel', () => {
|
||||
element: document.createElement('div'),
|
||||
dispose: jest.fn(),
|
||||
update: jest.fn(),
|
||||
onGroupChange: jest.fn(),
|
||||
onPanelVisibleChange: jest.fn(),
|
||||
};
|
||||
return partial as IContentRenderer;
|
||||
});
|
||||
@ -24,35 +29,31 @@ describe('dockviewGroupPanel', () => {
|
||||
element: document.createElement('div'),
|
||||
dispose: jest.fn(),
|
||||
update: jest.fn(),
|
||||
onGroupChange: jest.fn(),
|
||||
onPanelVisibleChange: jest.fn(),
|
||||
};
|
||||
return partial as IContentRenderer;
|
||||
});
|
||||
|
||||
accessorMock = fromPartial<DockviewComponent>({
|
||||
options: {
|
||||
createComponent(options) {
|
||||
switch (options.name) {
|
||||
case 'contentComponent':
|
||||
return new contentMock(options.id, options.name);
|
||||
default:
|
||||
throw new Error(`unsupported`);
|
||||
}
|
||||
accessorMock = jest.fn<DockviewComponent, []>(() => {
|
||||
const partial: Partial<DockviewComponent> = {
|
||||
options: {
|
||||
components: {
|
||||
contentComponent: contentMock,
|
||||
},
|
||||
tabComponents: {
|
||||
tabComponent: tabMock,
|
||||
},
|
||||
},
|
||||
createTabComponent(options) {
|
||||
switch (options.name) {
|
||||
case 'tabComponent':
|
||||
return new tabMock(options.id, options.name);
|
||||
default:
|
||||
throw new Error(`unsupported`);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return partial as DockviewComponent;
|
||||
});
|
||||
});
|
||||
|
||||
test('that dispose is called on content and tab renderers when present', () => {
|
||||
const cut = new DockviewPanelModel(
|
||||
accessorMock,
|
||||
<IDockviewComponent>new accessorMock(),
|
||||
'id',
|
||||
'contentComponent',
|
||||
'tabComponent'
|
||||
@ -66,7 +67,7 @@ describe('dockviewGroupPanel', () => {
|
||||
|
||||
test('that update is called on content and tab renderers when present', () => {
|
||||
const cut = new DockviewPanelModel(
|
||||
accessorMock,
|
||||
<IDockviewComponent>new accessorMock(),
|
||||
'id',
|
||||
'contentComponent',
|
||||
'tabComponent'
|
||||
@ -80,30 +81,72 @@ describe('dockviewGroupPanel', () => {
|
||||
expect(cut.tab.update).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('that events are fired', () => {
|
||||
const cut = new DockviewPanelModel(
|
||||
<IDockviewComponent>new accessorMock(),
|
||||
'id',
|
||||
'contentComponent',
|
||||
'tabComponent'
|
||||
);
|
||||
|
||||
const group1 = jest.fn() as any;
|
||||
const group2 = jest.fn() as any;
|
||||
cut.updateParentGroup(group1, false);
|
||||
|
||||
expect(cut.content.onGroupChange).toHaveBeenNthCalledWith(1, group1);
|
||||
expect(cut.tab.onGroupChange).toHaveBeenNthCalledWith(1, group1);
|
||||
expect(cut.content.onPanelVisibleChange).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
false
|
||||
);
|
||||
expect(cut.tab.onPanelVisibleChange).toHaveBeenNthCalledWith(1, false);
|
||||
expect(cut.content.onGroupChange).toHaveBeenCalledTimes(1);
|
||||
expect(cut.tab.onGroupChange).toHaveBeenCalledTimes(1);
|
||||
expect(cut.content.onPanelVisibleChange).toHaveBeenCalledTimes(1);
|
||||
expect(cut.tab.onPanelVisibleChange).toHaveBeenCalledTimes(1);
|
||||
|
||||
cut.updateParentGroup(group1, true);
|
||||
|
||||
expect(cut.content.onPanelVisibleChange).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
true
|
||||
);
|
||||
expect(cut.tab.onPanelVisibleChange).toHaveBeenNthCalledWith(2, true);
|
||||
expect(cut.content.onGroupChange).toHaveBeenCalledTimes(1);
|
||||
expect(cut.tab.onGroupChange).toHaveBeenCalledTimes(1);
|
||||
expect(cut.content.onPanelVisibleChange).toHaveBeenCalledTimes(2);
|
||||
expect(cut.tab.onPanelVisibleChange).toHaveBeenCalledTimes(2);
|
||||
|
||||
cut.updateParentGroup(group2, true);
|
||||
|
||||
expect(cut.content.onGroupChange).toHaveBeenNthCalledWith(2, group2);
|
||||
expect(cut.tab.onGroupChange).toHaveBeenNthCalledWith(2, group2);
|
||||
expect(cut.content.onGroupChange).toHaveBeenCalledTimes(2);
|
||||
expect(cut.tab.onGroupChange).toHaveBeenCalledTimes(2);
|
||||
expect(cut.content.onPanelVisibleChange).toHaveBeenCalledTimes(2);
|
||||
expect(cut.tab.onPanelVisibleChange).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
test('that the default tab is created', () => {
|
||||
accessorMock = fromPartial<DockviewComponent>({
|
||||
options: {
|
||||
createComponent(options) {
|
||||
switch (options.name) {
|
||||
case 'contentComponent':
|
||||
return new contentMock(options.id, options.name);
|
||||
default:
|
||||
throw new Error(`unsupported`);
|
||||
}
|
||||
accessorMock = jest.fn<DockviewComponent, []>(() => {
|
||||
const partial: Partial<DockviewComponent> = {
|
||||
options: {
|
||||
components: {
|
||||
contentComponent: contentMock,
|
||||
},
|
||||
tabComponents: {
|
||||
tabComponent: jest
|
||||
.fn()
|
||||
.mockImplementation(() => tabMock),
|
||||
},
|
||||
},
|
||||
createTabComponent(options) {
|
||||
switch (options.name) {
|
||||
case 'tabComponent':
|
||||
return tabMock;
|
||||
default:
|
||||
throw new Error(`unsupported`);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return partial as DockviewComponent;
|
||||
});
|
||||
|
||||
const cut = new DockviewPanelModel(
|
||||
accessorMock,
|
||||
<IDockviewComponent>new accessorMock(),
|
||||
'id',
|
||||
'contentComponent',
|
||||
'tabComponent'
|
||||
@ -113,30 +156,26 @@ describe('dockviewGroupPanel', () => {
|
||||
});
|
||||
|
||||
test('that the provided default tab is chosen when no implementation is provided', () => {
|
||||
accessorMock = fromPartial<DockviewComponent>({
|
||||
options: {
|
||||
defaultTabComponent: 'tabComponent',
|
||||
createComponent(options) {
|
||||
switch (options.name) {
|
||||
case 'contentComponent':
|
||||
return new contentMock(options.id, options.name);
|
||||
default:
|
||||
throw new Error(`unsupported`);
|
||||
}
|
||||
accessorMock = jest.fn<DockviewComponent, []>(() => {
|
||||
const partial: Partial<DockviewComponent> = {
|
||||
options: {
|
||||
components: {
|
||||
contentComponent: contentMock,
|
||||
},
|
||||
tabComponents: {
|
||||
tabComponent: jest
|
||||
.fn()
|
||||
.mockImplementation(() => tabMock),
|
||||
},
|
||||
defaultTabComponent: 'tabComponent',
|
||||
},
|
||||
createTabComponent(options) {
|
||||
switch (options.name) {
|
||||
case 'tabComponent':
|
||||
return tabMock;
|
||||
default:
|
||||
throw new Error(`unsupported`);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return partial as DockviewComponent;
|
||||
});
|
||||
|
||||
const cut = new DockviewPanelModel(
|
||||
accessorMock,
|
||||
<IDockviewComponent>new accessorMock(),
|
||||
'id',
|
||||
'contentComponent'
|
||||
);
|
||||
@ -144,22 +183,56 @@ describe('dockviewGroupPanel', () => {
|
||||
expect(cut.tab).toEqual(tabMock);
|
||||
});
|
||||
|
||||
test('that is library default tab instance is created when no alternative exists', () => {
|
||||
accessorMock = fromPartial<DockviewComponent>({
|
||||
options: {
|
||||
createComponent(options) {
|
||||
switch (options.name) {
|
||||
case 'contentComponent':
|
||||
return new contentMock(options.id, options.name);
|
||||
default:
|
||||
throw new Error(`unsupported`);
|
||||
}
|
||||
test('that the framework tab is created when provided tab is a framework tab', () => {
|
||||
const tab = jest.fn();
|
||||
const tabFactory = jest.fn().mockImplementation(() => tab);
|
||||
|
||||
accessorMock = jest.fn<DockviewComponent, []>(() => {
|
||||
const partial: Partial<DockviewComponent> = {
|
||||
options: {
|
||||
components: {
|
||||
contentComponent: contentMock,
|
||||
},
|
||||
frameworkTabComponents: {
|
||||
tabComponent: tabMock,
|
||||
},
|
||||
frameworkComponentFactory: (<
|
||||
Partial<GroupPanelFrameworkComponentFactory>
|
||||
>{
|
||||
tab: { createComponent: tabFactory },
|
||||
}) as GroupPanelFrameworkComponentFactory,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return partial as DockviewComponent;
|
||||
});
|
||||
|
||||
const cut = new DockviewPanelModel(
|
||||
accessorMock,
|
||||
<IDockviewComponent>new accessorMock(),
|
||||
'id',
|
||||
'contentComponent',
|
||||
'tabComponent'
|
||||
);
|
||||
|
||||
expect(tabFactory).toHaveBeenCalledWith('id', 'tabComponent', tabMock);
|
||||
expect(cut.tab).toEqual(tab);
|
||||
});
|
||||
|
||||
test('that is library default tab instance is created when no alternative exists', () => {
|
||||
accessorMock = jest.fn<DockviewComponent, []>(() => {
|
||||
const partial: Partial<DockviewComponent> = {
|
||||
options: {
|
||||
components: {
|
||||
contentComponent: contentMock,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return partial as DockviewComponent;
|
||||
});
|
||||
|
||||
const cut = new DockviewPanelModel(
|
||||
<IDockviewComponent>new accessorMock(),
|
||||
'id',
|
||||
'contentComponent'
|
||||
);
|
||||
@ -168,33 +241,61 @@ describe('dockviewGroupPanel', () => {
|
||||
});
|
||||
|
||||
test('that the default content is created', () => {
|
||||
accessorMock = fromPartial<DockviewComponent>({
|
||||
options: {
|
||||
createComponent(options) {
|
||||
switch (options.name) {
|
||||
case 'contentComponent':
|
||||
accessorMock = jest.fn<DockviewComponent, []>(() => {
|
||||
const partial: Partial<DockviewComponent> = {
|
||||
options: {
|
||||
components: {
|
||||
contentComponent: jest.fn().mockImplementation(() => {
|
||||
return contentMock;
|
||||
default:
|
||||
throw new Error(`unsupported`);
|
||||
}
|
||||
}),
|
||||
},
|
||||
},
|
||||
createTabComponent(options) {
|
||||
switch (options.name) {
|
||||
case 'tabComponent':
|
||||
return tabMock;
|
||||
default:
|
||||
throw new Error(`unsupported`);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return partial as DockviewComponent;
|
||||
});
|
||||
|
||||
const cut = new DockviewPanelModel(
|
||||
accessorMock,
|
||||
<IDockviewComponent>new accessorMock(),
|
||||
'id',
|
||||
'contentComponent'
|
||||
);
|
||||
|
||||
expect(cut.content).toEqual(contentMock);
|
||||
});
|
||||
|
||||
test('that the framework content is created', () => {
|
||||
const content = jest.fn();
|
||||
const contentFactory = jest.fn().mockImplementation(() => content);
|
||||
|
||||
accessorMock = jest.fn<DockviewComponent, []>(() => {
|
||||
const partial: Partial<DockviewComponent> = {
|
||||
options: {
|
||||
frameworkComponents: {
|
||||
contentComponent: contentMock,
|
||||
},
|
||||
frameworkComponentFactory: (<
|
||||
Partial<GroupPanelFrameworkComponentFactory>
|
||||
>{
|
||||
content: { createComponent: contentFactory },
|
||||
}) as GroupPanelFrameworkComponentFactory,
|
||||
},
|
||||
};
|
||||
|
||||
return partial as DockviewComponent;
|
||||
});
|
||||
|
||||
const cut = new DockviewPanelModel(
|
||||
<IDockviewComponent>new accessorMock(),
|
||||
'id',
|
||||
'contentComponent'
|
||||
);
|
||||
|
||||
expect(contentFactory).toHaveBeenCalledWith(
|
||||
'id',
|
||||
'contentComponent',
|
||||
contentMock
|
||||
);
|
||||
expect(cut.content).toEqual(content);
|
||||
});
|
||||
});
|
||||
|
@ -1,9 +1,4 @@
|
||||
import {
|
||||
disableIframePointEvents,
|
||||
isInDocument,
|
||||
quasiDefaultPrevented,
|
||||
quasiPreventDefault,
|
||||
} from '../dom';
|
||||
import { quasiDefaultPrevented, quasiPreventDefault } from '../dom';
|
||||
|
||||
describe('dom', () => {
|
||||
test('quasiPreventDefault', () => {
|
||||
@ -23,61 +18,4 @@ describe('dom', () => {
|
||||
(event as any)['dv-quasiPreventDefault'] = true;
|
||||
expect(quasiDefaultPrevented(event)).toBeTruthy();
|
||||
});
|
||||
|
||||
test('isInDocument: DOM element', () => {
|
||||
const el = document.createElement('div');
|
||||
|
||||
expect(isInDocument(el)).toBeFalsy();
|
||||
|
||||
document.body.appendChild(el);
|
||||
expect(isInDocument(el)).toBeTruthy();
|
||||
});
|
||||
|
||||
test('isInDocument: Shadow DOM element', () => {
|
||||
const el = document.createElement('div');
|
||||
document.body.appendChild(el);
|
||||
|
||||
const shadow = el.attachShadow({ mode: 'open' });
|
||||
|
||||
const el2 = document.createElement('div');
|
||||
expect(isInDocument(el2)).toBeFalsy();
|
||||
|
||||
shadow.appendChild(el2);
|
||||
|
||||
expect(isInDocument(el2)).toBeTruthy();
|
||||
});
|
||||
|
||||
test('disableIframePointEvents', () => {
|
||||
const el1 = document.createElement('iframe');
|
||||
const el2 = document.createElement('iframe');
|
||||
const el3 = document.createElement('webview');
|
||||
const el4 = document.createElement('webview');
|
||||
|
||||
document.body.appendChild(el1);
|
||||
document.body.appendChild(el2);
|
||||
document.body.appendChild(el3);
|
||||
document.body.appendChild(el4);
|
||||
|
||||
el1.style.pointerEvents = 'inherit';
|
||||
el3.style.pointerEvents = 'inherit';
|
||||
|
||||
expect(el1.style.pointerEvents).toBe('inherit');
|
||||
expect(el2.style.pointerEvents).toBe('');
|
||||
expect(el3.style.pointerEvents).toBe('inherit');
|
||||
expect(el4.style.pointerEvents).toBe('');
|
||||
|
||||
const f = disableIframePointEvents();
|
||||
|
||||
expect(el1.style.pointerEvents).toBe('none');
|
||||
expect(el2.style.pointerEvents).toBe('none');
|
||||
expect(el3.style.pointerEvents).toBe('none');
|
||||
expect(el4.style.pointerEvents).toBe('none');
|
||||
|
||||
f.release();
|
||||
|
||||
expect(el1.style.pointerEvents).toBe('inherit');
|
||||
expect(el2.style.pointerEvents).toBe('');
|
||||
expect(el3.style.pointerEvents).toBe('inherit');
|
||||
expect(el4.style.pointerEvents).toBe('');
|
||||
});
|
||||
});
|
||||
|
@ -1,8 +1,8 @@
|
||||
import {
|
||||
AsapEvent,
|
||||
Emitter,
|
||||
Event,
|
||||
addDisposableListener,
|
||||
addDisposableWindowListener,
|
||||
} from '../events';
|
||||
|
||||
describe('events', () => {
|
||||
@ -82,41 +82,6 @@ describe('events', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('asapEvent', () => {
|
||||
test('that asapEvents fire once per event-loop-cycle', () => {
|
||||
jest.useFakeTimers();
|
||||
|
||||
const event = new AsapEvent();
|
||||
|
||||
let preFireCount = 0;
|
||||
let postFireCount = 0;
|
||||
|
||||
event.onEvent(() => {
|
||||
preFireCount++;
|
||||
});
|
||||
|
||||
for (let i = 0; i < 100; i++) {
|
||||
event.fire();
|
||||
}
|
||||
|
||||
/**
|
||||
* check that subscribing after the events have fired but before the event-loop cycle completes
|
||||
* results in no event fires.
|
||||
*/
|
||||
event.onEvent((e) => {
|
||||
postFireCount++;
|
||||
});
|
||||
|
||||
expect(preFireCount).toBe(0);
|
||||
expect(postFireCount).toBe(0);
|
||||
|
||||
jest.runAllTimers();
|
||||
|
||||
expect(preFireCount).toBe(1);
|
||||
expect(postFireCount).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('should emit a value when any event fires', () => {
|
||||
const emitter1 = new Emitter<number>();
|
||||
const emitter2 = new Emitter<number>();
|
||||
@ -142,7 +107,7 @@ describe('events', () => {
|
||||
expect(value).toBe(3);
|
||||
});
|
||||
|
||||
it('addDisposableListener with capture options', () => {
|
||||
it('addDisposableWindowListener with capture options', () => {
|
||||
const element = {
|
||||
addEventListener: jest.fn(),
|
||||
removeEventListener: jest.fn(),
|
||||
@ -150,16 +115,16 @@ describe('events', () => {
|
||||
|
||||
const handler = jest.fn();
|
||||
|
||||
const disposable = addDisposableListener(
|
||||
const disposable = addDisposableWindowListener(
|
||||
element as any,
|
||||
'pointerdown',
|
||||
'mousedown',
|
||||
handler,
|
||||
true
|
||||
);
|
||||
|
||||
expect(element.addEventListener).toBeCalledTimes(1);
|
||||
expect(element.addEventListener).toHaveBeenCalledWith(
|
||||
'pointerdown',
|
||||
'mousedown',
|
||||
handler,
|
||||
true
|
||||
);
|
||||
@ -170,13 +135,13 @@ describe('events', () => {
|
||||
expect(element.addEventListener).toBeCalledTimes(1);
|
||||
expect(element.removeEventListener).toBeCalledTimes(1);
|
||||
expect(element.removeEventListener).toBeCalledWith(
|
||||
'pointerdown',
|
||||
'mousedown',
|
||||
handler,
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
it('addDisposableListener without capture options', () => {
|
||||
it('addDisposableWindowListener without capture options', () => {
|
||||
const element = {
|
||||
addEventListener: jest.fn(),
|
||||
removeEventListener: jest.fn(),
|
||||
@ -184,15 +149,15 @@ describe('events', () => {
|
||||
|
||||
const handler = jest.fn();
|
||||
|
||||
const disposable = addDisposableListener(
|
||||
const disposable = addDisposableWindowListener(
|
||||
element as any,
|
||||
'pointerdown',
|
||||
'mousedown',
|
||||
handler
|
||||
);
|
||||
|
||||
expect(element.addEventListener).toBeCalledTimes(1);
|
||||
expect(element.addEventListener).toHaveBeenCalledWith(
|
||||
'pointerdown',
|
||||
'mousedown',
|
||||
handler,
|
||||
undefined
|
||||
);
|
||||
@ -203,7 +168,7 @@ describe('events', () => {
|
||||
expect(element.addEventListener).toBeCalledTimes(1);
|
||||
expect(element.removeEventListener).toBeCalledTimes(1);
|
||||
expect(element.removeEventListener).toBeCalledWith(
|
||||
'pointerdown',
|
||||
'mousedown',
|
||||
handler,
|
||||
undefined
|
||||
);
|
||||
@ -219,14 +184,14 @@ describe('events', () => {
|
||||
|
||||
const disposable = addDisposableListener(
|
||||
element as any,
|
||||
'pointerdown',
|
||||
'mousedown',
|
||||
handler,
|
||||
true
|
||||
);
|
||||
|
||||
expect(element.addEventListener).toBeCalledTimes(1);
|
||||
expect(element.addEventListener).toHaveBeenCalledWith(
|
||||
'pointerdown',
|
||||
'mousedown',
|
||||
handler,
|
||||
true
|
||||
);
|
||||
@ -237,7 +202,7 @@ describe('events', () => {
|
||||
expect(element.addEventListener).toBeCalledTimes(1);
|
||||
expect(element.removeEventListener).toBeCalledTimes(1);
|
||||
expect(element.removeEventListener).toBeCalledWith(
|
||||
'pointerdown',
|
||||
'mousedown',
|
||||
handler,
|
||||
true
|
||||
);
|
||||
@ -253,13 +218,13 @@ describe('events', () => {
|
||||
|
||||
const disposable = addDisposableListener(
|
||||
element as any,
|
||||
'pointerdown',
|
||||
'mousedown',
|
||||
handler
|
||||
);
|
||||
|
||||
expect(element.addEventListener).toBeCalledTimes(1);
|
||||
expect(element.addEventListener).toHaveBeenCalledWith(
|
||||
'pointerdown',
|
||||
'mousedown',
|
||||
handler,
|
||||
undefined
|
||||
);
|
||||
@ -270,7 +235,7 @@ describe('events', () => {
|
||||
expect(element.addEventListener).toBeCalledTimes(1);
|
||||
expect(element.removeEventListener).toBeCalledTimes(1);
|
||||
expect(element.removeEventListener).toBeCalledWith(
|
||||
'pointerdown',
|
||||
'mousedown',
|
||||
handler,
|
||||
undefined
|
||||
);
|
||||
|
@ -17,9 +17,13 @@ class TestPanel implements IGridPanelView {
|
||||
_onDidChange = new Emitter<IViewSize | undefined>();
|
||||
readonly onDidChange = this._onDidChange.event;
|
||||
|
||||
isVisible: boolean = true;
|
||||
isActive: boolean = true;
|
||||
params: Parameters = {};
|
||||
get isActive(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
get params(): Record<string, any> {
|
||||
return {};
|
||||
}
|
||||
|
||||
constructor(
|
||||
public readonly id: string,
|
||||
@ -66,10 +70,8 @@ class TestPanel implements IGridPanelView {
|
||||
}
|
||||
|
||||
class ClassUnderTest extends BaseGrid<TestPanel> {
|
||||
readonly gridview = this.gridview;
|
||||
|
||||
constructor(parentElement: HTMLElement, options: BaseGridOptions) {
|
||||
super(parentElement, options);
|
||||
constructor(options: BaseGridOptions) {
|
||||
super(options);
|
||||
}
|
||||
|
||||
doRemoveGroup(
|
||||
@ -105,62 +107,24 @@ class ClassUnderTest extends BaseGrid<TestPanel> {
|
||||
}
|
||||
|
||||
describe('baseComponentGridview', () => {
|
||||
test('that the container is not removed when grid is disposed', () => {
|
||||
const root = document.createElement('div');
|
||||
const container = document.createElement('div');
|
||||
root.appendChild(container);
|
||||
|
||||
const cut = new ClassUnderTest(container, {
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
proportionalLayout: true,
|
||||
});
|
||||
|
||||
cut.dispose();
|
||||
|
||||
expect(container.parentElement).toBe(root);
|
||||
});
|
||||
|
||||
test('that .layout(...) force flag works', () => {
|
||||
const cut = new ClassUnderTest(document.createElement('div'), {
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
proportionalLayout: true,
|
||||
});
|
||||
|
||||
const spy = jest.spyOn(cut.gridview, 'layout');
|
||||
|
||||
cut.layout(100, 100);
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
|
||||
cut.layout(100, 100, false);
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
|
||||
cut.layout(100, 100, true);
|
||||
expect(spy).toHaveBeenCalledTimes(2);
|
||||
|
||||
cut.layout(150, 150, false);
|
||||
expect(spy).toHaveBeenCalledTimes(3);
|
||||
|
||||
cut.layout(150, 150, true);
|
||||
expect(spy).toHaveBeenCalledTimes(4);
|
||||
});
|
||||
|
||||
test('can add group', () => {
|
||||
const cut = new ClassUnderTest(document.createElement('div'), {
|
||||
const cut = new ClassUnderTest({
|
||||
parentElement: document.createElement('div'),
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
proportionalLayout: true,
|
||||
});
|
||||
|
||||
const events: { type: string; panel: TestPanel | undefined }[] = [];
|
||||
const events: (TestPanel | undefined)[] = [];
|
||||
|
||||
const disposable = new CompositeDisposable(
|
||||
cut.onDidAdd((event) => {
|
||||
events.push({ type: 'add', panel: event });
|
||||
cut.onDidAddGroup((event) => {
|
||||
events.push(event);
|
||||
}),
|
||||
cut.onDidRemove((event) => {
|
||||
events.push({ type: 'remove', panel: event });
|
||||
cut.onDidRemoveGroup((event) => {
|
||||
events.push(event);
|
||||
}),
|
||||
cut.onDidActiveChange((event) => {
|
||||
events.push({ type: 'active', panel: event });
|
||||
cut.onDidActiveGroupChange((event) => {
|
||||
events.push(event);
|
||||
})
|
||||
);
|
||||
|
||||
@ -177,8 +141,9 @@ describe('baseComponentGridview', () => {
|
||||
|
||||
cut.doAddGroup(panel1);
|
||||
|
||||
expect(events.length).toBe(1);
|
||||
expect(events[0]).toEqual({ type: 'add', panel: panel1 });
|
||||
expect(events.length).toBe(2);
|
||||
expect(events[0]).toBe(panel1);
|
||||
expect(events[1]).toBe(panel1);
|
||||
|
||||
const panel2 = new TestPanel(
|
||||
'id',
|
||||
@ -193,12 +158,12 @@ describe('baseComponentGridview', () => {
|
||||
|
||||
cut.doAddGroup(panel2);
|
||||
|
||||
expect(events.length).toBe(2);
|
||||
expect(events[1]).toEqual({ type: 'add', panel: panel2 });
|
||||
expect(events.length).toBe(4);
|
||||
expect(events[2]).toBe(panel2);
|
||||
|
||||
cut.doRemoveGroup(panel1);
|
||||
expect(events.length).toBe(3);
|
||||
expect(events[2]).toEqual({ type: 'remove', panel: panel1 });
|
||||
expect(events.length).toBe(5);
|
||||
expect(events[4]).toBe(panel1);
|
||||
|
||||
disposable.dispose();
|
||||
cut.dispose();
|
||||
|
@ -4,8 +4,6 @@ import {
|
||||
Gridview,
|
||||
IGridView,
|
||||
IViewSize,
|
||||
SerializedGridview,
|
||||
getGridLocation,
|
||||
orthogonal,
|
||||
} from '../../gridview/gridview';
|
||||
import { Orientation, Sizing } from '../../splitview/splitview';
|
||||
@ -19,24 +17,16 @@ class MockGridview implements IGridView {
|
||||
IViewSize | undefined
|
||||
>().event;
|
||||
element: HTMLElement = document.createElement('div');
|
||||
isVisible: boolean = true;
|
||||
width: number = 0;
|
||||
height: number = 0;
|
||||
|
||||
constructor(private id?: string) {
|
||||
constructor() {
|
||||
this.element.className = 'mock-grid-view';
|
||||
this.element.id = `${id ?? ''}`;
|
||||
}
|
||||
|
||||
layout(width: number, height: number): void {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
//
|
||||
}
|
||||
|
||||
toJSON(): object {
|
||||
if (this.id) {
|
||||
return { id: this.id };
|
||||
}
|
||||
return {};
|
||||
}
|
||||
}
|
||||
@ -733,475 +723,4 @@ describe('gridview', () => {
|
||||
width: 1000,
|
||||
});
|
||||
});
|
||||
|
||||
test('re-structuring deep gridivew where a branchnode becomes of length one and is coverted to a leaf node', () => {
|
||||
const gridview = new Gridview(
|
||||
false,
|
||||
{ separatorBorder: '' },
|
||||
Orientation.HORIZONTAL
|
||||
);
|
||||
gridview.layout(1000, 1000);
|
||||
|
||||
const view1 = new MockGridview('1');
|
||||
const view2 = new MockGridview('2');
|
||||
const view3 = new MockGridview('3');
|
||||
const view4 = new MockGridview('4');
|
||||
const view5 = new MockGridview('5');
|
||||
const view6 = new MockGridview('6');
|
||||
|
||||
gridview.addView(view1, Sizing.Distribute, [0]);
|
||||
|
||||
gridview.addView(view2, Sizing.Distribute, [1]);
|
||||
gridview.addView(view3, Sizing.Distribute, [1, 0]);
|
||||
gridview.addView(view4, Sizing.Distribute, [1, 1, 0]);
|
||||
gridview.addView(view5, Sizing.Distribute, [1, 1, 0, 0]);
|
||||
gridview.addView(view6, Sizing.Distribute, [1, 1, 0, 0, 0]);
|
||||
|
||||
let el = gridview.element.querySelectorAll('.mock-grid-view');
|
||||
expect(el.length).toBe(6);
|
||||
|
||||
gridview.remove(view2);
|
||||
|
||||
el = gridview.element.querySelectorAll('.mock-grid-view');
|
||||
expect(el.length).toBe(5);
|
||||
});
|
||||
|
||||
test('gridview nested proportional layouts', () => {
|
||||
const gridview = new Gridview(
|
||||
true,
|
||||
{ separatorBorder: '' },
|
||||
Orientation.HORIZONTAL
|
||||
);
|
||||
gridview.layout(1000, 1000);
|
||||
|
||||
const view1 = new MockGridview('1');
|
||||
const view2 = new MockGridview('2');
|
||||
const view3 = new MockGridview('3');
|
||||
const view4 = new MockGridview('4');
|
||||
const view5 = new MockGridview('5');
|
||||
const view6 = new MockGridview('6');
|
||||
|
||||
gridview.addView(view1, Sizing.Distribute, [0]);
|
||||
gridview.addView(view2, Sizing.Distribute, [1]);
|
||||
gridview.addView(view3, Sizing.Distribute, [1, 1]);
|
||||
gridview.addView(view4, Sizing.Distribute, [1, 1, 0]);
|
||||
gridview.addView(view5, Sizing.Distribute, [1, 1, 0, 0]);
|
||||
gridview.addView(view6, Sizing.Distribute, [1, 1, 0, 0, 0]);
|
||||
|
||||
const views = [view1, view2, view3, view4, view5, view6];
|
||||
|
||||
const dimensions = [
|
||||
{ width: 500, height: 1000 },
|
||||
{ width: 500, height: 500 },
|
||||
{ width: 250, height: 500 },
|
||||
{ width: 250, height: 250 },
|
||||
{ width: 125, height: 250 },
|
||||
{ width: 125, height: 250 },
|
||||
];
|
||||
|
||||
expect(
|
||||
views.map((view) => ({
|
||||
width: view.width,
|
||||
height: view.height,
|
||||
}))
|
||||
).toEqual(dimensions);
|
||||
|
||||
gridview.layout(2000, 1500);
|
||||
|
||||
expect(
|
||||
views.map((view) => ({
|
||||
width: view.width,
|
||||
height: view.height,
|
||||
}))
|
||||
).toEqual(
|
||||
dimensions.map(({ width, height }) => ({
|
||||
width: width * 2,
|
||||
height: height * 1.5,
|
||||
}))
|
||||
);
|
||||
|
||||
gridview.layout(200, 2000);
|
||||
|
||||
expect(
|
||||
views.map((view) => ({
|
||||
width: view.width,
|
||||
height: view.height,
|
||||
}))
|
||||
).toEqual(
|
||||
dimensions.map(({ width, height }) => ({
|
||||
width: width * 0.2,
|
||||
height: height * 2,
|
||||
}))
|
||||
);
|
||||
});
|
||||
|
||||
test('that maximizeView retains original dimensions when restored', () => {
|
||||
const gridview = new Gridview(
|
||||
true,
|
||||
{ separatorBorder: '' },
|
||||
Orientation.HORIZONTAL
|
||||
);
|
||||
gridview.layout(1000, 1000);
|
||||
|
||||
let counter = 0;
|
||||
const subscription = gridview.onDidMaximizedNodeChange(() => {
|
||||
counter++;
|
||||
});
|
||||
|
||||
const view1 = new MockGridview('1');
|
||||
const view2 = new MockGridview('2');
|
||||
const view3 = new MockGridview('3');
|
||||
const view4 = new MockGridview('4');
|
||||
const view5 = new MockGridview('5');
|
||||
const view6 = new MockGridview('6');
|
||||
|
||||
gridview.addView(view1, Sizing.Distribute, [0]);
|
||||
gridview.addView(view2, Sizing.Distribute, [1]);
|
||||
gridview.addView(view3, Sizing.Distribute, [1, 1]);
|
||||
gridview.addView(view4, Sizing.Distribute, [1, 1, 0]);
|
||||
gridview.addView(view5, Sizing.Distribute, [1, 1, 0, 0]);
|
||||
gridview.addView(view6, Sizing.Distribute, [1, 1, 0, 0, 0]);
|
||||
|
||||
/**
|
||||
* _____________________________________________
|
||||
* | | |
|
||||
* | | 2 |
|
||||
* | | |
|
||||
* | 1 |_______________________|
|
||||
* | | | 4 |
|
||||
* | | 3 |_____________|
|
||||
* | | | 5 | 6 |
|
||||
* |_____________________|_________|______|______|
|
||||
*/
|
||||
|
||||
const views = [view1, view2, view3, view4, view5, view6];
|
||||
|
||||
const dimensions = [
|
||||
{ width: 500, height: 1000 },
|
||||
{ width: 500, height: 500 },
|
||||
{ width: 250, height: 500 },
|
||||
{ width: 250, height: 250 },
|
||||
{ width: 125, height: 250 },
|
||||
{ width: 125, height: 250 },
|
||||
];
|
||||
|
||||
function assertLayout() {
|
||||
expect(
|
||||
views.map((view) => ({
|
||||
width: view.width,
|
||||
height: view.height,
|
||||
}))
|
||||
).toEqual(dimensions);
|
||||
}
|
||||
|
||||
// base case assertions
|
||||
assertLayout();
|
||||
expect(gridview.hasMaximizedView()).toBeFalsy();
|
||||
expect(counter).toBe(0);
|
||||
|
||||
/**
|
||||
* maximize each view individually and then return to the standard view
|
||||
* checking on each iteration that the original layout dimensions
|
||||
* are restored
|
||||
*/
|
||||
for (let i = 0; i < views.length; i++) {
|
||||
const view = views[i];
|
||||
|
||||
gridview.maximizeView(view);
|
||||
expect(counter).toBe(i * 2 + 1);
|
||||
expect(gridview.hasMaximizedView()).toBeTruthy();
|
||||
gridview.exitMaximizedView();
|
||||
expect(counter).toBe(i * 2 + 2);
|
||||
assertLayout();
|
||||
}
|
||||
|
||||
subscription.dispose();
|
||||
});
|
||||
|
||||
test('that maximizedView is exited when a views visibility is changed', () => {
|
||||
const gridview = new Gridview(
|
||||
true,
|
||||
{ separatorBorder: '' },
|
||||
Orientation.HORIZONTAL
|
||||
);
|
||||
gridview.layout(1000, 1000);
|
||||
|
||||
const view1 = new MockGridview('1');
|
||||
const view2 = new MockGridview('2');
|
||||
const view3 = new MockGridview('3');
|
||||
|
||||
gridview.addView(view1, Sizing.Distribute, [0]);
|
||||
gridview.addView(view2, Sizing.Distribute, [1]);
|
||||
gridview.addView(view3, Sizing.Distribute, [1, 1]);
|
||||
|
||||
expect(gridview.hasMaximizedView()).toBeFalsy();
|
||||
gridview.maximizeView(view2);
|
||||
expect(gridview.hasMaximizedView()).toBeTruthy();
|
||||
|
||||
gridview.setViewVisible([0], true);
|
||||
expect(gridview.hasMaximizedView()).toBeFalsy();
|
||||
});
|
||||
|
||||
test('that maximizedView is exited when a view is moved', () => {
|
||||
const gridview = new Gridview(
|
||||
true,
|
||||
{ separatorBorder: '' },
|
||||
Orientation.HORIZONTAL
|
||||
);
|
||||
gridview.layout(1000, 1000);
|
||||
|
||||
const view1 = new MockGridview('1');
|
||||
const view2 = new MockGridview('2');
|
||||
const view3 = new MockGridview('3');
|
||||
const view4 = new MockGridview('4');
|
||||
|
||||
gridview.addView(view1, Sizing.Distribute, [0]);
|
||||
gridview.addView(view2, Sizing.Distribute, [1]);
|
||||
gridview.addView(view3, Sizing.Distribute, [1, 1]);
|
||||
gridview.addView(view4, Sizing.Distribute, [1, 1, 0]);
|
||||
|
||||
expect(gridview.hasMaximizedView()).toBeFalsy();
|
||||
gridview.maximizeView(view2);
|
||||
expect(gridview.hasMaximizedView()).toBeTruthy();
|
||||
|
||||
gridview.moveView([1, 1], 0, 1);
|
||||
expect(gridview.hasMaximizedView()).toBeFalsy();
|
||||
});
|
||||
|
||||
test('that maximizedView is exited when a view is added', () => {
|
||||
const gridview = new Gridview(
|
||||
true,
|
||||
{ separatorBorder: '' },
|
||||
Orientation.HORIZONTAL
|
||||
);
|
||||
gridview.layout(1000, 1000);
|
||||
|
||||
const view1 = new MockGridview('1');
|
||||
const view2 = new MockGridview('2');
|
||||
const view3 = new MockGridview('3');
|
||||
const view4 = new MockGridview('4');
|
||||
|
||||
gridview.addView(view1, Sizing.Distribute, [0]);
|
||||
gridview.addView(view2, Sizing.Distribute, [1]);
|
||||
gridview.addView(view3, Sizing.Distribute, [1, 1]);
|
||||
|
||||
expect(gridview.hasMaximizedView()).toBeFalsy();
|
||||
gridview.maximizeView(view2);
|
||||
expect(gridview.hasMaximizedView()).toBeTruthy();
|
||||
|
||||
gridview.addView(view4, Sizing.Distribute, [1, 1, 0]);
|
||||
expect(gridview.hasMaximizedView()).toBeFalsy();
|
||||
});
|
||||
|
||||
test('that maximizedView is exited when a view is removed', () => {
|
||||
const gridview = new Gridview(
|
||||
true,
|
||||
{ separatorBorder: '' },
|
||||
Orientation.HORIZONTAL
|
||||
);
|
||||
gridview.layout(1000, 1000);
|
||||
|
||||
const view1 = new MockGridview('1');
|
||||
const view2 = new MockGridview('2');
|
||||
const view3 = new MockGridview('3');
|
||||
|
||||
gridview.addView(view1, Sizing.Distribute, [0]);
|
||||
gridview.addView(view2, Sizing.Distribute, [1]);
|
||||
gridview.addView(view3, Sizing.Distribute, [1, 1]);
|
||||
|
||||
expect(gridview.hasMaximizedView()).toBeFalsy();
|
||||
gridview.maximizeView(view2);
|
||||
expect(gridview.hasMaximizedView()).toBeTruthy();
|
||||
|
||||
gridview.removeView([1, 1]);
|
||||
expect(gridview.hasMaximizedView()).toBeFalsy();
|
||||
});
|
||||
|
||||
test('that maximizedView is cleared when layout is cleared', () => {
|
||||
const gridview = new Gridview(
|
||||
true,
|
||||
{ separatorBorder: '' },
|
||||
Orientation.HORIZONTAL
|
||||
);
|
||||
gridview.layout(1000, 1000);
|
||||
|
||||
const view1 = new MockGridview('1');
|
||||
const view2 = new MockGridview('2');
|
||||
const view3 = new MockGridview('3');
|
||||
|
||||
gridview.addView(view1, Sizing.Distribute, [0]);
|
||||
gridview.addView(view2, Sizing.Distribute, [1]);
|
||||
gridview.addView(view3, Sizing.Distribute, [1, 1]);
|
||||
|
||||
expect(gridview.hasMaximizedView()).toBeFalsy();
|
||||
gridview.maximizeView(view2);
|
||||
expect(gridview.hasMaximizedView()).toBeTruthy();
|
||||
|
||||
gridview.clear();
|
||||
|
||||
expect(gridview.hasMaximizedView()).toBeFalsy();
|
||||
});
|
||||
|
||||
test('that maximizedView is cleared when layout is disposed', () => {
|
||||
const gridview = new Gridview(
|
||||
true,
|
||||
{ separatorBorder: '' },
|
||||
Orientation.HORIZONTAL
|
||||
);
|
||||
gridview.layout(1000, 1000);
|
||||
|
||||
const view1 = new MockGridview('1');
|
||||
const view2 = new MockGridview('2');
|
||||
const view3 = new MockGridview('3');
|
||||
|
||||
gridview.addView(view1, Sizing.Distribute, [0]);
|
||||
gridview.addView(view2, Sizing.Distribute, [1]);
|
||||
gridview.addView(view3, Sizing.Distribute, [1, 1]);
|
||||
|
||||
expect(gridview.hasMaximizedView()).toBeFalsy();
|
||||
gridview.maximizeView(view2);
|
||||
expect(gridview.hasMaximizedView()).toBeTruthy();
|
||||
|
||||
gridview.dispose();
|
||||
|
||||
expect(gridview.hasMaximizedView()).toBeFalsy();
|
||||
});
|
||||
|
||||
test('that maximizedView is cleared when layout is reset', () => {
|
||||
const gridview = new Gridview(
|
||||
true,
|
||||
{ separatorBorder: '' },
|
||||
Orientation.HORIZONTAL
|
||||
);
|
||||
gridview.layout(1000, 1000);
|
||||
|
||||
const view1 = new MockGridview('1');
|
||||
const view2 = new MockGridview('2');
|
||||
const view3 = new MockGridview('3');
|
||||
|
||||
gridview.addView(view1, Sizing.Distribute, [0]);
|
||||
gridview.addView(view2, Sizing.Distribute, [1]);
|
||||
gridview.addView(view3, Sizing.Distribute, [1, 1]);
|
||||
|
||||
expect(gridview.hasMaximizedView()).toBeFalsy();
|
||||
gridview.maximizeView(view2);
|
||||
expect(gridview.hasMaximizedView()).toBeTruthy();
|
||||
|
||||
gridview.deserialize(
|
||||
{
|
||||
height: 1000,
|
||||
width: 1000,
|
||||
root: {
|
||||
type: 'leaf',
|
||||
data: [],
|
||||
},
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
},
|
||||
{
|
||||
fromJSON: (data) => {
|
||||
return new MockGridview('');
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
expect(gridview.hasMaximizedView()).toBeFalsy();
|
||||
});
|
||||
|
||||
test('visibility check', () => {
|
||||
const gridview = new Gridview(
|
||||
true,
|
||||
{ separatorBorder: '' },
|
||||
Orientation.HORIZONTAL
|
||||
);
|
||||
gridview.layout(1000, 1000);
|
||||
|
||||
const view1 = new MockGridview('1');
|
||||
const view2 = new MockGridview('2');
|
||||
const view3 = new MockGridview('3');
|
||||
const view4 = new MockGridview('4');
|
||||
const view5 = new MockGridview('5');
|
||||
const view6 = new MockGridview('6');
|
||||
|
||||
gridview.addView(view1, Sizing.Distribute, [0]);
|
||||
gridview.addView(view2, Sizing.Distribute, [1]);
|
||||
gridview.addView(view3, Sizing.Distribute, [1, 1]);
|
||||
gridview.addView(view4, Sizing.Distribute, [1, 1, 0]);
|
||||
gridview.addView(view5, Sizing.Distribute, [1, 1, 0, 0]);
|
||||
gridview.addView(view6, Sizing.Distribute, [1, 1, 0, 0, 0]);
|
||||
|
||||
/**
|
||||
* _____________________________________________
|
||||
* | | |
|
||||
* | | 2 |
|
||||
* | | |
|
||||
* | 1 |_______________________|
|
||||
* | | | 4 |
|
||||
* | | 3 |_____________|
|
||||
* | | | 5 | 6 |
|
||||
* |_____________________|_________|______|______|
|
||||
*/
|
||||
|
||||
function assertVisibility(visibility: boolean[]) {
|
||||
expect(gridview.isViewVisible(getGridLocation(view1.element))).toBe(
|
||||
visibility[0]
|
||||
);
|
||||
expect(gridview.isViewVisible(getGridLocation(view2.element))).toBe(
|
||||
visibility[1]
|
||||
);
|
||||
expect(gridview.isViewVisible(getGridLocation(view3.element))).toBe(
|
||||
visibility[2]
|
||||
);
|
||||
expect(gridview.isViewVisible(getGridLocation(view4.element))).toBe(
|
||||
visibility[3]
|
||||
);
|
||||
expect(gridview.isViewVisible(getGridLocation(view5.element))).toBe(
|
||||
visibility[4]
|
||||
);
|
||||
expect(gridview.isViewVisible(getGridLocation(view6.element))).toBe(
|
||||
visibility[5]
|
||||
);
|
||||
}
|
||||
|
||||
// hide each view one by one
|
||||
|
||||
assertVisibility([true, true, true, true, true, true]);
|
||||
|
||||
gridview.setViewVisible(getGridLocation(view5.element), false);
|
||||
assertVisibility([true, true, true, true, false, true]);
|
||||
|
||||
gridview.setViewVisible(getGridLocation(view4.element), false);
|
||||
assertVisibility([true, true, true, false, false, true]);
|
||||
|
||||
gridview.setViewVisible(getGridLocation(view1.element), false);
|
||||
assertVisibility([false, true, true, false, false, true]);
|
||||
|
||||
gridview.setViewVisible(getGridLocation(view2.element), false);
|
||||
assertVisibility([false, false, true, false, false, true]);
|
||||
|
||||
gridview.setViewVisible(getGridLocation(view3.element), false);
|
||||
assertVisibility([false, false, false, false, false, true]);
|
||||
|
||||
gridview.setViewVisible(getGridLocation(view6.element), false);
|
||||
assertVisibility([false, false, false, false, false, false]);
|
||||
|
||||
// un-hide each view one by one
|
||||
|
||||
gridview.setViewVisible(getGridLocation(view1.element), true);
|
||||
assertVisibility([true, false, false, false, false, false]);
|
||||
|
||||
gridview.setViewVisible(getGridLocation(view5.element), true);
|
||||
assertVisibility([true, false, false, false, true, false]);
|
||||
|
||||
gridview.setViewVisible(getGridLocation(view6.element), true);
|
||||
assertVisibility([true, false, false, false, true, true]);
|
||||
|
||||
gridview.setViewVisible(getGridLocation(view2.element), true);
|
||||
assertVisibility([true, true, false, false, true, true]);
|
||||
|
||||
gridview.setViewVisible(getGridLocation(view3.element), true);
|
||||
assertVisibility([true, true, true, false, true, true]);
|
||||
|
||||
gridview.setViewVisible(getGridLocation(view4.element), true);
|
||||
assertVisibility([true, true, true, true, true, true]);
|
||||
});
|
||||
});
|
||||
|
@ -32,40 +32,12 @@ describe('gridview', () => {
|
||||
container = document.createElement('div');
|
||||
});
|
||||
|
||||
test('update className', () => {
|
||||
const gridview = new GridviewComponent(container, {
|
||||
proportionalLayout: false,
|
||||
orientation: Orientation.VERTICAL,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
className: 'test-a test-b',
|
||||
});
|
||||
|
||||
expect(gridview.element.className).toBe('test-a test-b');
|
||||
|
||||
gridview.updateOptions({ className: 'test-b test-c' });
|
||||
|
||||
expect(gridview.element.className).toBe('test-b test-c');
|
||||
});
|
||||
|
||||
test('added views are visible by default', () => {
|
||||
const gridview = new GridviewComponent(container, {
|
||||
const gridview = new GridviewComponent({
|
||||
parentElement: container,
|
||||
proportionalLayout: false,
|
||||
orientation: Orientation.VERTICAL,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
components: { default: TestGridview },
|
||||
});
|
||||
|
||||
gridview.layout(800, 400);
|
||||
@ -81,17 +53,11 @@ describe('gridview', () => {
|
||||
});
|
||||
|
||||
test('remove panel', () => {
|
||||
const gridview = new GridviewComponent(container, {
|
||||
const gridview = new GridviewComponent({
|
||||
parentElement: container,
|
||||
proportionalLayout: false,
|
||||
orientation: Orientation.VERTICAL,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
components: { default: TestGridview },
|
||||
});
|
||||
|
||||
gridview.layout(800, 400);
|
||||
@ -118,17 +84,11 @@ describe('gridview', () => {
|
||||
});
|
||||
|
||||
test('active panel', () => {
|
||||
const gridview = new GridviewComponent(container, {
|
||||
const gridview = new GridviewComponent({
|
||||
parentElement: container,
|
||||
proportionalLayout: false,
|
||||
orientation: Orientation.VERTICAL,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
components: { default: TestGridview },
|
||||
});
|
||||
|
||||
gridview.layout(800, 400);
|
||||
@ -185,21 +145,13 @@ describe('gridview', () => {
|
||||
});
|
||||
|
||||
test('deserialize and serialize a layout', () => {
|
||||
const gridview = new GridviewComponent(container, {
|
||||
const gridview = new GridviewComponent({
|
||||
parentElement: container,
|
||||
proportionalLayout: false,
|
||||
orientation: Orientation.VERTICAL,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
components: { default: TestGridview },
|
||||
});
|
||||
|
||||
expect(container.querySelectorAll('.dv-grid-view').length).toBe(1);
|
||||
|
||||
gridview.layout(800, 400);
|
||||
gridview.fromJSON({
|
||||
grid: {
|
||||
@ -244,9 +196,6 @@ describe('gridview', () => {
|
||||
},
|
||||
activePanel: 'panel_1',
|
||||
});
|
||||
|
||||
expect(container.querySelectorAll('.dv-grid-view').length).toBe(1);
|
||||
|
||||
gridview.layout(800, 400, true);
|
||||
|
||||
const panel1 = gridview.getPanel('panel_1')!;
|
||||
@ -319,22 +268,16 @@ describe('gridview', () => {
|
||||
],
|
||||
},
|
||||
},
|
||||
activePanel: 'panel_2',
|
||||
activePanel: 'panel_1',
|
||||
});
|
||||
});
|
||||
|
||||
test('toJSON shouldnt fire any layout events', () => {
|
||||
const gridview = new GridviewComponent(container, {
|
||||
const gridview = new GridviewComponent({
|
||||
parentElement: container,
|
||||
proportionalLayout: false,
|
||||
orientation: Orientation.VERTICAL,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
components: { default: TestGridview },
|
||||
});
|
||||
|
||||
gridview.layout(1000, 1000);
|
||||
@ -367,17 +310,11 @@ describe('gridview', () => {
|
||||
});
|
||||
|
||||
test('gridview events', () => {
|
||||
const gridview = new GridviewComponent(container, {
|
||||
const gridview = new GridviewComponent({
|
||||
parentElement: container,
|
||||
proportionalLayout: false,
|
||||
orientation: Orientation.VERTICAL,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
components: { default: TestGridview },
|
||||
});
|
||||
|
||||
gridview.layout(800, 400);
|
||||
@ -497,17 +434,11 @@ describe('gridview', () => {
|
||||
test('dispose of gridviewComponent', () => {
|
||||
expect(container.childNodes.length).toBe(0);
|
||||
|
||||
const gridview = new GridviewComponent(container, {
|
||||
const gridview = new GridviewComponent({
|
||||
parentElement: container,
|
||||
proportionalLayout: false,
|
||||
orientation: Orientation.VERTICAL,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
components: { default: TestGridview },
|
||||
});
|
||||
|
||||
gridview.layout(800, 400);
|
||||
@ -529,21 +460,15 @@ describe('gridview', () => {
|
||||
|
||||
gridview.dispose();
|
||||
|
||||
expect(container.children.length).toBe(0);
|
||||
expect(container.childNodes.length).toBe(0);
|
||||
});
|
||||
|
||||
test('#1/VERTICAL', () => {
|
||||
const gridview = new GridviewComponent(container, {
|
||||
const gridview = new GridviewComponent({
|
||||
parentElement: container,
|
||||
proportionalLayout: true,
|
||||
orientation: Orientation.VERTICAL,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
components: { default: TestGridview },
|
||||
});
|
||||
|
||||
gridview.layout(800, 400);
|
||||
@ -598,17 +523,11 @@ describe('gridview', () => {
|
||||
});
|
||||
|
||||
test('#2/HORIZONTAL', () => {
|
||||
const gridview = new GridviewComponent(container, {
|
||||
const gridview = new GridviewComponent({
|
||||
parentElement: container,
|
||||
proportionalLayout: true,
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
components: { default: TestGridview },
|
||||
});
|
||||
|
||||
gridview.layout(800, 400);
|
||||
@ -663,17 +582,11 @@ describe('gridview', () => {
|
||||
});
|
||||
|
||||
test('#3/HORIZONTAL', () => {
|
||||
const gridview = new GridviewComponent(container, {
|
||||
const gridview = new GridviewComponent({
|
||||
parentElement: container,
|
||||
proportionalLayout: true,
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
components: { default: TestGridview },
|
||||
});
|
||||
|
||||
gridview.layout(800, 400);
|
||||
@ -746,17 +659,11 @@ describe('gridview', () => {
|
||||
});
|
||||
|
||||
test('#4/HORIZONTAL', () => {
|
||||
const gridview = new GridviewComponent(container, {
|
||||
const gridview = new GridviewComponent({
|
||||
parentElement: container,
|
||||
proportionalLayout: true,
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
components: { default: TestGridview },
|
||||
});
|
||||
|
||||
gridview.layout(800, 400);
|
||||
@ -847,17 +754,11 @@ describe('gridview', () => {
|
||||
});
|
||||
|
||||
test('#5/VERTICAL', () => {
|
||||
const gridview = new GridviewComponent(container, {
|
||||
const gridview = new GridviewComponent({
|
||||
parentElement: container,
|
||||
proportionalLayout: true,
|
||||
orientation: Orientation.VERTICAL,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
components: { default: TestGridview },
|
||||
});
|
||||
|
||||
gridview.layout(800, 400);
|
||||
@ -948,17 +849,11 @@ describe('gridview', () => {
|
||||
});
|
||||
|
||||
test('#5/VERTICAL/proportional/false', () => {
|
||||
const gridview = new GridviewComponent(container, {
|
||||
const gridview = new GridviewComponent({
|
||||
parentElement: container,
|
||||
proportionalLayout: false,
|
||||
orientation: Orientation.VERTICAL,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
components: { default: TestGridview },
|
||||
});
|
||||
|
||||
gridview.layout(800, 400);
|
||||
@ -1049,17 +944,11 @@ describe('gridview', () => {
|
||||
});
|
||||
|
||||
test('#6/VERTICAL', () => {
|
||||
const gridview = new GridviewComponent(container, {
|
||||
const gridview = new GridviewComponent({
|
||||
parentElement: container,
|
||||
proportionalLayout: true,
|
||||
orientation: Orientation.VERTICAL,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
components: { default: TestGridview },
|
||||
});
|
||||
|
||||
gridview.layout(800, 400);
|
||||
@ -1180,17 +1069,11 @@ describe('gridview', () => {
|
||||
});
|
||||
|
||||
test('#7/VERTICAL layout first', () => {
|
||||
const gridview = new GridviewComponent(container, {
|
||||
const gridview = new GridviewComponent({
|
||||
parentElement: container,
|
||||
proportionalLayout: true,
|
||||
orientation: Orientation.VERTICAL,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
components: { default: TestGridview },
|
||||
});
|
||||
|
||||
gridview.layout(800, 400);
|
||||
@ -1311,17 +1194,11 @@ describe('gridview', () => {
|
||||
});
|
||||
|
||||
test('#8/VERTICAL layout after', () => {
|
||||
const gridview = new GridviewComponent(container, {
|
||||
const gridview = new GridviewComponent({
|
||||
parentElement: container,
|
||||
proportionalLayout: true,
|
||||
orientation: Orientation.VERTICAL,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
components: { default: TestGridview },
|
||||
});
|
||||
|
||||
gridview.layout(800, 400);
|
||||
@ -1444,17 +1321,11 @@ describe('gridview', () => {
|
||||
});
|
||||
|
||||
test('#9/HORIZONTAL', () => {
|
||||
const gridview = new GridviewComponent(container, {
|
||||
const gridview = new GridviewComponent({
|
||||
parentElement: container,
|
||||
proportionalLayout: true,
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
components: { default: TestGridview },
|
||||
});
|
||||
|
||||
gridview.layout(800, 400);
|
||||
@ -1575,17 +1446,11 @@ describe('gridview', () => {
|
||||
});
|
||||
|
||||
test('#9/HORIZONTAL/proportional/false', () => {
|
||||
const gridview = new GridviewComponent(container, {
|
||||
const gridview = new GridviewComponent({
|
||||
parentElement: container,
|
||||
proportionalLayout: false,
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
components: { default: TestGridview },
|
||||
});
|
||||
|
||||
gridview.layout(800, 400);
|
||||
@ -1706,17 +1571,11 @@ describe('gridview', () => {
|
||||
});
|
||||
|
||||
test('#10/HORIZONTAL scale x:1.5 y:2', () => {
|
||||
const gridview = new GridviewComponent(container, {
|
||||
const gridview = new GridviewComponent({
|
||||
parentElement: container,
|
||||
proportionalLayout: true,
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
components: { default: TestGridview },
|
||||
});
|
||||
|
||||
gridview.fromJSON({
|
||||
@ -1840,17 +1699,11 @@ describe('gridview', () => {
|
||||
});
|
||||
|
||||
test('panel is disposed of when component is disposed', () => {
|
||||
const gridview = new GridviewComponent(container, {
|
||||
const gridview = new GridviewComponent({
|
||||
parentElement: container,
|
||||
proportionalLayout: false,
|
||||
orientation: Orientation.VERTICAL,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
components: { default: TestGridview },
|
||||
});
|
||||
|
||||
gridview.layout(1000, 1000);
|
||||
@ -1877,17 +1730,11 @@ describe('gridview', () => {
|
||||
});
|
||||
|
||||
test('panel is disposed of when removed', () => {
|
||||
const gridview = new GridviewComponent(container, {
|
||||
const gridview = new GridviewComponent({
|
||||
parentElement: container,
|
||||
proportionalLayout: false,
|
||||
orientation: Orientation.VERTICAL,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
components: { default: TestGridview },
|
||||
});
|
||||
gridview.layout(1000, 1000);
|
||||
|
||||
@ -1913,17 +1760,11 @@ describe('gridview', () => {
|
||||
});
|
||||
|
||||
test('panel is disposed of when fromJSON is called', () => {
|
||||
const gridview = new GridviewComponent(container, {
|
||||
const gridview = new GridviewComponent({
|
||||
parentElement: container,
|
||||
proportionalLayout: false,
|
||||
orientation: Orientation.VERTICAL,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
components: { default: TestGridview },
|
||||
});
|
||||
gridview.layout(1000, 1000);
|
||||
|
||||
@ -1958,17 +1799,11 @@ describe('gridview', () => {
|
||||
test('fromJSON events should still fire', () => {
|
||||
jest.useFakeTimers();
|
||||
|
||||
const gridview = new GridviewComponent(container, {
|
||||
const gridview = new GridviewComponent({
|
||||
parentElement: container,
|
||||
proportionalLayout: true,
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
components: { default: TestGridview },
|
||||
});
|
||||
|
||||
let addGroup: GridviewPanel[] = [];
|
||||
@ -2087,17 +1922,11 @@ describe('gridview', () => {
|
||||
test('that fromJSON layouts are resized to the current dimensions', async () => {
|
||||
const container = document.createElement('div');
|
||||
|
||||
const gridview = new GridviewComponent(container, {
|
||||
const gridview = new GridviewComponent({
|
||||
parentElement: container,
|
||||
proportionalLayout: true,
|
||||
orientation: Orientation.VERTICAL,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
components: { default: TestGridview },
|
||||
});
|
||||
|
||||
gridview.layout(1600, 800);
|
||||
@ -2220,17 +2049,11 @@ describe('gridview', () => {
|
||||
test('that a deep HORIZONTAL layout with fromJSON dimensions identical to the current dimensions loads', async () => {
|
||||
const container = document.createElement('div');
|
||||
|
||||
const gridview = new GridviewComponent(container, {
|
||||
const gridview = new GridviewComponent({
|
||||
parentElement: container,
|
||||
proportionalLayout: true,
|
||||
orientation: Orientation.VERTICAL,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
components: { default: TestGridview },
|
||||
});
|
||||
|
||||
gridview.layout(6000, 5000);
|
||||
@ -2502,17 +2325,11 @@ describe('gridview', () => {
|
||||
test('that a deep VERTICAL layout with fromJSON dimensions identical to the current dimensions loads', async () => {
|
||||
const container = document.createElement('div');
|
||||
|
||||
const gridview = new GridviewComponent(container, {
|
||||
const gridview = new GridviewComponent({
|
||||
parentElement: container,
|
||||
proportionalLayout: true,
|
||||
orientation: Orientation.VERTICAL,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
components: { default: TestGridview },
|
||||
});
|
||||
|
||||
gridview.layout(5000, 6000);
|
||||
@ -2780,160 +2597,4 @@ describe('gridview', () => {
|
||||
activePanel: 'panel_1',
|
||||
});
|
||||
});
|
||||
|
||||
test('that loading a corrupt layout throws an error and leaves a clean gridview behind', () => {
|
||||
const gridview = new GridviewComponent(container, {
|
||||
proportionalLayout: true,
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error(`unsupported panel '${options.name}'`);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
let el = gridview.element.querySelector('.dv-view-container');
|
||||
expect(el).toBeTruthy();
|
||||
expect(el!.childNodes.length).toBe(0);
|
||||
|
||||
expect(() => {
|
||||
gridview.fromJSON({
|
||||
grid: {
|
||||
height: 400,
|
||||
width: 800,
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
root: {
|
||||
type: 'branch',
|
||||
size: 400,
|
||||
data: [
|
||||
{
|
||||
type: 'leaf',
|
||||
size: 200,
|
||||
data: {
|
||||
id: 'panel_1',
|
||||
component: 'default',
|
||||
snap: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'branch',
|
||||
size: 400,
|
||||
data: [
|
||||
{
|
||||
type: 'leaf',
|
||||
size: 250,
|
||||
data: {
|
||||
id: 'panel_1',
|
||||
component: 'default',
|
||||
snap: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'leaf',
|
||||
size: 150,
|
||||
data: {
|
||||
id: 'panel_1',
|
||||
component: 'somethingBad',
|
||||
snap: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'leaf',
|
||||
size: 200,
|
||||
data: {
|
||||
id: 'panel_1',
|
||||
component: 'default',
|
||||
snap: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
activePanel: 'panel_1',
|
||||
});
|
||||
}).toThrow("unsupported panel 'somethingBad'");
|
||||
|
||||
expect(gridview.groups.length).toBe(0);
|
||||
|
||||
el = gridview.element.querySelector('.dv-view-container');
|
||||
expect(el).toBeTruthy();
|
||||
expect(el!.childNodes.length).toBe(0);
|
||||
});
|
||||
|
||||
test('that disableAutoResizing is false by default', () => {
|
||||
const gridview = new GridviewComponent(container, {
|
||||
proportionalLayout: true,
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
expect(gridview.disableResizing).toBeFalsy();
|
||||
});
|
||||
|
||||
test('that disableAutoResizing can be enabled', () => {
|
||||
const gridview = new GridviewComponent(container, {
|
||||
proportionalLayout: true,
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
disableAutoResizing: true,
|
||||
});
|
||||
|
||||
expect(gridview.disableResizing).toBeTruthy();
|
||||
});
|
||||
|
||||
test('that setVisible toggles visiblity', () => {
|
||||
const gridview = new GridviewComponent(container, {
|
||||
proportionalLayout: true,
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
disableAutoResizing: true,
|
||||
});
|
||||
gridview.layout(1000, 1000);
|
||||
|
||||
const panel1 = gridview.addPanel({
|
||||
id: 'panel1',
|
||||
component: 'default',
|
||||
});
|
||||
const panel2 = gridview.addPanel({
|
||||
id: 'panel2',
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
expect(panel1.api.isVisible).toBeTruthy();
|
||||
expect(panel2.api.isVisible).toBeTruthy();
|
||||
|
||||
panel1.api.setVisible(false);
|
||||
expect(panel1.api.isVisible).toBeFalsy();
|
||||
expect(panel2.api.isVisible).toBeTruthy();
|
||||
|
||||
panel1.api.setVisible(true);
|
||||
expect(panel1.api.isVisible).toBeTruthy();
|
||||
expect(panel2.api.isVisible).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
@ -8,7 +8,6 @@ describe('gridviewPanel', () => {
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
options: {},
|
||||
onDidOptionsChange: jest.fn(),
|
||||
} as any;
|
||||
});
|
||||
|
||||
|
@ -1,8 +1,4 @@
|
||||
import {
|
||||
CompositeDisposable,
|
||||
Disposable,
|
||||
MutableDisposable,
|
||||
} from '../lifecycle';
|
||||
import { CompositeDisposable, MutableDisposable } from '../lifecycle';
|
||||
|
||||
describe('lifecycle', () => {
|
||||
test('mutable disposable', () => {
|
||||
@ -68,16 +64,4 @@ describe('lifecycle', () => {
|
||||
|
||||
expect(cut.checkIsDisposed()).toBeTruthy();
|
||||
});
|
||||
|
||||
test('Disposable.from(...)', () => {
|
||||
const func = jest.fn();
|
||||
|
||||
const disposable = Disposable.from(func);
|
||||
|
||||
expect(func).not.toHaveBeenCalled();
|
||||
|
||||
disposable.dispose();
|
||||
|
||||
expect(func).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
@ -8,8 +8,10 @@ describe('math', () => {
|
||||
expect(clamp(55, 40, 50)).toBe(50);
|
||||
});
|
||||
|
||||
it('if min > max return min', () => {
|
||||
expect(clamp(55, 50, 40)).toBe(50);
|
||||
it('should throw an error if min > max', () => {
|
||||
expect(() => clamp(55, 50, 40)).toThrow(
|
||||
'50 > 40 is an invalid condition'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -1,418 +0,0 @@
|
||||
import { Overlay } from '../../overlay/overlay';
|
||||
import { mockGetBoundingClientRect } from '../__test_utils__/utils';
|
||||
|
||||
describe('overlay', () => {
|
||||
test('toJSON, top left', () => {
|
||||
const container = document.createElement('div');
|
||||
const content = document.createElement('div');
|
||||
|
||||
document.body.appendChild(container);
|
||||
container.appendChild(content);
|
||||
|
||||
const cut = new Overlay({
|
||||
height: 200,
|
||||
width: 100,
|
||||
left: 10,
|
||||
top: 20,
|
||||
minimumInViewportWidth: 0,
|
||||
minimumInViewportHeight: 0,
|
||||
container,
|
||||
content,
|
||||
});
|
||||
|
||||
jest.spyOn(
|
||||
container.childNodes.item(0) as HTMLElement,
|
||||
'getBoundingClientRect'
|
||||
).mockImplementation(() => {
|
||||
return mockGetBoundingClientRect({
|
||||
left: 80,
|
||||
top: 100,
|
||||
width: 40,
|
||||
height: 50,
|
||||
});
|
||||
});
|
||||
jest.spyOn(container, 'getBoundingClientRect').mockImplementation(
|
||||
() => {
|
||||
return mockGetBoundingClientRect({
|
||||
left: 20,
|
||||
top: 30,
|
||||
width: 100,
|
||||
height: 100,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
cut.setBounds();
|
||||
|
||||
expect(cut.toJSON()).toEqual({
|
||||
top: 70,
|
||||
left: 60,
|
||||
width: 40,
|
||||
height: 50,
|
||||
});
|
||||
|
||||
cut.dispose();
|
||||
});
|
||||
|
||||
test('toJSON, bottom right', () => {
|
||||
const container = document.createElement('div');
|
||||
const content = document.createElement('div');
|
||||
|
||||
document.body.appendChild(container);
|
||||
container.appendChild(content);
|
||||
|
||||
const cut = new Overlay({
|
||||
height: 200,
|
||||
width: 100,
|
||||
right: 10,
|
||||
bottom: 20,
|
||||
minimumInViewportWidth: 0,
|
||||
minimumInViewportHeight: 0,
|
||||
container,
|
||||
content,
|
||||
});
|
||||
|
||||
jest.spyOn(
|
||||
container.childNodes.item(0) as HTMLElement,
|
||||
'getBoundingClientRect'
|
||||
).mockImplementation(() => {
|
||||
return mockGetBoundingClientRect({
|
||||
left: 80,
|
||||
top: 100,
|
||||
width: 40,
|
||||
height: 50,
|
||||
});
|
||||
});
|
||||
jest.spyOn(container, 'getBoundingClientRect').mockImplementation(
|
||||
() => {
|
||||
return mockGetBoundingClientRect({
|
||||
left: 20,
|
||||
top: 30,
|
||||
width: 100,
|
||||
height: 100,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
cut.setBounds();
|
||||
|
||||
expect(cut.toJSON()).toEqual({
|
||||
bottom: -20,
|
||||
right: 0,
|
||||
width: 40,
|
||||
height: 50,
|
||||
});
|
||||
|
||||
cut.dispose();
|
||||
});
|
||||
|
||||
test('that out-of-bounds dimensions are fixed, top left', () => {
|
||||
const container = document.createElement('div');
|
||||
const content = document.createElement('div');
|
||||
|
||||
document.body.appendChild(container);
|
||||
container.appendChild(content);
|
||||
|
||||
const cut = new Overlay({
|
||||
height: 200,
|
||||
width: 100,
|
||||
left: -1000,
|
||||
top: -1000,
|
||||
minimumInViewportWidth: 0,
|
||||
minimumInViewportHeight: 0,
|
||||
container,
|
||||
content,
|
||||
});
|
||||
|
||||
jest.spyOn(
|
||||
container.childNodes.item(0) as HTMLElement,
|
||||
'getBoundingClientRect'
|
||||
).mockImplementation(() => {
|
||||
return mockGetBoundingClientRect({
|
||||
left: 80,
|
||||
top: 100,
|
||||
width: 40,
|
||||
height: 50,
|
||||
});
|
||||
});
|
||||
jest.spyOn(container, 'getBoundingClientRect').mockImplementation(
|
||||
() => {
|
||||
return mockGetBoundingClientRect({
|
||||
left: 20,
|
||||
top: 30,
|
||||
width: 100,
|
||||
height: 100,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
cut.setBounds();
|
||||
|
||||
expect(cut.toJSON()).toEqual({
|
||||
top: 70,
|
||||
left: 60,
|
||||
width: 40,
|
||||
height: 50,
|
||||
});
|
||||
|
||||
cut.dispose();
|
||||
});
|
||||
|
||||
test('that out-of-bounds dimensions are fixed, bottom right', () => {
|
||||
const container = document.createElement('div');
|
||||
const content = document.createElement('div');
|
||||
|
||||
document.body.appendChild(container);
|
||||
container.appendChild(content);
|
||||
|
||||
const cut = new Overlay({
|
||||
height: 200,
|
||||
width: 100,
|
||||
bottom: -1000,
|
||||
right: -1000,
|
||||
minimumInViewportWidth: 0,
|
||||
minimumInViewportHeight: 0,
|
||||
container,
|
||||
content,
|
||||
});
|
||||
|
||||
jest.spyOn(
|
||||
container.childNodes.item(0) as HTMLElement,
|
||||
'getBoundingClientRect'
|
||||
).mockImplementation(() => {
|
||||
return mockGetBoundingClientRect({
|
||||
left: 80,
|
||||
top: 100,
|
||||
width: 40,
|
||||
height: 50,
|
||||
});
|
||||
});
|
||||
jest.spyOn(container, 'getBoundingClientRect').mockImplementation(
|
||||
() => {
|
||||
return mockGetBoundingClientRect({
|
||||
left: 20,
|
||||
top: 30,
|
||||
width: 100,
|
||||
height: 100,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
cut.setBounds();
|
||||
|
||||
expect(cut.toJSON()).toEqual({
|
||||
bottom: -20,
|
||||
right: 0,
|
||||
width: 40,
|
||||
height: 50,
|
||||
});
|
||||
|
||||
cut.dispose();
|
||||
});
|
||||
|
||||
test('setBounds, top left', () => {
|
||||
const container = document.createElement('div');
|
||||
const content = document.createElement('div');
|
||||
|
||||
document.body.appendChild(container);
|
||||
container.appendChild(content);
|
||||
|
||||
const cut = new Overlay({
|
||||
height: 1000,
|
||||
width: 1000,
|
||||
left: 0,
|
||||
top: 0,
|
||||
minimumInViewportWidth: 0,
|
||||
minimumInViewportHeight: 0,
|
||||
container,
|
||||
content,
|
||||
});
|
||||
|
||||
const element: HTMLElement = container.querySelector(
|
||||
'.dv-resize-container'
|
||||
)!;
|
||||
expect(element).toBeTruthy();
|
||||
|
||||
jest.spyOn(element, 'getBoundingClientRect').mockImplementation(() => {
|
||||
return mockGetBoundingClientRect({
|
||||
left: 300,
|
||||
top: 400,
|
||||
width: 200,
|
||||
height: 100,
|
||||
});
|
||||
});
|
||||
jest.spyOn(container, 'getBoundingClientRect').mockImplementation(
|
||||
() => {
|
||||
return mockGetBoundingClientRect({
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: 1000,
|
||||
height: 1000,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
cut.setBounds({ height: 100, width: 200, left: 300, top: 400 });
|
||||
|
||||
expect(element.style.height).toBe('100px');
|
||||
expect(element.style.width).toBe('200px');
|
||||
expect(element.style.left).toBe('300px');
|
||||
expect(element.style.top).toBe('400px');
|
||||
|
||||
cut.dispose();
|
||||
});
|
||||
|
||||
test('setBounds, bottom right', () => {
|
||||
const container = document.createElement('div');
|
||||
const content = document.createElement('div');
|
||||
|
||||
document.body.appendChild(container);
|
||||
container.appendChild(content);
|
||||
|
||||
const cut = new Overlay({
|
||||
height: 1000,
|
||||
width: 1000,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
minimumInViewportWidth: 0,
|
||||
minimumInViewportHeight: 0,
|
||||
container,
|
||||
content,
|
||||
});
|
||||
|
||||
const element: HTMLElement = container.querySelector(
|
||||
'.dv-resize-container'
|
||||
)!;
|
||||
expect(element).toBeTruthy();
|
||||
|
||||
jest.spyOn(element, 'getBoundingClientRect').mockImplementation(() => {
|
||||
return mockGetBoundingClientRect({
|
||||
left: 500,
|
||||
top: 500,
|
||||
width: 200,
|
||||
height: 100,
|
||||
});
|
||||
});
|
||||
jest.spyOn(container, 'getBoundingClientRect').mockImplementation(
|
||||
() => {
|
||||
return mockGetBoundingClientRect({
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: 1000,
|
||||
height: 1000,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
cut.setBounds({ height: 100, width: 200, right: 300, bottom: 400 });
|
||||
|
||||
expect(element.style.height).toBe('100px');
|
||||
expect(element.style.width).toBe('200px');
|
||||
expect(element.style.right).toBe('300px');
|
||||
expect(element.style.bottom).toBe('400px');
|
||||
|
||||
cut.dispose();
|
||||
});
|
||||
|
||||
test('that the resize handles are added', () => {
|
||||
const container = document.createElement('div');
|
||||
const content = document.createElement('div');
|
||||
|
||||
const cut = new Overlay({
|
||||
height: 500,
|
||||
width: 500,
|
||||
left: 100,
|
||||
top: 200,
|
||||
minimumInViewportWidth: 0,
|
||||
minimumInViewportHeight: 0,
|
||||
container,
|
||||
content,
|
||||
});
|
||||
|
||||
expect(container.querySelector('.dv-resize-handle-top')).toBeTruthy();
|
||||
expect(
|
||||
container.querySelector('.dv-resize-handle-bottom')
|
||||
).toBeTruthy();
|
||||
expect(container.querySelector('.dv-resize-handle-left')).toBeTruthy();
|
||||
expect(container.querySelector('.dv-resize-handle-right')).toBeTruthy();
|
||||
expect(
|
||||
container.querySelector('.dv-resize-handle-topleft')
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
container.querySelector('.dv-resize-handle-topright')
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
container.querySelector('.dv-resize-handle-bottomleft')
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
container.querySelector('.dv-resize-handle-bottomright')
|
||||
).toBeTruthy();
|
||||
|
||||
cut.dispose();
|
||||
});
|
||||
|
||||
test('aria-level attributes and corresponding z-index', () => {
|
||||
const container = document.createElement('div');
|
||||
const content = document.createElement('div');
|
||||
|
||||
const createOverlay = () =>
|
||||
new Overlay({
|
||||
height: 500,
|
||||
width: 500,
|
||||
left: 100,
|
||||
top: 200,
|
||||
minimumInViewportWidth: 0,
|
||||
minimumInViewportHeight: 0,
|
||||
container,
|
||||
content,
|
||||
});
|
||||
|
||||
const overlay1 = createOverlay();
|
||||
|
||||
const zIndexValue = (delta: number) =>
|
||||
`calc(var(--dv-overlay-z-index, 999) + ${delta})`;
|
||||
|
||||
expect(overlay1.element.getAttribute('aria-level')).toBe('0');
|
||||
expect(overlay1.element.style.zIndex).toBe(zIndexValue(0));
|
||||
|
||||
const overlay2 = createOverlay();
|
||||
const overlay3 = createOverlay();
|
||||
|
||||
expect(overlay1.element.getAttribute('aria-level')).toBe('0');
|
||||
expect(overlay2.element.getAttribute('aria-level')).toBe('1');
|
||||
expect(overlay3.element.getAttribute('aria-level')).toBe('2');
|
||||
expect(overlay1.element.style.zIndex).toBe(zIndexValue(0));
|
||||
expect(overlay2.element.style.zIndex).toBe(zIndexValue(2));
|
||||
expect(overlay3.element.style.zIndex).toBe(zIndexValue(4));
|
||||
|
||||
overlay2.bringToFront();
|
||||
|
||||
expect(overlay1.element.getAttribute('aria-level')).toBe('0');
|
||||
expect(overlay2.element.getAttribute('aria-level')).toBe('2');
|
||||
expect(overlay3.element.getAttribute('aria-level')).toBe('1');
|
||||
expect(overlay1.element.style.zIndex).toBe(zIndexValue(0));
|
||||
expect(overlay2.element.style.zIndex).toBe(zIndexValue(4));
|
||||
expect(overlay3.element.style.zIndex).toBe(zIndexValue(2));
|
||||
|
||||
overlay1.bringToFront();
|
||||
|
||||
expect(overlay1.element.getAttribute('aria-level')).toBe('2');
|
||||
expect(overlay2.element.getAttribute('aria-level')).toBe('1');
|
||||
expect(overlay3.element.getAttribute('aria-level')).toBe('0');
|
||||
expect(overlay1.element.style.zIndex).toBe(zIndexValue(4));
|
||||
expect(overlay2.element.style.zIndex).toBe(zIndexValue(2));
|
||||
expect(overlay3.element.style.zIndex).toBe(zIndexValue(0));
|
||||
|
||||
overlay2.dispose();
|
||||
|
||||
expect(overlay1.element.getAttribute('aria-level')).toBe('1');
|
||||
expect(overlay3.element.getAttribute('aria-level')).toBe('0');
|
||||
expect(overlay1.element.style.zIndex).toBe(zIndexValue(2));
|
||||
expect(overlay3.element.style.zIndex).toBe(zIndexValue(0));
|
||||
|
||||
overlay1.dispose();
|
||||
|
||||
expect(overlay3.element.getAttribute('aria-level')).toBe('0');
|
||||
expect(overlay3.element.style.zIndex).toBe(zIndexValue(0));
|
||||
});
|
||||
});
|
@ -1,265 +0,0 @@
|
||||
import { Droptarget } from '../../dnd/droptarget';
|
||||
import { IDockviewPanel } from '../../dockview/dockviewPanel';
|
||||
import { Emitter } from '../../events';
|
||||
import {
|
||||
IRenderable,
|
||||
OverlayRenderContainer,
|
||||
} from '../../overlay/overlayRenderContainer';
|
||||
import { fromPartial } from '@total-typescript/shoehorn';
|
||||
import { Writable, exhaustMicrotaskQueue } from '../__test_utils__/utils';
|
||||
import { DockviewComponent } from '../../dockview/dockviewComponent';
|
||||
import { DockviewGroupPanel } from '../../dockview/dockviewGroupPanel';
|
||||
|
||||
describe('overlayRenderContainer', () => {
|
||||
let referenceContainer: IRenderable;
|
||||
let parentContainer: HTMLElement;
|
||||
|
||||
beforeEach(() => {
|
||||
parentContainer = document.createElement('div');
|
||||
|
||||
referenceContainer = {
|
||||
element: document.createElement('div'),
|
||||
dropTarget: fromPartial<Droptarget>({}),
|
||||
};
|
||||
});
|
||||
|
||||
test('that attach(...) and detach(...) mutate the DOM as expected', () => {
|
||||
const cut = new OverlayRenderContainer(
|
||||
parentContainer,
|
||||
fromPartial<DockviewComponent>({})
|
||||
);
|
||||
|
||||
const panelContentEl = document.createElement('div');
|
||||
|
||||
const onDidVisibilityChange = new Emitter<any>();
|
||||
const onDidDimensionsChange = new Emitter<any>();
|
||||
const onDidLocationChange = new Emitter<any>();
|
||||
|
||||
const panel = fromPartial<IDockviewPanel>({
|
||||
api: {
|
||||
id: 'test_panel_id',
|
||||
onDidVisibilityChange: onDidVisibilityChange.event,
|
||||
onDidDimensionsChange: onDidDimensionsChange.event,
|
||||
onDidLocationChange: onDidLocationChange.event,
|
||||
isVisible: true,
|
||||
location: { type: 'grid' },
|
||||
},
|
||||
view: {
|
||||
content: {
|
||||
element: panelContentEl,
|
||||
},
|
||||
},
|
||||
group: {
|
||||
api: {
|
||||
location: { type: 'grid' },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
cut.attach({ panel, referenceContainer });
|
||||
|
||||
expect(panelContentEl.parentElement?.parentElement).toBe(
|
||||
parentContainer
|
||||
);
|
||||
|
||||
cut.detatch(panel);
|
||||
|
||||
expect(panelContentEl.parentElement?.parentElement).toBeUndefined();
|
||||
});
|
||||
|
||||
test('add a view that is not currently in the DOM', async () => {
|
||||
const cut = new OverlayRenderContainer(
|
||||
parentContainer,
|
||||
fromPartial<DockviewComponent>({})
|
||||
);
|
||||
|
||||
const panelContentEl = document.createElement('div');
|
||||
|
||||
const onDidVisibilityChange = new Emitter<any>();
|
||||
const onDidDimensionsChange = new Emitter<any>();
|
||||
const onDidLocationChange = new Emitter<any>();
|
||||
|
||||
const panel = fromPartial<IDockviewPanel>({
|
||||
api: {
|
||||
id: 'test_panel_id',
|
||||
onDidVisibilityChange: onDidVisibilityChange.event,
|
||||
onDidDimensionsChange: onDidDimensionsChange.event,
|
||||
onDidLocationChange: onDidLocationChange.event,
|
||||
isVisible: true,
|
||||
location: { type: 'grid' },
|
||||
},
|
||||
view: {
|
||||
content: {
|
||||
element: panelContentEl,
|
||||
},
|
||||
},
|
||||
group: {
|
||||
api: {
|
||||
location: { type: 'grid' },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
(parentContainer as jest.Mocked<HTMLDivElement>).getBoundingClientRect =
|
||||
jest
|
||||
.fn<DOMRect, []>()
|
||||
.mockReturnValueOnce(
|
||||
fromPartial<DOMRect>({
|
||||
left: 100,
|
||||
top: 200,
|
||||
width: 1000,
|
||||
height: 500,
|
||||
})
|
||||
)
|
||||
.mockReturnValueOnce(
|
||||
fromPartial<DOMRect>({
|
||||
left: 101,
|
||||
top: 201,
|
||||
width: 1000,
|
||||
height: 500,
|
||||
})
|
||||
)
|
||||
.mockReturnValueOnce(
|
||||
fromPartial<DOMRect>({
|
||||
left: 100,
|
||||
top: 200,
|
||||
width: 1000,
|
||||
height: 500,
|
||||
})
|
||||
);
|
||||
|
||||
(
|
||||
referenceContainer.element as jest.Mocked<HTMLDivElement>
|
||||
).getBoundingClientRect = jest
|
||||
.fn<DOMRect, []>()
|
||||
.mockReturnValueOnce(
|
||||
fromPartial<DOMRect>({
|
||||
left: 150,
|
||||
top: 300,
|
||||
width: 100,
|
||||
height: 200,
|
||||
})
|
||||
)
|
||||
.mockReturnValueOnce(
|
||||
fromPartial<DOMRect>({
|
||||
left: 150,
|
||||
top: 300,
|
||||
width: 101,
|
||||
height: 201,
|
||||
})
|
||||
)
|
||||
.mockReturnValueOnce(
|
||||
fromPartial<DOMRect>({
|
||||
left: 150,
|
||||
top: 300,
|
||||
width: 100,
|
||||
height: 200,
|
||||
})
|
||||
);
|
||||
|
||||
const container = cut.attach({ panel, referenceContainer });
|
||||
|
||||
await exhaustMicrotaskQueue();
|
||||
|
||||
expect(panelContentEl.parentElement).toBe(container);
|
||||
expect(container.parentElement).toBe(parentContainer);
|
||||
|
||||
expect(container.style.display).toBe('');
|
||||
|
||||
expect(container.style.left).toBe('50px');
|
||||
expect(container.style.top).toBe('100px');
|
||||
expect(container.style.width).toBe('100px');
|
||||
expect(container.style.height).toBe('200px');
|
||||
expect(
|
||||
referenceContainer.element.getBoundingClientRect
|
||||
).toHaveBeenCalledTimes(1);
|
||||
|
||||
onDidDimensionsChange.fire({});
|
||||
expect(container.style.display).toBe('');
|
||||
|
||||
expect(container.style.left).toBe('49px');
|
||||
expect(container.style.top).toBe('99px');
|
||||
expect(container.style.width).toBe('101px');
|
||||
expect(container.style.height).toBe('201px');
|
||||
expect(
|
||||
referenceContainer.element.getBoundingClientRect
|
||||
).toHaveBeenCalledTimes(2);
|
||||
|
||||
(panel as Writable<IDockviewPanel>).api.isVisible = false;
|
||||
onDidVisibilityChange.fire({});
|
||||
expect(container.style.display).toBe('none');
|
||||
expect(
|
||||
referenceContainer.element.getBoundingClientRect
|
||||
).toHaveBeenCalledTimes(2);
|
||||
|
||||
(panel as Writable<IDockviewPanel>).api.isVisible = true;
|
||||
onDidVisibilityChange.fire({});
|
||||
expect(container.style.display).toBe('');
|
||||
|
||||
expect(container.style.left).toBe('50px');
|
||||
expect(container.style.top).toBe('100px');
|
||||
expect(container.style.width).toBe('100px');
|
||||
expect(container.style.height).toBe('200px');
|
||||
expect(
|
||||
referenceContainer.element.getBoundingClientRect
|
||||
).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
test('related z-index from `aria-level` set on floating panels', async () => {
|
||||
const group = fromPartial<DockviewGroupPanel>({});
|
||||
|
||||
const element = document.createElement('div');
|
||||
element.setAttribute('aria-level', '2');
|
||||
const spy = jest.spyOn(element, 'getAttribute');
|
||||
|
||||
const accessor = fromPartial<DockviewComponent>({
|
||||
floatingGroups: [
|
||||
{
|
||||
group,
|
||||
overlay: {
|
||||
element,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const cut = new OverlayRenderContainer(parentContainer, accessor);
|
||||
|
||||
const panelContentEl = document.createElement('div');
|
||||
|
||||
const onDidVisibilityChange = new Emitter<any>();
|
||||
const onDidDimensionsChange = new Emitter<any>();
|
||||
const onDidLocationChange = new Emitter<any>();
|
||||
|
||||
const panel = fromPartial<IDockviewPanel>({
|
||||
api: {
|
||||
id: 'test_panel_id',
|
||||
onDidVisibilityChange: onDidVisibilityChange.event,
|
||||
onDidDimensionsChange: onDidDimensionsChange.event,
|
||||
onDidLocationChange: onDidLocationChange.event,
|
||||
isVisible: true,
|
||||
group,
|
||||
location: { type: 'floating' },
|
||||
},
|
||||
view: {
|
||||
content: {
|
||||
element: panelContentEl,
|
||||
},
|
||||
},
|
||||
group: {
|
||||
api: {
|
||||
location: { type: 'floating' },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
cut.attach({ panel, referenceContainer });
|
||||
|
||||
await exhaustMicrotaskQueue();
|
||||
|
||||
expect(spy).toHaveBeenCalledWith('aria-level');
|
||||
expect(panelContentEl.parentElement!.style.zIndex).toBe(
|
||||
'calc(var(--dv-overlay-z-index, 999) + 5)'
|
||||
);
|
||||
});
|
||||
});
|
@ -0,0 +1,102 @@
|
||||
import { createComponent } from '../../panel/componentFactory';
|
||||
|
||||
describe('componentFactory', () => {
|
||||
describe('createComponent', () => {
|
||||
test('valid component and framework component', () => {
|
||||
const mock = jest.fn();
|
||||
const mock2 = jest.fn();
|
||||
|
||||
expect(() =>
|
||||
createComponent(
|
||||
'id-1',
|
||||
'component-1',
|
||||
{ 'component-1': mock },
|
||||
{ 'component-1': mock2 }
|
||||
)
|
||||
).toThrow(
|
||||
"Cannot create 'id-1'. component 'component-1' registered as both a component and frameworkComponent"
|
||||
);
|
||||
});
|
||||
|
||||
test('valid framework component but no factory', () => {
|
||||
const mock = jest.fn();
|
||||
|
||||
expect(() =>
|
||||
createComponent(
|
||||
'id-1',
|
||||
'component-1',
|
||||
{},
|
||||
{ 'component-1': mock }
|
||||
)
|
||||
).toThrow(
|
||||
"Cannot create 'id-1' for framework component 'component-1'. you must register a frameworkPanelWrapper to use framework components"
|
||||
);
|
||||
});
|
||||
|
||||
test('valid framework component', () => {
|
||||
const component = jest.fn();
|
||||
const createComponentFn = jest
|
||||
.fn()
|
||||
.mockImplementation(() => component);
|
||||
const frameworkComponent = jest.fn();
|
||||
|
||||
expect(
|
||||
createComponent(
|
||||
'id-1',
|
||||
'component-1',
|
||||
{},
|
||||
{ 'component-1': frameworkComponent },
|
||||
{
|
||||
createComponent: createComponentFn,
|
||||
}
|
||||
)
|
||||
).toBe(component);
|
||||
|
||||
expect(createComponentFn).toHaveBeenCalledWith(
|
||||
'id-1',
|
||||
'component-1',
|
||||
frameworkComponent
|
||||
);
|
||||
});
|
||||
|
||||
test('no valid component with fallback', () => {
|
||||
const mock = jest.fn();
|
||||
|
||||
expect(
|
||||
createComponent(
|
||||
'id-1',
|
||||
'component-1',
|
||||
{},
|
||||
{},
|
||||
{
|
||||
createComponent: () => null,
|
||||
},
|
||||
() => mock
|
||||
)
|
||||
).toBe(mock);
|
||||
});
|
||||
|
||||
test('no valid component', () => {
|
||||
expect(() =>
|
||||
createComponent('id-1', 'component-1', {}, {})
|
||||
).toThrow(
|
||||
"Cannot create 'id-1', no component 'component-1' provided"
|
||||
);
|
||||
});
|
||||
|
||||
test('valid component', () => {
|
||||
const component = jest.fn();
|
||||
|
||||
const componentResult = createComponent(
|
||||
'id-1',
|
||||
'component-1',
|
||||
{ 'component-1': component },
|
||||
{}
|
||||
);
|
||||
|
||||
expect(component).toHaveBeenCalled();
|
||||
|
||||
expect(componentResult instanceof component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
@ -1,10 +1,14 @@
|
||||
import { CompositeDisposable } from '../../lifecycle';
|
||||
import { Paneview } from '../../paneview/paneview';
|
||||
import { IPanePart, PaneviewPanel } from '../../paneview/paneviewPanel';
|
||||
import {
|
||||
IPaneBodyPart,
|
||||
IPaneHeaderPart,
|
||||
PaneviewPanel,
|
||||
} from '../../paneview/paneviewPanel';
|
||||
import { Orientation } from '../../splitview/splitview';
|
||||
|
||||
class TestPanel extends PaneviewPanel {
|
||||
protected getBodyComponent(): IPanePart {
|
||||
protected getBodyComponent(): IPaneBodyPart {
|
||||
return {
|
||||
element: document.createElement('div'),
|
||||
update: () => {
|
||||
@ -19,7 +23,7 @@ class TestPanel extends PaneviewPanel {
|
||||
};
|
||||
}
|
||||
|
||||
protected getHeaderComponent(): IPanePart {
|
||||
protected getHeaderComponent(): IPaneHeaderPart {
|
||||
return {
|
||||
element: document.createElement('div'),
|
||||
update: () => {
|
||||
@ -56,28 +60,22 @@ describe('paneview', () => {
|
||||
paneview.onDidRemoveView((view) => removed.push(view))
|
||||
);
|
||||
|
||||
const view1 = new TestPanel({
|
||||
id: 'id',
|
||||
component: 'component',
|
||||
headerComponent: 'headerComponent',
|
||||
orientation: Orientation.VERTICAL,
|
||||
isExpanded: true,
|
||||
isHeaderVisible: true,
|
||||
headerSize: 22,
|
||||
minimumBodySize: 0,
|
||||
maximumBodySize: Number.MAX_SAFE_INTEGER,
|
||||
});
|
||||
const view2 = new TestPanel({
|
||||
id: 'id2',
|
||||
component: 'component',
|
||||
headerComponent: 'headerComponent',
|
||||
orientation: Orientation.VERTICAL,
|
||||
isExpanded: true,
|
||||
isHeaderVisible: true,
|
||||
headerSize: 22,
|
||||
minimumBodySize: 0,
|
||||
maximumBodySize: Number.MAX_SAFE_INTEGER,
|
||||
});
|
||||
const view1 = new TestPanel(
|
||||
'id',
|
||||
'component',
|
||||
'headerComponent',
|
||||
Orientation.VERTICAL,
|
||||
true,
|
||||
true
|
||||
);
|
||||
const view2 = new TestPanel(
|
||||
'id2',
|
||||
'component',
|
||||
'headerComponent',
|
||||
Orientation.VERTICAL,
|
||||
true,
|
||||
true
|
||||
);
|
||||
|
||||
expect(added.length).toBe(0);
|
||||
expect(removed.length).toBe(0);
|
||||
@ -112,28 +110,22 @@ describe('paneview', () => {
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
});
|
||||
|
||||
const view1 = new TestPanel({
|
||||
id: 'id',
|
||||
component: 'component',
|
||||
headerComponent: 'headerComponent',
|
||||
orientation: Orientation.VERTICAL,
|
||||
isExpanded: true,
|
||||
isHeaderVisible: true,
|
||||
headerSize: 22,
|
||||
minimumBodySize: 0,
|
||||
maximumBodySize: Number.MAX_SAFE_INTEGER,
|
||||
});
|
||||
const view2 = new TestPanel({
|
||||
id: 'id2',
|
||||
component: 'component',
|
||||
headerComponent: 'headerComponent',
|
||||
orientation: Orientation.VERTICAL,
|
||||
isExpanded: true,
|
||||
isHeaderVisible: true,
|
||||
headerSize: 22,
|
||||
minimumBodySize: 0,
|
||||
maximumBodySize: Number.MAX_SAFE_INTEGER,
|
||||
});
|
||||
const view1 = new TestPanel(
|
||||
'id',
|
||||
'component',
|
||||
'headerComponent',
|
||||
Orientation.VERTICAL,
|
||||
true,
|
||||
true
|
||||
);
|
||||
const view2 = new TestPanel(
|
||||
'id2',
|
||||
'component',
|
||||
'headerComponent',
|
||||
Orientation.VERTICAL,
|
||||
true,
|
||||
true
|
||||
);
|
||||
|
||||
paneview.addPane(view1);
|
||||
paneview.addPane(view2);
|
||||
|
@ -4,28 +4,19 @@ import { PanelUpdateEvent } from '../../panel/types';
|
||||
import { PaneviewComponent } from '../../paneview/paneviewComponent';
|
||||
import {
|
||||
PaneviewPanel,
|
||||
IPanePart,
|
||||
IPaneBodyPart,
|
||||
IPaneHeaderPart,
|
||||
PanePanelComponentInitParameter,
|
||||
} from '../../paneview/paneviewPanel';
|
||||
import { Orientation } from '../../splitview/splitview';
|
||||
|
||||
class TestPanel extends PaneviewPanel {
|
||||
constructor(id: string, component: string) {
|
||||
super({
|
||||
id,
|
||||
component,
|
||||
headerComponent: 'header',
|
||||
orientation: Orientation.VERTICAL,
|
||||
isExpanded: false,
|
||||
isHeaderVisible: true,
|
||||
headerSize: 22,
|
||||
minimumBodySize: 0,
|
||||
maximumBodySize: Number.MAX_SAFE_INTEGER,
|
||||
});
|
||||
super(id, component, 'header', Orientation.VERTICAL, false, true);
|
||||
}
|
||||
|
||||
getHeaderComponent() {
|
||||
return new (class Header implements IPanePart {
|
||||
return new (class Header implements IPaneHeaderPart {
|
||||
private _element: HTMLElement = document.createElement('div');
|
||||
|
||||
get element() {
|
||||
@ -47,7 +38,7 @@ class TestPanel extends PaneviewPanel {
|
||||
}
|
||||
|
||||
getBodyComponent() {
|
||||
return new (class Header implements IPanePart {
|
||||
return new (class Header implements IPaneBodyPart {
|
||||
private _element: HTMLElement = document.createElement('div');
|
||||
|
||||
get element() {
|
||||
@ -69,7 +60,7 @@ class TestPanel extends PaneviewPanel {
|
||||
}
|
||||
}
|
||||
|
||||
describe('paneviewComponent', () => {
|
||||
describe('componentPaneview', () => {
|
||||
let container: HTMLElement;
|
||||
|
||||
beforeEach(() => {
|
||||
@ -77,52 +68,26 @@ describe('paneviewComponent', () => {
|
||||
container.className = 'container';
|
||||
});
|
||||
|
||||
test('that the container is not removed when grid is disposed', () => {
|
||||
const root = document.createElement('div');
|
||||
const container = document.createElement('div');
|
||||
root.appendChild(container);
|
||||
|
||||
const paneview = new PaneviewComponent(container, {
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
paneview.dispose();
|
||||
|
||||
expect(container.parentElement).toBe(root);
|
||||
expect(container.children.length).toBe(0);
|
||||
});
|
||||
|
||||
test('vertical panels', () => {
|
||||
const disposables = new CompositeDisposable();
|
||||
|
||||
const paneview = new PaneviewComponent(container, {
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
const paneview = new PaneviewComponent({
|
||||
parentElement: container,
|
||||
components: {
|
||||
testPanel: TestPanel,
|
||||
},
|
||||
});
|
||||
|
||||
paneview.layout(300, 200);
|
||||
paneview.layout(600, 400);
|
||||
|
||||
paneview.addPanel({
|
||||
id: 'panel1',
|
||||
component: 'default',
|
||||
component: 'testPanel',
|
||||
title: 'Panel 1',
|
||||
});
|
||||
paneview.addPanel({
|
||||
id: 'panel2',
|
||||
component: 'default',
|
||||
component: 'testPanel',
|
||||
title: 'Panel2',
|
||||
});
|
||||
|
||||
@ -143,8 +108,6 @@ describe('paneviewComponent', () => {
|
||||
})
|
||||
);
|
||||
|
||||
paneview.layout(600, 400);
|
||||
|
||||
expect(panel1Dimensions).toEqual({ width: 600, height: 22 });
|
||||
expect(panel2Dimensions).toEqual({ width: 600, height: 22 });
|
||||
|
||||
@ -179,19 +142,13 @@ describe('paneviewComponent', () => {
|
||||
});
|
||||
|
||||
test('serialization', () => {
|
||||
const paneview = new PaneviewComponent(container, {
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
const paneview = new PaneviewComponent({
|
||||
parentElement: container,
|
||||
components: {
|
||||
testPanel: TestPanel,
|
||||
},
|
||||
});
|
||||
|
||||
expect(container.querySelectorAll('.dv-pane-container').length).toBe(1);
|
||||
|
||||
paneview.fromJSON({
|
||||
size: 6,
|
||||
views: [
|
||||
@ -199,7 +156,7 @@ describe('paneviewComponent', () => {
|
||||
size: 1,
|
||||
data: {
|
||||
id: 'panel1',
|
||||
component: 'default',
|
||||
component: 'testPanel',
|
||||
title: 'Panel 1',
|
||||
},
|
||||
expanded: true,
|
||||
@ -208,7 +165,7 @@ describe('paneviewComponent', () => {
|
||||
size: 2,
|
||||
data: {
|
||||
id: 'panel2',
|
||||
component: 'default',
|
||||
component: 'testPanel',
|
||||
title: 'Panel 2',
|
||||
},
|
||||
expanded: false,
|
||||
@ -217,15 +174,13 @@ describe('paneviewComponent', () => {
|
||||
size: 3,
|
||||
data: {
|
||||
id: 'panel3',
|
||||
component: 'default',
|
||||
component: 'testPanel',
|
||||
title: 'Panel 3',
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(container.querySelectorAll('.dv-pane-container').length).toBe(1);
|
||||
|
||||
paneview.layout(400, 800);
|
||||
|
||||
const panel1 = paneview.getPanel('panel1');
|
||||
@ -265,57 +220,53 @@ describe('paneviewComponent', () => {
|
||||
size: 756,
|
||||
data: {
|
||||
id: 'panel1',
|
||||
component: 'default',
|
||||
component: 'testPanel',
|
||||
title: 'Panel 1',
|
||||
},
|
||||
expanded: true,
|
||||
headerSize: 22,
|
||||
minimumSize: 100,
|
||||
},
|
||||
{
|
||||
size: 22,
|
||||
data: {
|
||||
id: 'panel2',
|
||||
component: 'default',
|
||||
component: 'testPanel',
|
||||
title: 'Panel 2',
|
||||
},
|
||||
expanded: false,
|
||||
headerSize: 22,
|
||||
minimumSize: 100,
|
||||
},
|
||||
{
|
||||
size: 22,
|
||||
data: {
|
||||
id: 'panel3',
|
||||
component: 'default',
|
||||
component: 'testPanel',
|
||||
title: 'Panel 3',
|
||||
},
|
||||
expanded: false,
|
||||
headerSize: 22,
|
||||
minimumSize: 100,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test('toJSON shouldnt fire any layout events', () => {
|
||||
const paneview = new PaneviewComponent(container, {
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
const paneview = new PaneviewComponent({
|
||||
parentElement: container,
|
||||
components: {
|
||||
testPanel: TestPanel,
|
||||
},
|
||||
});
|
||||
paneview.layout(1000, 1000);
|
||||
|
||||
paneview.addPanel({
|
||||
id: 'panel1',
|
||||
component: 'default',
|
||||
component: 'testPanel',
|
||||
title: 'Panel 1',
|
||||
});
|
||||
paneview.addPanel({
|
||||
id: 'panel2',
|
||||
component: 'default',
|
||||
component: 'testPanel',
|
||||
title: 'Panel 2',
|
||||
});
|
||||
|
||||
@ -329,15 +280,13 @@ describe('paneviewComponent', () => {
|
||||
disposable.dispose();
|
||||
});
|
||||
|
||||
test('panel is disposed of when component is disposed', () => {
|
||||
const paneview = new PaneviewComponent(container, {
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
test('dispose of paneviewComponent', () => {
|
||||
expect(container.childNodes.length).toBe(0);
|
||||
|
||||
const paneview = new PaneviewComponent({
|
||||
parentElement: container,
|
||||
components: {
|
||||
testPanel: TestPanel,
|
||||
},
|
||||
});
|
||||
|
||||
@ -345,12 +294,40 @@ describe('paneviewComponent', () => {
|
||||
|
||||
paneview.addPanel({
|
||||
id: 'panel1',
|
||||
component: 'default',
|
||||
component: 'testPanel',
|
||||
title: 'Panel 1',
|
||||
});
|
||||
paneview.addPanel({
|
||||
id: 'panel2',
|
||||
component: 'default',
|
||||
component: 'testPanel',
|
||||
title: 'Panel 2',
|
||||
});
|
||||
|
||||
expect(container.childNodes.length).toBeGreaterThan(0);
|
||||
|
||||
paneview.dispose();
|
||||
|
||||
expect(container.childNodes.length).toBe(0);
|
||||
});
|
||||
|
||||
test('panel is disposed of when component is disposed', () => {
|
||||
const paneview = new PaneviewComponent({
|
||||
parentElement: container,
|
||||
components: {
|
||||
testPanel: TestPanel,
|
||||
},
|
||||
});
|
||||
|
||||
paneview.layout(1000, 1000);
|
||||
|
||||
paneview.addPanel({
|
||||
id: 'panel1',
|
||||
component: 'testPanel',
|
||||
title: 'Panel 1',
|
||||
});
|
||||
paneview.addPanel({
|
||||
id: 'panel2',
|
||||
component: 'testPanel',
|
||||
title: 'Panel 2',
|
||||
});
|
||||
|
||||
@ -367,14 +344,10 @@ describe('paneviewComponent', () => {
|
||||
});
|
||||
|
||||
test('panel is disposed of when removed', () => {
|
||||
const paneview = new PaneviewComponent(container, {
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
const paneview = new PaneviewComponent({
|
||||
parentElement: container,
|
||||
components: {
|
||||
testPanel: TestPanel,
|
||||
},
|
||||
});
|
||||
|
||||
@ -382,12 +355,12 @@ describe('paneviewComponent', () => {
|
||||
|
||||
paneview.addPanel({
|
||||
id: 'panel1',
|
||||
component: 'default',
|
||||
component: 'testPanel',
|
||||
title: 'Panel 1',
|
||||
});
|
||||
paneview.addPanel({
|
||||
id: 'panel2',
|
||||
component: 'default',
|
||||
component: 'testPanel',
|
||||
title: 'Panel 2',
|
||||
});
|
||||
|
||||
@ -404,14 +377,10 @@ describe('paneviewComponent', () => {
|
||||
});
|
||||
|
||||
test('panel is disposed of when fromJSON is called', () => {
|
||||
const paneview = new PaneviewComponent(container, {
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
const paneview = new PaneviewComponent({
|
||||
parentElement: container,
|
||||
components: {
|
||||
testPanel: TestPanel,
|
||||
},
|
||||
});
|
||||
|
||||
@ -419,12 +388,12 @@ describe('paneviewComponent', () => {
|
||||
|
||||
paneview.addPanel({
|
||||
id: 'panel1',
|
||||
component: 'default',
|
||||
component: 'testPanel',
|
||||
title: 'Panel 1',
|
||||
});
|
||||
paneview.addPanel({
|
||||
id: 'panel2',
|
||||
component: 'default',
|
||||
component: 'testPanel',
|
||||
title: 'Panel 2',
|
||||
});
|
||||
|
||||
@ -441,14 +410,10 @@ describe('paneviewComponent', () => {
|
||||
});
|
||||
|
||||
test('that fromJSON layouts are resized to the current dimensions', async () => {
|
||||
const paneview = new PaneviewComponent(container, {
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
const paneview = new PaneviewComponent({
|
||||
parentElement: container,
|
||||
components: {
|
||||
testPanel: TestPanel,
|
||||
},
|
||||
});
|
||||
|
||||
@ -461,17 +426,16 @@ describe('paneviewComponent', () => {
|
||||
size: 1,
|
||||
data: {
|
||||
id: 'panel1',
|
||||
component: 'default',
|
||||
component: 'testPanel',
|
||||
title: 'Panel 1',
|
||||
},
|
||||
minimumSize: 100,
|
||||
expanded: true,
|
||||
},
|
||||
{
|
||||
size: 2,
|
||||
data: {
|
||||
id: 'panel2',
|
||||
component: 'default',
|
||||
component: 'testPanel',
|
||||
title: 'Panel 2',
|
||||
},
|
||||
expanded: true,
|
||||
@ -480,7 +444,7 @@ describe('paneviewComponent', () => {
|
||||
size: 3,
|
||||
data: {
|
||||
id: 'panel3',
|
||||
component: 'default',
|
||||
component: 'testPanel',
|
||||
title: 'Panel 3',
|
||||
},
|
||||
expanded: true,
|
||||
@ -496,124 +460,33 @@ describe('paneviewComponent', () => {
|
||||
size: 122,
|
||||
data: {
|
||||
id: 'panel1',
|
||||
component: 'default',
|
||||
component: 'testPanel',
|
||||
title: 'Panel 1',
|
||||
},
|
||||
expanded: true,
|
||||
minimumSize: 100,
|
||||
headerSize: 22,
|
||||
},
|
||||
{
|
||||
size: 22,
|
||||
size: 122,
|
||||
data: {
|
||||
id: 'panel2',
|
||||
component: 'default',
|
||||
component: 'testPanel',
|
||||
title: 'Panel 2',
|
||||
},
|
||||
expanded: true,
|
||||
headerSize: 22,
|
||||
minimumSize: 100,
|
||||
},
|
||||
{
|
||||
size: 456,
|
||||
size: 356,
|
||||
data: {
|
||||
id: 'panel3',
|
||||
component: 'default',
|
||||
component: 'testPanel',
|
||||
title: 'Panel 3',
|
||||
},
|
||||
expanded: true,
|
||||
headerSize: 22,
|
||||
minimumSize: 100,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test('that disableAutoResizing is false by default', () => {
|
||||
const paneview = new PaneviewComponent(container, {
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
expect(paneview.disableResizing).toBeFalsy();
|
||||
});
|
||||
|
||||
test('that disableAutoResizing can be enabled', () => {
|
||||
const paneview = new PaneviewComponent(container, {
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
disableAutoResizing: true,
|
||||
});
|
||||
|
||||
expect(paneview.disableResizing).toBeTruthy();
|
||||
});
|
||||
|
||||
test('that setVisible toggles visiblity', () => {
|
||||
const paneview = new PaneviewComponent(container, {
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
disableAutoResizing: true,
|
||||
});
|
||||
|
||||
paneview.layout(1000, 1000);
|
||||
|
||||
const panel1 = paneview.addPanel({
|
||||
id: 'panel1',
|
||||
component: 'default',
|
||||
title: 'panel1',
|
||||
});
|
||||
const panel2 = paneview.addPanel({
|
||||
id: 'panel2',
|
||||
component: 'default',
|
||||
title: 'panel2',
|
||||
});
|
||||
|
||||
expect(panel1.api.isVisible).toBeTruthy();
|
||||
expect(panel2.api.isVisible).toBeTruthy();
|
||||
|
||||
panel1.api.setVisible(false);
|
||||
expect(panel1.api.isVisible).toBeFalsy();
|
||||
expect(panel2.api.isVisible).toBeTruthy();
|
||||
|
||||
panel1.api.setVisible(true);
|
||||
expect(panel1.api.isVisible).toBeTruthy();
|
||||
expect(panel2.api.isVisible).toBeTruthy();
|
||||
});
|
||||
|
||||
test('update className', () => {
|
||||
const paneview = new PaneviewComponent(container, {
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
disableAutoResizing: true,
|
||||
className: 'test-a test-b',
|
||||
});
|
||||
|
||||
expect(paneview.element.className).toBe('test-a test-b');
|
||||
|
||||
paneview.updateOptions({ className: 'test-b test-c' });
|
||||
|
||||
expect(paneview.element.className).toBe('test-b test-c');
|
||||
});
|
||||
});
|
||||
|
@ -96,7 +96,7 @@ describe('splitview', () => {
|
||||
expect(splitview.orientation).toBe(Orientation.HORIZONTAL);
|
||||
|
||||
const viewQuery = container.querySelectorAll(
|
||||
'.dv-split-view-container dv-horizontal'
|
||||
'.split-view-container horizontal'
|
||||
);
|
||||
expect(viewQuery).toBeTruthy();
|
||||
|
||||
@ -111,7 +111,7 @@ describe('splitview', () => {
|
||||
expect(splitview.orientation).toBe(Orientation.VERTICAL);
|
||||
|
||||
const viewQuery = container.querySelectorAll(
|
||||
'.dv-split-view-container dv-vertical'
|
||||
'.split-view-container vertical'
|
||||
);
|
||||
expect(viewQuery).toBeTruthy();
|
||||
|
||||
@ -128,48 +128,48 @@ describe('splitview', () => {
|
||||
splitview.addView(new Testview(50, 50));
|
||||
|
||||
let viewQuery = container.querySelectorAll(
|
||||
'.dv-split-view-container > .dv-view-container > .dv-view'
|
||||
'.split-view-container > .view-container > .view'
|
||||
);
|
||||
expect(viewQuery.length).toBe(3);
|
||||
|
||||
let sashQuery = container.querySelectorAll(
|
||||
'.dv-split-view-container > .dv-sash-container > .dv-sash'
|
||||
'.split-view-container > .sash-container > .sash'
|
||||
);
|
||||
expect(sashQuery.length).toBe(2);
|
||||
|
||||
splitview.removeView(2);
|
||||
|
||||
viewQuery = container.querySelectorAll(
|
||||
'.dv-split-view-container > .dv-view-container > .dv-view'
|
||||
'.split-view-container > .view-container > .view'
|
||||
);
|
||||
expect(viewQuery.length).toBe(2);
|
||||
|
||||
sashQuery = container.querySelectorAll(
|
||||
'.dv-split-view-container > .dv-sash-container > .dv-sash'
|
||||
'.split-view-container > .sash-container > .sash'
|
||||
);
|
||||
expect(sashQuery.length).toBe(1);
|
||||
|
||||
splitview.removeView(0);
|
||||
|
||||
viewQuery = container.querySelectorAll(
|
||||
'.dv-split-view-container > .dv-view-container > .dv-view'
|
||||
'.split-view-container > .view-container > .view'
|
||||
);
|
||||
expect(viewQuery.length).toBe(1);
|
||||
|
||||
sashQuery = container.querySelectorAll(
|
||||
'.dv-split-view-container > .dv-sash-container > .dv-sash'
|
||||
'.split-view-container > .sash-container > .sash'
|
||||
);
|
||||
expect(sashQuery.length).toBe(0);
|
||||
|
||||
splitview.removeView(0);
|
||||
|
||||
viewQuery = container.querySelectorAll(
|
||||
'.dv-split-view-container > .dv-view-container > .dv-view'
|
||||
'.split-view-container > .view-container > .view'
|
||||
);
|
||||
expect(viewQuery.length).toBe(0);
|
||||
|
||||
sashQuery = container.querySelectorAll(
|
||||
'.dv-split-view-container > .dv-sash-container > .dv-sash'
|
||||
'.split-view-container > .sash-container > .sash'
|
||||
);
|
||||
expect(sashQuery.length).toBe(0);
|
||||
|
||||
@ -188,14 +188,14 @@ describe('splitview', () => {
|
||||
splitview.addView(view2);
|
||||
|
||||
let viewQuery = container.querySelectorAll(
|
||||
'.dv-split-view-container > .dv-view-container > .dv-view.visible'
|
||||
'.split-view-container > .view-container > .view.visible'
|
||||
);
|
||||
expect(viewQuery.length).toBe(2);
|
||||
|
||||
splitview.setViewVisible(1, false);
|
||||
|
||||
viewQuery = container.querySelectorAll(
|
||||
'.dv-split-view-container > .dv-view-container > .dv-view.visible'
|
||||
'.split-view-container > .view-container > .view.visible'
|
||||
);
|
||||
expect(viewQuery.length).toBe(1);
|
||||
|
||||
@ -619,7 +619,7 @@ describe('splitview', () => {
|
||||
);
|
||||
|
||||
const sashElement = container
|
||||
.getElementsByClassName('dv-sash')
|
||||
.getElementsByClassName('sash')
|
||||
.item(0) as HTMLElement;
|
||||
|
||||
// validate the expected state before drag
|
||||
@ -676,226 +676,4 @@ describe('splitview', () => {
|
||||
expect(addEventListenerSpy).toBeCalledTimes(3);
|
||||
expect(removeEventListenerSpy).toBeCalledTimes(3);
|
||||
});
|
||||
|
||||
test('setViewVisible', () => {
|
||||
const splitview = new Splitview(container, {
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
proportionalLayout: false,
|
||||
});
|
||||
splitview.layout(900, 500);
|
||||
|
||||
const view1 = new Testview(0, 1000);
|
||||
const view2 = new Testview(0, 1000);
|
||||
const view3 = new Testview(0, 1000);
|
||||
|
||||
splitview.addView(view1);
|
||||
splitview.addView(view2);
|
||||
splitview.addView(view3);
|
||||
|
||||
expect([view1.size, view2.size, view3.size]).toEqual([300, 300, 300]);
|
||||
|
||||
splitview.setViewVisible(0, false);
|
||||
expect([view1.size, view2.size, view3.size]).toEqual([0, 300, 600]);
|
||||
|
||||
splitview.setViewVisible(0, true);
|
||||
expect([view1.size, view2.size, view3.size]).toEqual([300, 300, 300]);
|
||||
});
|
||||
|
||||
test('setViewVisible with one view having high layout priority', () => {
|
||||
const splitview = new Splitview(container, {
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
proportionalLayout: false,
|
||||
});
|
||||
splitview.layout(900, 500);
|
||||
|
||||
const view1 = new Testview(0, 1000);
|
||||
const view2 = new Testview(0, 1000, LayoutPriority.High);
|
||||
const view3 = new Testview(0, 1000);
|
||||
|
||||
splitview.addView(view1);
|
||||
splitview.addView(view2);
|
||||
splitview.addView(view3);
|
||||
|
||||
expect([view1.size, view2.size, view3.size]).toEqual([300, 300, 300]);
|
||||
|
||||
splitview.setViewVisible(0, false);
|
||||
expect([view1.size, view2.size, view3.size]).toEqual([0, 600, 300]);
|
||||
|
||||
splitview.setViewVisible(0, true);
|
||||
expect([view1.size, view2.size, view3.size]).toEqual([300, 300, 300]);
|
||||
});
|
||||
|
||||
test('set view size', () => {
|
||||
const splitview = new Splitview(container, {
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
proportionalLayout: false,
|
||||
});
|
||||
splitview.layout(900, 500);
|
||||
|
||||
const view1 = new Testview(0, 1000);
|
||||
const view2 = new Testview(0, 1000);
|
||||
const view3 = new Testview(0, 1000);
|
||||
|
||||
splitview.addView(view1);
|
||||
splitview.addView(view2);
|
||||
splitview.addView(view3);
|
||||
|
||||
expect([view1.size, view2.size, view3.size]).toEqual([300, 300, 300]);
|
||||
|
||||
view1.fireChangeEvent({ size: 0 });
|
||||
expect([view1.size, view2.size, view3.size]).toEqual([0, 300, 600]);
|
||||
|
||||
view1.fireChangeEvent({ size: 300 });
|
||||
expect([view1.size, view2.size, view3.size]).toEqual([300, 300, 300]);
|
||||
});
|
||||
|
||||
test('set view size with one view having high layout priority', () => {
|
||||
const splitview = new Splitview(container, {
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
proportionalLayout: false,
|
||||
});
|
||||
splitview.layout(900, 500);
|
||||
|
||||
const view1 = new Testview(0, 1000);
|
||||
const view2 = new Testview(0, 1000, LayoutPriority.High);
|
||||
const view3 = new Testview(0, 1000);
|
||||
|
||||
splitview.addView(view1);
|
||||
splitview.addView(view2);
|
||||
splitview.addView(view3);
|
||||
|
||||
expect([view1.size, view2.size, view3.size]).toEqual([300, 300, 300]);
|
||||
|
||||
view1.fireChangeEvent({ size: 0 });
|
||||
expect([view1.size, view2.size, view3.size]).toEqual([0, 600, 300]);
|
||||
|
||||
view1.fireChangeEvent({ size: 300 });
|
||||
expect([view1.size, view2.size, view3.size]).toEqual([300, 300, 300]);
|
||||
});
|
||||
|
||||
test('that margins are applied to view sizing', () => {
|
||||
const splitview = new Splitview(container, {
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
proportionalLayout: false,
|
||||
margin: 24,
|
||||
});
|
||||
splitview.layout(924, 500);
|
||||
|
||||
const view1 = new Testview(0, 1000);
|
||||
const view2 = new Testview(0, 1000);
|
||||
const view3 = new Testview(0, 1000);
|
||||
const view4 = new Testview(0, 1000);
|
||||
|
||||
splitview.addView(view1);
|
||||
expect([view1.size]).toEqual([924]);
|
||||
|
||||
splitview.addView(view2);
|
||||
expect([view1.size, view2.size]).toEqual([450, 450]); // 450 + 24 + 450 = 924
|
||||
|
||||
splitview.addView(view3);
|
||||
expect([view1.size, view2.size, view3.size]).toEqual([292, 292, 292]); // 292 + 24 + 292 + 24 + 292 = 924
|
||||
|
||||
splitview.addView(view4);
|
||||
expect([view1.size, view2.size, view3.size, view4.size]).toEqual([
|
||||
213, 213, 213, 213,
|
||||
]); // 213 + 24 + 213 + 24 + 213 + 24 + 213 = 924
|
||||
|
||||
let viewQuery = Array.from(
|
||||
container
|
||||
.querySelectorAll(
|
||||
'.dv-split-view-container > .dv-view-container > .dv-view'
|
||||
)
|
||||
.entries()
|
||||
)
|
||||
.map(([i, e]) => e as HTMLElement)
|
||||
.map((e) => ({
|
||||
left: e.style.left,
|
||||
top: e.style.top,
|
||||
height: e.style.height,
|
||||
width: e.style.width,
|
||||
}));
|
||||
|
||||
let sashQuery = Array.from(
|
||||
container
|
||||
.querySelectorAll(
|
||||
'.dv-split-view-container > .dv-sash-container > .dv-sash'
|
||||
)
|
||||
.entries()
|
||||
)
|
||||
.map(([i, e]) => e as HTMLElement)
|
||||
.map((e) => ({
|
||||
left: e.style.left,
|
||||
top: e.style.top,
|
||||
}));
|
||||
|
||||
// check HTMLElement positions since these are the ones that really matter
|
||||
|
||||
expect(viewQuery).toEqual([
|
||||
{ left: '0px', top: '', width: '213px', height: '' },
|
||||
// 213 + 24 = 237
|
||||
{ left: '237px', top: '', width: '213px', height: '' },
|
||||
// 237 + 213 + 24 = 474
|
||||
{ left: '474px', top: '', width: '213px', height: '' },
|
||||
// 474 + 213 + 24 = 474
|
||||
{ left: '711px', top: '', width: '213px', height: '' },
|
||||
// 711 + 213 = 924
|
||||
]);
|
||||
|
||||
// 924 / 4 = 231 view size
|
||||
// 231 - (24*3/4) = 213 margin adjusted view size
|
||||
// 213 - 4/2 + 24/2 = 223
|
||||
expect(sashQuery).toEqual([
|
||||
// 213 - 4/2 + 24/2 = 223
|
||||
{ left: '223px', top: '0px' },
|
||||
// 213 + 24 + 213 = 450
|
||||
// 450 - 4/2 + 24/2 = 460
|
||||
{ left: '460px', top: '0px' },
|
||||
// 213 + 24 + 213 + 24 + 213 = 687
|
||||
// 687 - 4/2 + 24/2 = 697
|
||||
{ left: '697px', top: '0px' },
|
||||
]);
|
||||
|
||||
splitview.setViewVisible(0, false);
|
||||
|
||||
viewQuery = Array.from(
|
||||
container
|
||||
.querySelectorAll(
|
||||
'.dv-split-view-container > .dv-view-container > .dv-view'
|
||||
)
|
||||
.entries()
|
||||
)
|
||||
.map(([i, e]) => e as HTMLElement)
|
||||
.map((e) => ({
|
||||
left: e.style.left,
|
||||
top: e.style.top,
|
||||
height: e.style.height,
|
||||
width: e.style.width,
|
||||
}));
|
||||
|
||||
sashQuery = Array.from(
|
||||
container
|
||||
.querySelectorAll(
|
||||
'.dv-split-view-container > .dv-sash-container > .dv-sash'
|
||||
)
|
||||
.entries()
|
||||
)
|
||||
.map(([i, e]) => e as HTMLElement)
|
||||
.map((e) => ({
|
||||
left: e.style.left,
|
||||
top: e.style.top,
|
||||
}));
|
||||
|
||||
expect(viewQuery).toEqual([
|
||||
{ left: '0px', top: '', width: '0px', height: '' },
|
||||
{ left: '0px', top: '', width: '215px', height: '' },
|
||||
{ left: '239px', top: '', width: '215px', height: '' },
|
||||
{ left: '478px', top: '', width: '446px', height: '' },
|
||||
]);
|
||||
|
||||
expect(sashQuery).toEqual([
|
||||
{ left: '0px', top: '0px' },
|
||||
{ left: '225px', top: '0px' },
|
||||
{ left: '464px', top: '0px' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
@ -26,52 +26,25 @@ describe('componentSplitview', () => {
|
||||
container.className = 'container';
|
||||
});
|
||||
|
||||
test('that the container is not removed when grid is disposed', () => {
|
||||
const root = document.createElement('div');
|
||||
const container = document.createElement('div');
|
||||
root.appendChild(container);
|
||||
|
||||
const splitview = new SplitviewComponent(container, {
|
||||
orientation: Orientation.VERTICAL,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
splitview.dispose();
|
||||
|
||||
expect(container.parentElement).toBe(root);
|
||||
expect(container.children.length).toBe(0);
|
||||
});
|
||||
|
||||
test('event leakage', () => {
|
||||
Emitter.setLeakageMonitorEnabled(true);
|
||||
|
||||
const splitview = new SplitviewComponent(container, {
|
||||
const splitview = new SplitviewComponent({
|
||||
parentElement: container,
|
||||
orientation: Orientation.VERTICAL,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
components: {
|
||||
testPanel: TestPanel,
|
||||
},
|
||||
});
|
||||
splitview.layout(600, 400);
|
||||
|
||||
const panel1 = splitview.addPanel({
|
||||
id: 'panel1',
|
||||
component: 'default',
|
||||
component: 'testPanel',
|
||||
});
|
||||
const panel2 = splitview.addPanel({
|
||||
id: 'panel2',
|
||||
component: 'default',
|
||||
component: 'testPanel',
|
||||
});
|
||||
|
||||
splitview.movePanel(0, 1);
|
||||
@ -93,22 +66,18 @@ describe('componentSplitview', () => {
|
||||
});
|
||||
|
||||
test('remove panel', () => {
|
||||
const splitview = new SplitviewComponent(container, {
|
||||
const splitview = new SplitviewComponent({
|
||||
parentElement: container,
|
||||
orientation: Orientation.VERTICAL,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
components: {
|
||||
testPanel: TestPanel,
|
||||
},
|
||||
});
|
||||
splitview.layout(600, 400);
|
||||
|
||||
splitview.addPanel({ id: 'panel1', component: 'default' });
|
||||
splitview.addPanel({ id: 'panel2', component: 'default' });
|
||||
splitview.addPanel({ id: 'panel3', component: 'default' });
|
||||
splitview.addPanel({ id: 'panel1', component: 'testPanel' });
|
||||
splitview.addPanel({ id: 'panel2', component: 'testPanel' });
|
||||
splitview.addPanel({ id: 'panel3', component: 'testPanel' });
|
||||
|
||||
const panel1 = splitview.getPanel('panel1')!;
|
||||
const panel2 = splitview.getPanel('panel2')!;
|
||||
@ -133,15 +102,11 @@ describe('componentSplitview', () => {
|
||||
});
|
||||
|
||||
test('horizontal dimensions', () => {
|
||||
const splitview = new SplitviewComponent(container, {
|
||||
const splitview = new SplitviewComponent({
|
||||
parentElement: container,
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
components: {
|
||||
testPanel: TestPanel,
|
||||
},
|
||||
});
|
||||
splitview.layout(600, 400);
|
||||
@ -151,15 +116,11 @@ describe('componentSplitview', () => {
|
||||
});
|
||||
|
||||
test('vertical dimensions', () => {
|
||||
const splitview = new SplitviewComponent(container, {
|
||||
const splitview = new SplitviewComponent({
|
||||
parentElement: container,
|
||||
orientation: Orientation.VERTICAL,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
components: {
|
||||
testPanel: TestPanel,
|
||||
},
|
||||
});
|
||||
splitview.layout(600, 400);
|
||||
@ -169,22 +130,18 @@ describe('componentSplitview', () => {
|
||||
});
|
||||
|
||||
test('api resize', () => {
|
||||
const splitview = new SplitviewComponent(container, {
|
||||
const splitview = new SplitviewComponent({
|
||||
parentElement: container,
|
||||
orientation: Orientation.VERTICAL,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
components: {
|
||||
testPanel: TestPanel,
|
||||
},
|
||||
});
|
||||
|
||||
splitview.layout(400, 600);
|
||||
splitview.addPanel({ id: 'panel1', component: 'default' });
|
||||
splitview.addPanel({ id: 'panel2', component: 'default' });
|
||||
splitview.addPanel({ id: 'panel3', component: 'default' });
|
||||
splitview.addPanel({ id: 'panel1', component: 'testPanel' });
|
||||
splitview.addPanel({ id: 'panel2', component: 'testPanel' });
|
||||
splitview.addPanel({ id: 'panel3', component: 'testPanel' });
|
||||
|
||||
const panel1 = splitview.getPanel('panel1')!;
|
||||
const panel2 = splitview.getPanel('panel2')!;
|
||||
@ -226,20 +183,16 @@ describe('componentSplitview', () => {
|
||||
});
|
||||
|
||||
test('api', () => {
|
||||
const splitview = new SplitviewComponent(container, {
|
||||
const splitview = new SplitviewComponent({
|
||||
parentElement: container,
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
components: {
|
||||
testPanel: TestPanel,
|
||||
},
|
||||
});
|
||||
|
||||
splitview.layout(600, 400);
|
||||
splitview.addPanel({ id: 'panel1', component: 'default' });
|
||||
splitview.addPanel({ id: 'panel1', component: 'testPanel' });
|
||||
|
||||
const panel1 = splitview.getPanel('panel1');
|
||||
|
||||
@ -250,7 +203,7 @@ describe('componentSplitview', () => {
|
||||
// expect(panel1?.api.isFocused).toBeFalsy();
|
||||
expect(panel1!.api.isVisible).toBeTruthy();
|
||||
|
||||
splitview.addPanel({ id: 'panel2', component: 'default' });
|
||||
splitview.addPanel({ id: 'panel2', component: 'testPanel' });
|
||||
|
||||
const panel2 = splitview.getPanel('panel2');
|
||||
|
||||
@ -272,22 +225,18 @@ describe('componentSplitview', () => {
|
||||
test('vertical panels', () => {
|
||||
const disposables = new CompositeDisposable();
|
||||
|
||||
const splitview = new SplitviewComponent(container, {
|
||||
const splitview = new SplitviewComponent({
|
||||
parentElement: container,
|
||||
orientation: Orientation.VERTICAL,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
components: {
|
||||
testPanel: TestPanel,
|
||||
},
|
||||
});
|
||||
|
||||
splitview.layout(300, 200);
|
||||
splitview.layout(600, 400);
|
||||
|
||||
splitview.addPanel({ id: 'panel1', component: 'default' });
|
||||
splitview.addPanel({ id: 'panel2', component: 'default' });
|
||||
splitview.addPanel({ id: 'panel1', component: 'testPanel' });
|
||||
splitview.addPanel({ id: 'panel2', component: 'testPanel' });
|
||||
|
||||
const panel1 = splitview.getPanel('panel1') as SplitviewPanel;
|
||||
const panel2 = splitview.getPanel('panel2') as SplitviewPanel;
|
||||
@ -306,8 +255,6 @@ describe('componentSplitview', () => {
|
||||
})
|
||||
);
|
||||
|
||||
splitview.layout(600, 400);
|
||||
|
||||
expect(panel1Dimensions).toEqual({ width: 600, height: 200 });
|
||||
expect(panel2Dimensions).toEqual({ width: 600, height: 200 });
|
||||
|
||||
@ -328,22 +275,18 @@ describe('componentSplitview', () => {
|
||||
test('horizontal panels', () => {
|
||||
const disposables = new CompositeDisposable();
|
||||
|
||||
const splitview = new SplitviewComponent(container, {
|
||||
const splitview = new SplitviewComponent({
|
||||
parentElement: container,
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
components: {
|
||||
testPanel: TestPanel,
|
||||
},
|
||||
});
|
||||
|
||||
splitview.layout(300, 200);
|
||||
splitview.layout(600, 400);
|
||||
|
||||
splitview.addPanel({ id: 'panel1', component: 'default' });
|
||||
splitview.addPanel({ id: 'panel2', component: 'default' });
|
||||
splitview.addPanel({ id: 'panel1', component: 'testPanel' });
|
||||
splitview.addPanel({ id: 'panel2', component: 'testPanel' });
|
||||
|
||||
const panel1 = splitview.getPanel('panel1') as SplitviewPanel;
|
||||
const panel2 = splitview.getPanel('panel2') as SplitviewPanel;
|
||||
@ -362,8 +305,6 @@ describe('componentSplitview', () => {
|
||||
})
|
||||
);
|
||||
|
||||
splitview.layout(600, 400);
|
||||
|
||||
expect(panel1Dimensions).toEqual({ width: 300, height: 400 });
|
||||
expect(panel2Dimensions).toEqual({ width: 300, height: 400 });
|
||||
|
||||
@ -382,63 +323,51 @@ describe('componentSplitview', () => {
|
||||
});
|
||||
|
||||
test('serialization', () => {
|
||||
const splitview = new SplitviewComponent(container, {
|
||||
const splitview = new SplitviewComponent({
|
||||
parentElement: container,
|
||||
orientation: Orientation.VERTICAL,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
components: {
|
||||
testPanel: TestPanel,
|
||||
},
|
||||
});
|
||||
splitview.layout(400, 6);
|
||||
|
||||
expect(
|
||||
container.querySelectorAll('.dv-split-view-container').length
|
||||
).toBe(1);
|
||||
|
||||
splitview.fromJSON({
|
||||
views: [
|
||||
{
|
||||
size: 1,
|
||||
data: { id: 'panel1', component: 'default' },
|
||||
data: { id: 'panel1', component: 'testPanel' },
|
||||
snap: false,
|
||||
},
|
||||
{
|
||||
size: 2,
|
||||
data: { id: 'panel2', component: 'default' },
|
||||
data: { id: 'panel2', component: 'testPanel' },
|
||||
snap: true,
|
||||
},
|
||||
{ size: 3, data: { id: 'panel3', component: 'default' } },
|
||||
{ size: 3, data: { id: 'panel3', component: 'testPanel' } },
|
||||
],
|
||||
size: 6,
|
||||
orientation: Orientation.VERTICAL,
|
||||
activeView: 'panel1',
|
||||
});
|
||||
|
||||
expect(
|
||||
container.querySelectorAll('.dv-split-view-container').length
|
||||
).toBe(1);
|
||||
|
||||
expect(splitview.length).toBe(3);
|
||||
|
||||
expect(JSON.parse(JSON.stringify(splitview.toJSON()))).toEqual({
|
||||
views: [
|
||||
{
|
||||
size: 1,
|
||||
data: { id: 'panel1', component: 'default' },
|
||||
data: { id: 'panel1', component: 'testPanel' },
|
||||
snap: false,
|
||||
},
|
||||
{
|
||||
size: 2,
|
||||
data: { id: 'panel2', component: 'default' },
|
||||
data: { id: 'panel2', component: 'testPanel' },
|
||||
snap: true,
|
||||
},
|
||||
{
|
||||
size: 3,
|
||||
data: { id: 'panel3', component: 'default' },
|
||||
data: { id: 'panel3', component: 'testPanel' },
|
||||
snap: false,
|
||||
},
|
||||
],
|
||||
@ -449,15 +378,11 @@ describe('componentSplitview', () => {
|
||||
});
|
||||
|
||||
test('toJSON shouldnt fire any layout events', () => {
|
||||
const splitview = new SplitviewComponent(container, {
|
||||
const splitview = new SplitviewComponent({
|
||||
parentElement: container,
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
components: {
|
||||
testPanel: TestPanel,
|
||||
},
|
||||
});
|
||||
|
||||
@ -465,11 +390,11 @@ describe('componentSplitview', () => {
|
||||
|
||||
splitview.addPanel({
|
||||
id: 'panel1',
|
||||
component: 'default',
|
||||
component: 'testPanel',
|
||||
});
|
||||
splitview.addPanel({
|
||||
id: 'panel2',
|
||||
component: 'default',
|
||||
component: 'testPanel',
|
||||
});
|
||||
|
||||
const disposable = splitview.onDidLayoutChange(() => {
|
||||
@ -482,16 +407,41 @@ describe('componentSplitview', () => {
|
||||
disposable.dispose();
|
||||
});
|
||||
|
||||
test('panel is disposed of when component is disposed', () => {
|
||||
const splitview = new SplitviewComponent(container, {
|
||||
test('dispose of splitviewComponent', () => {
|
||||
expect(container.childNodes.length).toBe(0);
|
||||
|
||||
const splitview = new SplitviewComponent({
|
||||
parentElement: container,
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
components: {
|
||||
testPanel: TestPanel,
|
||||
},
|
||||
});
|
||||
|
||||
splitview.layout(1000, 1000);
|
||||
|
||||
splitview.addPanel({
|
||||
id: 'panel1',
|
||||
component: 'testPanel',
|
||||
});
|
||||
splitview.addPanel({
|
||||
id: 'panel2',
|
||||
component: 'testPanel',
|
||||
});
|
||||
|
||||
expect(container.childNodes.length).toBeGreaterThan(0);
|
||||
|
||||
splitview.dispose();
|
||||
|
||||
expect(container.childNodes.length).toBe(0);
|
||||
});
|
||||
|
||||
test('panel is disposed of when component is disposed', () => {
|
||||
const splitview = new SplitviewComponent({
|
||||
parentElement: container,
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
components: {
|
||||
default: TestPanel,
|
||||
},
|
||||
});
|
||||
|
||||
@ -519,15 +469,11 @@ describe('componentSplitview', () => {
|
||||
});
|
||||
|
||||
test('panel is disposed of when removed', () => {
|
||||
const splitview = new SplitviewComponent(container, {
|
||||
const splitview = new SplitviewComponent({
|
||||
parentElement: container,
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
components: {
|
||||
default: TestPanel,
|
||||
},
|
||||
});
|
||||
|
||||
@ -555,15 +501,11 @@ describe('componentSplitview', () => {
|
||||
});
|
||||
|
||||
test('panel is disposed of when fromJSON is called', () => {
|
||||
const splitview = new SplitviewComponent(container, {
|
||||
const splitview = new SplitviewComponent({
|
||||
parentElement: container,
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
components: {
|
||||
default: TestPanel,
|
||||
},
|
||||
});
|
||||
|
||||
@ -595,15 +537,11 @@ describe('componentSplitview', () => {
|
||||
});
|
||||
|
||||
test('that fromJSON layouts are resized to the current dimensions', async () => {
|
||||
const splitview = new SplitviewComponent(container, {
|
||||
const splitview = new SplitviewComponent({
|
||||
parentElement: container,
|
||||
orientation: Orientation.VERTICAL,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
components: {
|
||||
testPanel: TestPanel,
|
||||
},
|
||||
});
|
||||
splitview.layout(400, 600);
|
||||
@ -612,15 +550,15 @@ describe('componentSplitview', () => {
|
||||
views: [
|
||||
{
|
||||
size: 1,
|
||||
data: { id: 'panel1', component: 'default' },
|
||||
data: { id: 'panel1', component: 'testPanel' },
|
||||
snap: false,
|
||||
},
|
||||
{
|
||||
size: 2,
|
||||
data: { id: 'panel2', component: 'default' },
|
||||
data: { id: 'panel2', component: 'testPanel' },
|
||||
snap: true,
|
||||
},
|
||||
{ size: 3, data: { id: 'panel3', component: 'default' } },
|
||||
{ size: 3, data: { id: 'panel3', component: 'testPanel' } },
|
||||
],
|
||||
size: 6,
|
||||
orientation: Orientation.VERTICAL,
|
||||
@ -631,17 +569,17 @@ describe('componentSplitview', () => {
|
||||
views: [
|
||||
{
|
||||
size: 100,
|
||||
data: { id: 'panel1', component: 'default' },
|
||||
data: { id: 'panel1', component: 'testPanel' },
|
||||
snap: false,
|
||||
},
|
||||
{
|
||||
size: 200,
|
||||
data: { id: 'panel2', component: 'default' },
|
||||
data: { id: 'panel2', component: 'testPanel' },
|
||||
snap: true,
|
||||
},
|
||||
{
|
||||
size: 300,
|
||||
data: { id: 'panel3', component: 'default' },
|
||||
data: { id: 'panel3', component: 'testPanel' },
|
||||
snap: false,
|
||||
},
|
||||
],
|
||||
@ -650,94 +588,4 @@ describe('componentSplitview', () => {
|
||||
activeView: 'panel1',
|
||||
});
|
||||
});
|
||||
|
||||
test('that disableAutoResizing is false by default', () => {
|
||||
const splitview = new SplitviewComponent(container, {
|
||||
orientation: Orientation.VERTICAL,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
expect(splitview.disableResizing).toBeFalsy();
|
||||
});
|
||||
|
||||
test('that disableAutoResizing can be enabled', () => {
|
||||
const splitview = new SplitviewComponent(container, {
|
||||
orientation: Orientation.VERTICAL,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
disableAutoResizing: true,
|
||||
});
|
||||
|
||||
expect(splitview.disableResizing).toBeTruthy();
|
||||
});
|
||||
|
||||
test('that setVisible toggles visiblity', () => {
|
||||
const splitview = new SplitviewComponent(container, {
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
splitview.layout(1000, 1000);
|
||||
|
||||
const panel1 = splitview.addPanel({
|
||||
id: 'panel1',
|
||||
component: 'default',
|
||||
});
|
||||
const panel2 = splitview.addPanel({
|
||||
id: 'panel2',
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
expect(panel1.api.isVisible).toBeTruthy();
|
||||
expect(panel2.api.isVisible).toBeTruthy();
|
||||
|
||||
panel1.api.setVisible(false);
|
||||
expect(panel1.api.isVisible).toBeFalsy();
|
||||
expect(panel2.api.isVisible).toBeTruthy();
|
||||
|
||||
panel1.api.setVisible(true);
|
||||
expect(panel1.api.isVisible).toBeTruthy();
|
||||
expect(panel2.api.isVisible).toBeTruthy();
|
||||
});
|
||||
|
||||
test('update className', () => {
|
||||
const splitview = new SplitviewComponent(container, {
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
className: 'test-a test-b',
|
||||
});
|
||||
|
||||
expect(splitview.element.className).toBe('test-a test-b');
|
||||
|
||||
splitview.updateOptions({ className: 'test-b test-c' });
|
||||
|
||||
expect(splitview.element.className).toBe('test-b test-c');
|
||||
});
|
||||
});
|
||||
|
@ -1,20 +1,13 @@
|
||||
import {
|
||||
DockviewMaximizedGroupChanged,
|
||||
FloatingGroupOptions,
|
||||
DockviewDropEvent,
|
||||
IDockviewComponent,
|
||||
MovePanelEvent,
|
||||
PopoutGroupChangePositionEvent,
|
||||
PopoutGroupChangeSizeEvent,
|
||||
SerializedDockview,
|
||||
} from '../dockview/dockviewComponent';
|
||||
import {
|
||||
AddGroupOptions,
|
||||
AddPanelOptions,
|
||||
DockviewComponentOptions,
|
||||
DockviewDndOverlayEvent,
|
||||
MovementOptions,
|
||||
} from '../dockview/options';
|
||||
import { Parameters } from '../panel/types';
|
||||
import { Direction } from '../gridview/baseComponentGridview';
|
||||
import {
|
||||
AddComponentOptions,
|
||||
@ -33,6 +26,7 @@ import {
|
||||
AddSplitviewComponentOptions,
|
||||
ISplitviewComponent,
|
||||
SerializedSplitview,
|
||||
SplitviewComponentUpdateOptions,
|
||||
} from '../splitview/splitviewComponent';
|
||||
import { IView, Orientation, Sizing } from '../splitview/splitview';
|
||||
import { ISplitviewPanel } from '../splitview/splitviewPanel';
|
||||
@ -40,25 +34,9 @@ import {
|
||||
DockviewGroupPanel,
|
||||
IDockviewGroupPanel,
|
||||
} from '../dockview/dockviewGroupPanel';
|
||||
import { Event } from '../events';
|
||||
import { Emitter, Event } from '../events';
|
||||
import { IDockviewPanel } from '../dockview/dockviewPanel';
|
||||
import { PaneviewDidDropEvent } from '../paneview/draggablePaneviewPanel';
|
||||
import {
|
||||
GroupDragEvent,
|
||||
TabDragEvent,
|
||||
} from '../dockview/components/titlebar/tabsContainer';
|
||||
import { Box } from '../types';
|
||||
import {
|
||||
DockviewDidDropEvent,
|
||||
DockviewWillDropEvent,
|
||||
WillShowOverlayLocationEvent,
|
||||
} from '../dockview/dockviewGroupPanelModel';
|
||||
import {
|
||||
PaneviewComponentOptions,
|
||||
PaneviewDndOverlayEvent,
|
||||
} from '../paneview/options';
|
||||
import { SplitviewComponentOptions } from '../splitview/options';
|
||||
import { GridviewComponentOptions } from '../gridview/options';
|
||||
import { PaneviewDropEvent } from '../paneview/draggablePaneviewPanel';
|
||||
|
||||
export interface CommonApi<T = any> {
|
||||
readonly height: number;
|
||||
@ -70,413 +48,236 @@ export interface CommonApi<T = any> {
|
||||
fromJSON(data: T): void;
|
||||
toJSON(): T;
|
||||
clear(): void;
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
export class SplitviewApi implements CommonApi<SerializedSplitview> {
|
||||
/**
|
||||
* The minimum size the component can reach where size is measured in the direction of orientation provided.
|
||||
*/
|
||||
get minimumSize(): number {
|
||||
return this.component.minimumSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* The maximum size the component can reach where size is measured in the direction of orientation provided.
|
||||
*/
|
||||
get maximumSize(): number {
|
||||
return this.component.maximumSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Width of the component.
|
||||
*/
|
||||
get height(): number {
|
||||
return this.component.height;
|
||||
}
|
||||
|
||||
get width(): number {
|
||||
return this.component.width;
|
||||
}
|
||||
|
||||
/**
|
||||
* Height of the component.
|
||||
*/
|
||||
get height(): number {
|
||||
return this.component.height;
|
||||
}
|
||||
/**
|
||||
* The current number of panels.
|
||||
*/
|
||||
get length(): number {
|
||||
return this.component.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* The current orientation of the component.
|
||||
*/
|
||||
get orientation(): Orientation {
|
||||
return this.component.orientation;
|
||||
}
|
||||
|
||||
/**
|
||||
* The list of current panels.
|
||||
*/
|
||||
get panels(): ISplitviewPanel[] {
|
||||
return this.component.panels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked after a layout is loaded through the `fromJSON` method.
|
||||
*/
|
||||
get onDidLayoutFromJSON(): Event<void> {
|
||||
return this.component.onDidLayoutFromJSON;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked whenever any aspect of the layout changes.
|
||||
* If listening to this event it may be worth debouncing ouputs.
|
||||
*/
|
||||
get onDidLayoutChange(): Event<void> {
|
||||
return this.component.onDidLayoutChange;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when a view is added.
|
||||
*/
|
||||
get onDidAddView(): Event<IView> {
|
||||
return this.component.onDidAddView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when a view is removed.
|
||||
*/
|
||||
get onDidRemoveView(): Event<IView> {
|
||||
return this.component.onDidRemoveView;
|
||||
}
|
||||
|
||||
constructor(private readonly component: ISplitviewComponent) {}
|
||||
|
||||
/**
|
||||
* Removes an existing panel and optionally provide a `Sizing` method
|
||||
* for the subsequent resize.
|
||||
*/
|
||||
updateOptions(options: SplitviewComponentUpdateOptions): void {
|
||||
this.component.updateOptions(options);
|
||||
}
|
||||
|
||||
removePanel(panel: ISplitviewPanel, sizing?: Sizing): void {
|
||||
this.component.removePanel(panel, sizing);
|
||||
}
|
||||
|
||||
/**
|
||||
* Focus the component.
|
||||
*/
|
||||
focus(): void {
|
||||
this.component.focus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the reference to a panel given it's `string` id.
|
||||
*/
|
||||
getPanel(id: string): ISplitviewPanel | undefined {
|
||||
return this.component.getPanel(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Layout the panel with a width and height.
|
||||
*/
|
||||
layout(width: number, height: number): void {
|
||||
return this.component.layout(width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new panel and return the created instance.
|
||||
*/
|
||||
addPanel<T extends object = Parameters>(
|
||||
options: AddSplitviewComponentOptions<T>
|
||||
): ISplitviewPanel {
|
||||
addPanel(options: AddSplitviewComponentOptions): ISplitviewPanel {
|
||||
return this.component.addPanel(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move a panel given it's current and desired index.
|
||||
*/
|
||||
movePanel(from: number, to: number): void {
|
||||
this.component.movePanel(from, to);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize a layout to built a splitivew.
|
||||
*/
|
||||
fromJSON(data: SerializedSplitview): void {
|
||||
this.component.fromJSON(data);
|
||||
}
|
||||
|
||||
/** Serialize a layout */
|
||||
toJSON(): SerializedSplitview {
|
||||
return this.component.toJSON();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all panels and clear the component.
|
||||
*/
|
||||
clear(): void {
|
||||
this.component.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update configuratable options.
|
||||
*/
|
||||
updateOptions(options: Partial<SplitviewComponentOptions>): void {
|
||||
this.component.updateOptions(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Release resources and teardown component. Do not call when using framework versions of dockview.
|
||||
*/
|
||||
dispose(): void {
|
||||
this.component.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export class PaneviewApi implements CommonApi<SerializedPaneview> {
|
||||
/**
|
||||
* The minimum size the component can reach where size is measured in the direction of orientation provided.
|
||||
*/
|
||||
get minimumSize(): number {
|
||||
return this.component.minimumSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* The maximum size the component can reach where size is measured in the direction of orientation provided.
|
||||
*/
|
||||
get maximumSize(): number {
|
||||
return this.component.maximumSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Width of the component.
|
||||
*/
|
||||
get width(): number {
|
||||
return this.component.width;
|
||||
}
|
||||
|
||||
/**
|
||||
* Height of the component.
|
||||
*/
|
||||
get height(): number {
|
||||
return this.component.height;
|
||||
}
|
||||
|
||||
/**
|
||||
* All panel objects.
|
||||
*/
|
||||
get width(): number {
|
||||
return this.component.width;
|
||||
}
|
||||
|
||||
get panels(): IPaneviewPanel[] {
|
||||
return this.component.panels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when any layout change occures, an aggregation of many events.
|
||||
*/
|
||||
get onDidLayoutChange(): Event<void> {
|
||||
return this.component.onDidLayoutChange;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked after a layout is deserialzied using the `fromJSON` method.
|
||||
*/
|
||||
get onDidLayoutFromJSON(): Event<void> {
|
||||
return this.component.onDidLayoutFromJSON;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when a panel is added. May be called multiple times when moving panels.
|
||||
*/
|
||||
get onDidAddView(): Event<IPaneviewPanel> {
|
||||
return this.component.onDidAddView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when a panel is removed. May be called multiple times when moving panels.
|
||||
*/
|
||||
get onDidRemoveView(): Event<IPaneviewPanel> {
|
||||
return this.component.onDidRemoveView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when a Drag'n'Drop event occurs that the component was unable to handle. Exposed for custom Drag'n'Drop functionality.
|
||||
*/
|
||||
get onDidDrop(): Event<PaneviewDidDropEvent> {
|
||||
return this.component.onDidDrop;
|
||||
}
|
||||
get onDidDrop(): Event<PaneviewDropEvent> {
|
||||
const emitter = new Emitter<PaneviewDropEvent>();
|
||||
|
||||
get onUnhandledDragOverEvent(): Event<PaneviewDndOverlayEvent> {
|
||||
return this.component.onUnhandledDragOverEvent;
|
||||
const disposable = this.component.onDidDrop((e) => {
|
||||
emitter.fire({ ...e, api: this });
|
||||
});
|
||||
|
||||
emitter.dispose = () => {
|
||||
disposable.dispose();
|
||||
emitter.dispose();
|
||||
};
|
||||
|
||||
return emitter.event;
|
||||
}
|
||||
|
||||
constructor(private readonly component: IPaneviewComponent) {}
|
||||
|
||||
/**
|
||||
* Remove a panel given the panel object.
|
||||
*/
|
||||
removePanel(panel: IPaneviewPanel): void {
|
||||
this.component.removePanel(panel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a panel object given a `string` id. May return `undefined`.
|
||||
*/
|
||||
getPanel(id: string): IPaneviewPanel | undefined {
|
||||
return this.component.getPanel(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move a panel given it's current and desired index.
|
||||
*/
|
||||
movePanel(from: number, to: number): void {
|
||||
this.component.movePanel(from, to);
|
||||
}
|
||||
|
||||
/**
|
||||
* Focus the component. Will try to focus an active panel if one exists.
|
||||
*/
|
||||
focus(): void {
|
||||
this.component.focus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Force resize the component to an exact width and height. Read about auto-resizing before using.
|
||||
*/
|
||||
layout(width: number, height: number): void {
|
||||
this.component.layout(width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a panel and return the created object.
|
||||
*/
|
||||
addPanel<T extends object = Parameters>(
|
||||
options: AddPaneviewComponentOptions<T>
|
||||
): IPaneviewPanel {
|
||||
addPanel(options: AddPaneviewComponentOptions): IPaneviewPanel {
|
||||
return this.component.addPanel(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a component from a serialized object.
|
||||
*/
|
||||
fromJSON(data: SerializedPaneview): void {
|
||||
this.component.fromJSON(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a serialized object of the current component.
|
||||
*/
|
||||
toJSON(): SerializedPaneview {
|
||||
return this.component.toJSON();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the component back to an empty and default state.
|
||||
*/
|
||||
clear(): void {
|
||||
this.component.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update configuratable options.
|
||||
*/
|
||||
updateOptions(options: Partial<PaneviewComponentOptions>): void {
|
||||
this.component.updateOptions(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Release resources and teardown component. Do not call when using framework versions of dockview.
|
||||
*/
|
||||
dispose(): void {
|
||||
this.component.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export class GridviewApi implements CommonApi<SerializedGridviewComponent> {
|
||||
/**
|
||||
* Width of the component.
|
||||
*/
|
||||
get width(): number {
|
||||
return this.component.width;
|
||||
}
|
||||
|
||||
/**
|
||||
* Height of the component.
|
||||
*/
|
||||
get height(): number {
|
||||
return this.component.height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimum height of the component.
|
||||
*/
|
||||
get minimumHeight(): number {
|
||||
return this.component.minimumHeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maximum height of the component.
|
||||
*/
|
||||
get maximumHeight(): number {
|
||||
return this.component.maximumHeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimum width of the component.
|
||||
*/
|
||||
get minimumWidth(): number {
|
||||
return this.component.minimumWidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maximum width of the component.
|
||||
*/
|
||||
get maximumWidth(): number {
|
||||
return this.component.maximumWidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when any layout change occures, an aggregation of many events.
|
||||
*/
|
||||
get width(): number {
|
||||
return this.component.width;
|
||||
}
|
||||
|
||||
get height(): number {
|
||||
return this.component.height;
|
||||
}
|
||||
|
||||
get onDidLayoutChange(): Event<void> {
|
||||
return this.component.onDidLayoutChange;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when a panel is added. May be called multiple times when moving panels.
|
||||
*/
|
||||
get onDidAddPanel(): Event<IGridviewPanel> {
|
||||
return this.component.onDidAddGroup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when a panel is removed. May be called multiple times when moving panels.
|
||||
*/
|
||||
get onDidRemovePanel(): Event<IGridviewPanel> {
|
||||
return this.component.onDidRemoveGroup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when the active panel changes. May be undefined if no panel is active.
|
||||
*/
|
||||
get onDidActivePanelChange(): Event<IGridviewPanel | undefined> {
|
||||
return this.component.onDidActiveGroupChange;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked after a layout is deserialzied using the `fromJSON` method.
|
||||
*/
|
||||
get onDidLayoutFromJSON(): Event<void> {
|
||||
return this.component.onDidLayoutFromJSON;
|
||||
}
|
||||
|
||||
/**
|
||||
* All panel objects.
|
||||
*/
|
||||
get panels(): IGridviewPanel[] {
|
||||
return this.component.groups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Current orientation. Can be changed after initialization.
|
||||
*/
|
||||
get orientation(): Orientation {
|
||||
return this.component.orientation;
|
||||
}
|
||||
@ -487,39 +288,22 @@ export class GridviewApi implements CommonApi<SerializedGridviewComponent> {
|
||||
|
||||
constructor(private readonly component: IGridviewComponent) {}
|
||||
|
||||
/**
|
||||
* Focus the component. Will try to focus an active panel if one exists.
|
||||
*/
|
||||
focus(): void {
|
||||
this.component.focus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Force resize the component to an exact width and height. Read about auto-resizing before using.
|
||||
*/
|
||||
layout(width: number, height: number, force = false): void {
|
||||
this.component.layout(width, height, force);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a panel and return the created object.
|
||||
*/
|
||||
addPanel<T extends object = Parameters>(
|
||||
options: AddComponentOptions<T>
|
||||
): IGridviewPanel {
|
||||
addPanel(options: AddComponentOptions): IGridviewPanel {
|
||||
return this.component.addPanel(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a panel given the panel object.
|
||||
*/
|
||||
removePanel(panel: IGridviewPanel, sizing?: Sizing): void {
|
||||
this.component.removePanel(panel, sizing);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move a panel in a particular direction relative to another panel.
|
||||
*/
|
||||
movePanel(
|
||||
panel: IGridviewPanel,
|
||||
options: { direction: Direction; reference: string; size?: number }
|
||||
@ -527,407 +311,174 @@ export class GridviewApi implements CommonApi<SerializedGridviewComponent> {
|
||||
this.component.movePanel(panel, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a panel object given a `string` id. May return `undefined`.
|
||||
*/
|
||||
getPanel(id: string): IGridviewPanel | undefined {
|
||||
return this.component.getPanel(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a component from a serialized object.
|
||||
*/
|
||||
fromJSON(data: SerializedGridviewComponent): void {
|
||||
return this.component.fromJSON(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a serialized object of the current component.
|
||||
*/
|
||||
toJSON(): SerializedGridviewComponent {
|
||||
return this.component.toJSON();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the component back to an empty and default state.
|
||||
*/
|
||||
clear(): void {
|
||||
this.component.clear();
|
||||
}
|
||||
|
||||
updateOptions(options: Partial<GridviewComponentOptions>) {
|
||||
this.component.updateOptions(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Release resources and teardown component. Do not call when using framework versions of dockview.
|
||||
*/
|
||||
dispose(): void {
|
||||
this.component.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export class DockviewApi implements CommonApi<SerializedDockview> {
|
||||
/**
|
||||
* The unique identifier for this instance. Used to manage scope of Drag'n'Drop events.
|
||||
*/
|
||||
get id(): string {
|
||||
return this.component.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Width of the component.
|
||||
*/
|
||||
get width(): number {
|
||||
return this.component.width;
|
||||
}
|
||||
|
||||
/**
|
||||
* Height of the component.
|
||||
*/
|
||||
get height(): number {
|
||||
return this.component.height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimum height of the component.
|
||||
*/
|
||||
get minimumHeight(): number {
|
||||
return this.component.minimumHeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maximum height of the component.
|
||||
*/
|
||||
get maximumHeight(): number {
|
||||
return this.component.maximumHeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimum width of the component.
|
||||
*/
|
||||
get minimumWidth(): number {
|
||||
return this.component.minimumWidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maximum width of the component.
|
||||
*/
|
||||
get maximumWidth(): number {
|
||||
return this.component.maximumWidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Total number of groups.
|
||||
*/
|
||||
get size(): number {
|
||||
return this.component.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Total number of panels.
|
||||
*/
|
||||
get totalPanels(): number {
|
||||
return this.component.totalPanels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when the active group changes. May be undefined if no group is active.
|
||||
*/
|
||||
get onDidActiveGroupChange(): Event<DockviewGroupPanel | undefined> {
|
||||
return this.component.onDidActiveGroupChange;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when a group is added. May be called multiple times when moving groups.
|
||||
*/
|
||||
get onDidAddGroup(): Event<DockviewGroupPanel> {
|
||||
return this.component.onDidAddGroup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when a group is removed. May be called multiple times when moving groups.
|
||||
*/
|
||||
get onDidRemoveGroup(): Event<DockviewGroupPanel> {
|
||||
return this.component.onDidRemoveGroup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when the active panel changes. May be undefined if no panel is active.
|
||||
*/
|
||||
get onDidActivePanelChange(): Event<IDockviewPanel | undefined> {
|
||||
return this.component.onDidActivePanelChange;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when a panel is added. May be called multiple times when moving panels.
|
||||
*/
|
||||
get onDidAddPanel(): Event<IDockviewPanel> {
|
||||
return this.component.onDidAddPanel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when a panel is removed. May be called multiple times when moving panels.
|
||||
*/
|
||||
get onDidRemovePanel(): Event<IDockviewPanel> {
|
||||
return this.component.onDidRemovePanel;
|
||||
}
|
||||
|
||||
get onDidMovePanel(): Event<MovePanelEvent> {
|
||||
return this.component.onDidMovePanel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked after a layout is deserialzied using the `fromJSON` method.
|
||||
*/
|
||||
get onDidLayoutFromJSON(): Event<void> {
|
||||
return this.component.onDidLayoutFromJSON;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when any layout change occures, an aggregation of many events.
|
||||
*/
|
||||
get onDidLayoutChange(): Event<void> {
|
||||
return this.component.onDidLayoutChange;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when a Drag'n'Drop event occurs that the component was unable to handle. Exposed for custom Drag'n'Drop functionality.
|
||||
*/
|
||||
get onDidDrop(): Event<DockviewDidDropEvent> {
|
||||
get onDidDrop(): Event<DockviewDropEvent> {
|
||||
return this.component.onDidDrop;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when a Drag'n'Drop event occurs but before dockview handles it giving the user an opportunity to intecept and
|
||||
* prevent the event from occuring using the standard `preventDefault()` syntax.
|
||||
*
|
||||
* Preventing certain events may causes unexpected behaviours, use carefully.
|
||||
*/
|
||||
get onWillDrop(): Event<DockviewWillDropEvent> {
|
||||
return this.component.onWillDrop;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked before an overlay is shown indicating a drop target.
|
||||
*
|
||||
* Calling `event.preventDefault()` will prevent the overlay being shown and prevent
|
||||
* the any subsequent drop event.
|
||||
*/
|
||||
get onWillShowOverlay(): Event<WillShowOverlayLocationEvent> {
|
||||
return this.component.onWillShowOverlay;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked before a group is dragged.
|
||||
*
|
||||
* Calling `event.nativeEvent.preventDefault()` will prevent the group drag starting.
|
||||
*
|
||||
*/
|
||||
get onWillDragGroup(): Event<GroupDragEvent> {
|
||||
return this.component.onWillDragGroup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked before a panel is dragged.
|
||||
*
|
||||
* Calling `event.nativeEvent.preventDefault()` will prevent the panel drag starting.
|
||||
*/
|
||||
get onWillDragPanel(): Event<TabDragEvent> {
|
||||
return this.component.onWillDragPanel;
|
||||
}
|
||||
|
||||
get onUnhandledDragOverEvent(): Event<DockviewDndOverlayEvent> {
|
||||
return this.component.onUnhandledDragOverEvent;
|
||||
}
|
||||
|
||||
get onDidPopoutGroupSizeChange(): Event<PopoutGroupChangeSizeEvent> {
|
||||
return this.component.onDidPopoutGroupSizeChange;
|
||||
}
|
||||
|
||||
get onDidPopoutGroupPositionChange(): Event<PopoutGroupChangePositionEvent> {
|
||||
return this.component.onDidPopoutGroupPositionChange;
|
||||
}
|
||||
|
||||
/**
|
||||
* All panel objects.
|
||||
*/
|
||||
get panels(): IDockviewPanel[] {
|
||||
return this.component.panels;
|
||||
}
|
||||
|
||||
/**
|
||||
* All group objects.
|
||||
*/
|
||||
get groups(): DockviewGroupPanel[] {
|
||||
return this.component.groups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Active panel object.
|
||||
*/
|
||||
get activePanel(): IDockviewPanel | undefined {
|
||||
return this.component.activePanel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Active group object.
|
||||
*/
|
||||
get activeGroup(): DockviewGroupPanel | undefined {
|
||||
return this.component.activeGroup;
|
||||
}
|
||||
|
||||
constructor(private readonly component: IDockviewComponent) {}
|
||||
|
||||
/**
|
||||
* Focus the component. Will try to focus an active panel if one exists.
|
||||
*/
|
||||
focus(): void {
|
||||
this.component.focus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a panel object given a `string` id. May return `undefined`.
|
||||
*/
|
||||
getPanel(id: string): IDockviewPanel | undefined {
|
||||
return this.component.getGroupPanel(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Force resize the component to an exact width and height. Read about auto-resizing before using.
|
||||
*/
|
||||
layout(width: number, height: number, force = false): void {
|
||||
this.component.layout(width, height, force);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a panel and return the created object.
|
||||
*/
|
||||
addPanel<T extends object = Parameters>(
|
||||
options: AddPanelOptions<T>
|
||||
): IDockviewPanel {
|
||||
addPanel(options: AddPanelOptions): IDockviewPanel {
|
||||
return this.component.addPanel(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a panel given the panel object.
|
||||
*/
|
||||
removePanel(panel: IDockviewPanel): void {
|
||||
this.component.removePanel(panel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a group and return the created object.
|
||||
*/
|
||||
addGroup(options?: AddGroupOptions): DockviewGroupPanel {
|
||||
return this.component.addGroup(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close all groups and panels.
|
||||
*/
|
||||
closeAllGroups(): void {
|
||||
return this.component.closeAllGroups();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a group and any panels within the group.
|
||||
*/
|
||||
removeGroup(group: IDockviewGroupPanel): void {
|
||||
this.component.removeGroup(<DockviewGroupPanel>group);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a group object given a `string` id. May return undefined.
|
||||
*/
|
||||
getGroup(id: string): DockviewGroupPanel | undefined {
|
||||
return this.component.getPanel(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a floating group
|
||||
*/
|
||||
addFloatingGroup(
|
||||
item: IDockviewPanel | DockviewGroupPanel,
|
||||
options?: FloatingGroupOptions
|
||||
): void {
|
||||
return this.component.addFloatingGroup(item, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a component from a serialized object.
|
||||
*/
|
||||
fromJSON(data: SerializedDockview): void {
|
||||
this.component.fromJSON(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a serialized object of the current component.
|
||||
*/
|
||||
toJSON(): SerializedDockview {
|
||||
return this.component.toJSON();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the component back to an empty and default state.
|
||||
*/
|
||||
clear(): void {
|
||||
this.component.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the focus progmatically to the next panel or group.
|
||||
*/
|
||||
moveToNext(options?: MovementOptions): void {
|
||||
this.component.moveToNext(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the focus progmatically to the previous panel or group.
|
||||
*/
|
||||
moveToPrevious(options?: MovementOptions): void {
|
||||
this.component.moveToPrevious(options);
|
||||
}
|
||||
|
||||
maximizeGroup(panel: IDockviewPanel): void {
|
||||
this.component.maximizeGroup(panel.group);
|
||||
closeAllGroups(): void {
|
||||
return this.component.closeAllGroups();
|
||||
}
|
||||
|
||||
hasMaximizedGroup(): boolean {
|
||||
return this.component.hasMaximizedGroup();
|
||||
removeGroup(group: IDockviewGroupPanel): void {
|
||||
this.component.removeGroup(<DockviewGroupPanel>group);
|
||||
}
|
||||
|
||||
exitMaximizedGroup(): void {
|
||||
this.component.exitMaximizedGroup();
|
||||
getGroup(id: string): DockviewGroupPanel | undefined {
|
||||
return this.component.getPanel(id);
|
||||
}
|
||||
|
||||
get onDidMaximizedGroupChange(): Event<DockviewMaximizedGroupChanged> {
|
||||
return this.component.onDidMaximizedGroupChange;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a popout group in a new Window
|
||||
*/
|
||||
addPopoutGroup(
|
||||
addFloatingGroup(
|
||||
item: IDockviewPanel | DockviewGroupPanel,
|
||||
options?: {
|
||||
position?: Box;
|
||||
popoutUrl?: string;
|
||||
onDidOpen?: (event: { id: string; window: Window }) => void;
|
||||
onWillClose?: (event: { id: string; window: Window }) => void;
|
||||
}
|
||||
): Promise<boolean> {
|
||||
return this.component.addPopoutGroup(item, options);
|
||||
coord?: { x: number; y: number }
|
||||
): void {
|
||||
return this.component.addFloatingGroup(item, coord);
|
||||
}
|
||||
|
||||
updateOptions(options: Partial<DockviewComponentOptions>) {
|
||||
this.component.updateOptions(options);
|
||||
fromJSON(data: SerializedDockview): void {
|
||||
this.component.fromJSON(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Release resources and teardown component. Do not call when using framework versions of dockview.
|
||||
*/
|
||||
dispose(): void {
|
||||
this.component.dispose();
|
||||
toJSON(): SerializedDockview {
|
||||
return this.component.toJSON();
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.component.clear();
|
||||
}
|
||||
}
|
||||
|
@ -1,139 +1,53 @@
|
||||
import { Position, positionToDirection } from '../dnd/droptarget';
|
||||
import { Position } from '../dnd/droptarget';
|
||||
import { DockviewComponent } from '../dockview/dockviewComponent';
|
||||
import { DockviewGroupPanel } from '../dockview/dockviewGroupPanel';
|
||||
import {
|
||||
DockviewGroupChangeEvent,
|
||||
DockviewGroupLocation,
|
||||
} from '../dockview/dockviewGroupPanelModel';
|
||||
import { Emitter, Event } from '../events';
|
||||
import { GridviewPanelApi, GridviewPanelApiImpl } from './gridviewPanelApi';
|
||||
|
||||
export interface DockviewGroupMoveParams {
|
||||
group?: DockviewGroupPanel;
|
||||
position?: Position;
|
||||
/**
|
||||
* The index to place the panel within a group, only applicable if the placement is within an existing group
|
||||
*/
|
||||
index?: number;
|
||||
}
|
||||
|
||||
export interface DockviewGroupPanelApi extends GridviewPanelApi {
|
||||
readonly onDidLocationChange: Event<DockviewGroupPanelFloatingChangeEvent>;
|
||||
readonly onDidActivePanelChange: Event<DockviewGroupChangeEvent>;
|
||||
readonly location: DockviewGroupLocation;
|
||||
/**
|
||||
* If you require the Window object
|
||||
*/
|
||||
getWindow(): Window;
|
||||
moveTo(options: DockviewGroupMoveParams): void;
|
||||
maximize(): void;
|
||||
isMaximized(): boolean;
|
||||
exitMaximized(): void;
|
||||
close(): void;
|
||||
readonly onDidFloatingStateChange: Event<DockviewGroupPanelFloatingChangeEvent>;
|
||||
readonly isFloating: boolean;
|
||||
moveTo(options: { group: DockviewGroupPanel; position?: Position }): void;
|
||||
}
|
||||
|
||||
export interface DockviewGroupPanelFloatingChangeEvent {
|
||||
readonly location: DockviewGroupLocation;
|
||||
readonly isFloating: boolean;
|
||||
}
|
||||
|
||||
const NOT_INITIALIZED_MESSAGE =
|
||||
'dockview: DockviewGroupPanelApiImpl not initialized';
|
||||
|
||||
export class DockviewGroupPanelApiImpl extends GridviewPanelApiImpl {
|
||||
private _group: DockviewGroupPanel | undefined;
|
||||
|
||||
readonly _onDidLocationChange =
|
||||
readonly _onDidFloatingStateChange =
|
||||
new Emitter<DockviewGroupPanelFloatingChangeEvent>();
|
||||
readonly onDidLocationChange: Event<DockviewGroupPanelFloatingChangeEvent> =
|
||||
this._onDidLocationChange.event;
|
||||
readonly onDidFloatingStateChange: Event<DockviewGroupPanelFloatingChangeEvent> =
|
||||
this._onDidFloatingStateChange.event;
|
||||
|
||||
readonly _onDidActivePanelChange = new Emitter<DockviewGroupChangeEvent>();
|
||||
readonly onDidActivePanelChange = this._onDidActivePanelChange.event;
|
||||
|
||||
get location(): DockviewGroupLocation {
|
||||
get isFloating() {
|
||||
if (!this._group) {
|
||||
throw new Error(NOT_INITIALIZED_MESSAGE);
|
||||
throw new Error(`DockviewGroupPanelApiImpl not initialized`);
|
||||
}
|
||||
return this._group.model.location;
|
||||
return this._group.model.isFloating;
|
||||
}
|
||||
|
||||
constructor(id: string, private readonly accessor: DockviewComponent) {
|
||||
super(id, '__dockviewgroup__');
|
||||
super(id);
|
||||
|
||||
this.addDisposables(
|
||||
this._onDidLocationChange,
|
||||
this._onDidActivePanelChange
|
||||
this.addDisposables(this._onDidFloatingStateChange);
|
||||
}
|
||||
|
||||
moveTo(options: { group: DockviewGroupPanel; position?: Position }): void {
|
||||
if (!this._group) {
|
||||
throw new Error(`DockviewGroupPanelApiImpl not initialized`);
|
||||
}
|
||||
|
||||
this.accessor.moveGroupOrPanel(
|
||||
options.group,
|
||||
this._group.id,
|
||||
undefined,
|
||||
options.position ?? 'center'
|
||||
);
|
||||
}
|
||||
|
||||
close(): void {
|
||||
if (!this._group) {
|
||||
return;
|
||||
}
|
||||
return this.accessor.removeGroup(this._group);
|
||||
}
|
||||
|
||||
getWindow(): Window {
|
||||
return this.location.type === 'popout'
|
||||
? this.location.getWindow()
|
||||
: window;
|
||||
}
|
||||
|
||||
moveTo(options: DockviewGroupMoveParams): void {
|
||||
if (!this._group) {
|
||||
throw new Error(NOT_INITIALIZED_MESSAGE);
|
||||
}
|
||||
|
||||
const group =
|
||||
options.group ??
|
||||
this.accessor.addGroup({
|
||||
direction: positionToDirection(options.position ?? 'right'),
|
||||
skipSetActive: true,
|
||||
});
|
||||
|
||||
this.accessor.moveGroupOrPanel({
|
||||
from: { groupId: this._group.id },
|
||||
to: {
|
||||
group,
|
||||
position: options.group
|
||||
? options.position ?? 'center'
|
||||
: 'center',
|
||||
index: options.index,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
maximize(): void {
|
||||
if (!this._group) {
|
||||
throw new Error(NOT_INITIALIZED_MESSAGE);
|
||||
}
|
||||
|
||||
if (this.location.type !== 'grid') {
|
||||
// only grid groups can be maximized
|
||||
return;
|
||||
}
|
||||
|
||||
this.accessor.maximizeGroup(this._group);
|
||||
}
|
||||
|
||||
isMaximized(): boolean {
|
||||
if (!this._group) {
|
||||
throw new Error(NOT_INITIALIZED_MESSAGE);
|
||||
}
|
||||
|
||||
return this.accessor.isMaximizedGroup(this._group);
|
||||
}
|
||||
|
||||
exitMaximized(): void {
|
||||
if (!this._group) {
|
||||
throw new Error(NOT_INITIALIZED_MESSAGE);
|
||||
}
|
||||
|
||||
if (this.isMaximized()) {
|
||||
this.accessor.exitMaximizedGroup();
|
||||
}
|
||||
}
|
||||
|
||||
initialize(group: DockviewGroupPanel): void {
|
||||
this._group = group;
|
||||
}
|
||||
|
@ -1,67 +1,36 @@
|
||||
import { Emitter, Event } from '../events';
|
||||
import { GridviewPanelApiImpl, GridviewPanelApi } from './gridviewPanelApi';
|
||||
import { DockviewGroupPanel } from '../dockview/dockviewGroupPanel';
|
||||
import { CompositeDisposable, MutableDisposable } from '../lifecycle';
|
||||
import { DockviewPanel } from '../dockview/dockviewPanel';
|
||||
import { MutableDisposable } from '../lifecycle';
|
||||
import { IDockviewPanel } from '../dockview/dockviewPanel';
|
||||
import { DockviewComponent } from '../dockview/dockviewComponent';
|
||||
import { DockviewPanelRenderer } from '../overlay/overlayRenderContainer';
|
||||
import {
|
||||
DockviewGroupMoveParams,
|
||||
DockviewGroupPanelFloatingChangeEvent,
|
||||
} from './dockviewGroupPanelApi';
|
||||
import { DockviewGroupLocation } from '../dockview/dockviewGroupPanelModel';
|
||||
import { Position } from '../dnd/droptarget';
|
||||
|
||||
export interface TitleEvent {
|
||||
readonly title: string;
|
||||
}
|
||||
|
||||
export interface RendererChangedEvent {
|
||||
readonly renderer: DockviewPanelRenderer;
|
||||
}
|
||||
|
||||
export interface ActiveGroupEvent {
|
||||
readonly isActive: boolean;
|
||||
}
|
||||
|
||||
export interface GroupChangedEvent {
|
||||
// empty
|
||||
}
|
||||
|
||||
export type DockviewPanelMoveParams = DockviewGroupMoveParams;
|
||||
|
||||
/*
|
||||
* omit visibility modifiers since the visibility of a single group doesn't make sense
|
||||
* because it belongs to a groupview
|
||||
*/
|
||||
export interface DockviewPanelApi
|
||||
extends Omit<
|
||||
GridviewPanelApi,
|
||||
// omit properties that do not make sense here
|
||||
'setVisible' | 'onDidConstraintsChange' | 'setConstraints'
|
||||
> {
|
||||
/**
|
||||
* The id of the tab component renderer
|
||||
*
|
||||
* Undefined if no custom tab renderer is provided
|
||||
*/
|
||||
readonly tabComponent: string | undefined;
|
||||
readonly group: DockviewGroupPanel;
|
||||
readonly isGroupActive: boolean;
|
||||
readonly renderer: DockviewPanelRenderer;
|
||||
readonly title: string | undefined;
|
||||
readonly onDidActiveGroupChange: Event<ActiveGroupEvent>;
|
||||
readonly onDidGroupChange: Event<GroupChangedEvent>;
|
||||
readonly onDidTitleChange: Event<TitleEvent>;
|
||||
readonly onDidRendererChange: Event<RendererChangedEvent>;
|
||||
readonly location: DockviewGroupLocation;
|
||||
readonly onDidLocationChange: Event<DockviewGroupPanelFloatingChangeEvent>;
|
||||
readonly onDidActiveGroupChange: Event<void>;
|
||||
readonly onDidGroupChange: Event<void>;
|
||||
close(): void;
|
||||
setTitle(title: string): void;
|
||||
setRenderer(renderer: DockviewPanelRenderer): void;
|
||||
moveTo(options: DockviewPanelMoveParams): void;
|
||||
maximize(): void;
|
||||
isMaximized(): boolean;
|
||||
exitMaximized(): void;
|
||||
/**
|
||||
* If you require the Window object
|
||||
*/
|
||||
getWindow(): Window;
|
||||
moveTo(options: {
|
||||
group: DockviewGroupPanel;
|
||||
position?: Position;
|
||||
index?: number;
|
||||
}): void;
|
||||
}
|
||||
|
||||
export class DockviewPanelApiImpl
|
||||
@ -69,56 +38,41 @@ export class DockviewPanelApiImpl
|
||||
implements DockviewPanelApi
|
||||
{
|
||||
private _group: DockviewGroupPanel;
|
||||
private readonly _tabComponent: string | undefined;
|
||||
|
||||
readonly _onDidTitleChange = new Emitter<TitleEvent>();
|
||||
readonly onDidTitleChange = this._onDidTitleChange.event;
|
||||
|
||||
private readonly _onDidActiveGroupChange = new Emitter<ActiveGroupEvent>();
|
||||
private readonly _onDidActiveGroupChange = new Emitter<void>();
|
||||
readonly onDidActiveGroupChange = this._onDidActiveGroupChange.event;
|
||||
|
||||
private readonly _onDidGroupChange = new Emitter<GroupChangedEvent>();
|
||||
private readonly _onDidGroupChange = new Emitter<void>();
|
||||
readonly onDidGroupChange = this._onDidGroupChange.event;
|
||||
|
||||
readonly _onDidRendererChange = new Emitter<RendererChangedEvent>();
|
||||
readonly onDidRendererChange = this._onDidRendererChange.event;
|
||||
|
||||
private readonly _onDidLocationChange =
|
||||
new Emitter<DockviewGroupPanelFloatingChangeEvent>();
|
||||
readonly onDidLocationChange: Event<DockviewGroupPanelFloatingChangeEvent> =
|
||||
this._onDidLocationChange.event;
|
||||
|
||||
private readonly groupEventsDisposable = new MutableDisposable();
|
||||
|
||||
get location(): DockviewGroupLocation {
|
||||
return this.group.api.location;
|
||||
}
|
||||
private readonly disposable = new MutableDisposable();
|
||||
|
||||
get title(): string | undefined {
|
||||
return this.panel.title;
|
||||
}
|
||||
|
||||
get isGroupActive(): boolean {
|
||||
return this.group.isActive;
|
||||
}
|
||||
|
||||
get renderer(): DockviewPanelRenderer {
|
||||
return this.panel.renderer;
|
||||
return !!this.group?.isActive;
|
||||
}
|
||||
|
||||
set group(value: DockviewGroupPanel) {
|
||||
const oldGroup = this._group;
|
||||
const isOldGroupActive = this.isGroupActive;
|
||||
|
||||
if (this._group !== value) {
|
||||
this._group = value;
|
||||
this._group = value;
|
||||
|
||||
this._onDidGroupChange.fire({});
|
||||
this._onDidGroupChange.fire();
|
||||
|
||||
this.setupGroupEventListeners(oldGroup);
|
||||
|
||||
this._onDidLocationChange.fire({
|
||||
location: this.group.api.location,
|
||||
if (this._group) {
|
||||
this.disposable.value = this._group.api.onDidActiveChange(() => {
|
||||
this._onDidActiveGroupChange.fire();
|
||||
});
|
||||
|
||||
if (this.isGroupActive !== isOldGroupActive) {
|
||||
this._onDidActiveGroupChange.fire();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -126,111 +80,44 @@ export class DockviewPanelApiImpl
|
||||
return this._group;
|
||||
}
|
||||
|
||||
get tabComponent(): string | undefined {
|
||||
return this._tabComponent;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private readonly panel: DockviewPanel,
|
||||
private panel: IDockviewPanel,
|
||||
group: DockviewGroupPanel,
|
||||
private readonly accessor: DockviewComponent,
|
||||
component: string,
|
||||
tabComponent?: string
|
||||
private readonly accessor: DockviewComponent
|
||||
) {
|
||||
super(panel.id, component);
|
||||
|
||||
this._tabComponent = tabComponent;
|
||||
super(panel.id);
|
||||
|
||||
this.initialize(panel);
|
||||
|
||||
this._group = group;
|
||||
this.setupGroupEventListeners();
|
||||
|
||||
this.addDisposables(
|
||||
this.groupEventsDisposable,
|
||||
this._onDidRendererChange,
|
||||
this.disposable,
|
||||
this._onDidTitleChange,
|
||||
this._onDidGroupChange,
|
||||
this._onDidActiveGroupChange,
|
||||
this._onDidLocationChange
|
||||
this._onDidActiveGroupChange
|
||||
);
|
||||
}
|
||||
|
||||
getWindow(): Window {
|
||||
return this.group.api.getWindow();
|
||||
}
|
||||
|
||||
moveTo(options: DockviewPanelMoveParams): void {
|
||||
this.accessor.moveGroupOrPanel({
|
||||
from: { groupId: this._group.id, panelId: this.panel.id },
|
||||
to: {
|
||||
group: options.group ?? this._group,
|
||||
position: options.group
|
||||
? options.position ?? 'center'
|
||||
: 'center',
|
||||
index: options.index,
|
||||
},
|
||||
});
|
||||
moveTo(options: {
|
||||
group: DockviewGroupPanel;
|
||||
position?: Position;
|
||||
index?: number;
|
||||
}): void {
|
||||
this.accessor.moveGroupOrPanel(
|
||||
options.group,
|
||||
this._group.id,
|
||||
this.panel.id,
|
||||
options.position ?? 'center',
|
||||
options.index
|
||||
);
|
||||
}
|
||||
|
||||
setTitle(title: string): void {
|
||||
this.panel.setTitle(title);
|
||||
}
|
||||
|
||||
setRenderer(renderer: DockviewPanelRenderer): void {
|
||||
this.panel.setRenderer(renderer);
|
||||
}
|
||||
|
||||
close(): void {
|
||||
this.group.model.closePanel(this.panel);
|
||||
}
|
||||
|
||||
maximize(): void {
|
||||
this.group.api.maximize();
|
||||
}
|
||||
|
||||
isMaximized(): boolean {
|
||||
return this.group.api.isMaximized();
|
||||
}
|
||||
|
||||
exitMaximized(): void {
|
||||
this.group.api.exitMaximized();
|
||||
}
|
||||
|
||||
private setupGroupEventListeners(previousGroup?: DockviewGroupPanel) {
|
||||
let _trackGroupActive = previousGroup?.isActive ?? false; // prevent duplicate events with same state
|
||||
|
||||
this.groupEventsDisposable.value = new CompositeDisposable(
|
||||
this.group.api.onDidVisibilityChange((event) => {
|
||||
const hasBecomeHidden = !event.isVisible && this.isVisible;
|
||||
const hasBecomeVisible = event.isVisible && !this.isVisible;
|
||||
|
||||
const isActivePanel = this.group.model.isPanelActive(
|
||||
this.panel
|
||||
);
|
||||
|
||||
if (hasBecomeHidden || (hasBecomeVisible && isActivePanel)) {
|
||||
this._onDidVisibilityChange.fire(event);
|
||||
}
|
||||
}),
|
||||
this.group.api.onDidLocationChange((event) => {
|
||||
if (this.group !== this.panel.group) {
|
||||
return;
|
||||
}
|
||||
this._onDidLocationChange.fire(event);
|
||||
}),
|
||||
this.group.api.onDidActiveChange(() => {
|
||||
if (this.group !== this.panel.group) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_trackGroupActive !== this.isGroupActive) {
|
||||
_trackGroupActive = this.isGroupActive;
|
||||
this._onDidActiveGroupChange.fire({
|
||||
isActive: this.isGroupActive,
|
||||
});
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,46 +0,0 @@
|
||||
import {
|
||||
DockviewApi,
|
||||
GridviewApi,
|
||||
PaneviewApi,
|
||||
SplitviewApi,
|
||||
} from '../api/component.api';
|
||||
import { DockviewComponent } from '../dockview/dockviewComponent';
|
||||
import { DockviewComponentOptions } from '../dockview/options';
|
||||
import { GridviewComponent } from '../gridview/gridviewComponent';
|
||||
import { GridviewComponentOptions } from '../gridview/options';
|
||||
import { PaneviewComponentOptions } from '../paneview/options';
|
||||
import { PaneviewComponent } from '../paneview/paneviewComponent';
|
||||
import { SplitviewComponentOptions } from '../splitview/options';
|
||||
import { SplitviewComponent } from '../splitview/splitviewComponent';
|
||||
|
||||
export function createDockview(
|
||||
element: HTMLElement,
|
||||
options: DockviewComponentOptions
|
||||
): DockviewApi {
|
||||
const component = new DockviewComponent(element, options);
|
||||
return component.api;
|
||||
}
|
||||
|
||||
export function createSplitview(
|
||||
element: HTMLElement,
|
||||
options: SplitviewComponentOptions
|
||||
): SplitviewApi {
|
||||
const component = new SplitviewComponent(element, options);
|
||||
return new SplitviewApi(component);
|
||||
}
|
||||
|
||||
export function createGridview(
|
||||
element: HTMLElement,
|
||||
options: GridviewComponentOptions
|
||||
): GridviewApi {
|
||||
const component = new GridviewComponent(element, options);
|
||||
return new GridviewApi(component);
|
||||
}
|
||||
|
||||
export function createPaneview(
|
||||
element: HTMLElement,
|
||||
options: PaneviewComponentOptions
|
||||
): PaneviewApi {
|
||||
const component = new PaneviewComponent(element, options);
|
||||
return new PaneviewApi(component);
|
||||
}
|
@ -37,15 +37,17 @@ export class GridviewPanelApiImpl
|
||||
readonly onDidConstraintsChangeInternal: Event<GridConstraintChangeEvent2> =
|
||||
this._onDidConstraintsChangeInternal.event;
|
||||
|
||||
readonly _onDidConstraintsChange = new Emitter<GridConstraintChangeEvent>();
|
||||
readonly _onDidConstraintsChange = new Emitter<GridConstraintChangeEvent>({
|
||||
replay: true,
|
||||
});
|
||||
readonly onDidConstraintsChange: Event<GridConstraintChangeEvent> =
|
||||
this._onDidConstraintsChange.event;
|
||||
|
||||
private readonly _onDidSizeChange = new Emitter<SizeEvent>();
|
||||
readonly onDidSizeChange: Event<SizeEvent> = this._onDidSizeChange.event;
|
||||
|
||||
constructor(id: string, component: string, panel?: IPanel) {
|
||||
super(id, component);
|
||||
constructor(id: string, panel?: IPanel) {
|
||||
super(id);
|
||||
|
||||
this.addDisposables(
|
||||
this._onDidConstraintsChangeInternal,
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { DockviewEvent, Emitter, Event } from '../events';
|
||||
import { Emitter, Event } from '../events';
|
||||
import { CompositeDisposable, MutableDisposable } from '../lifecycle';
|
||||
import { IPanel, Parameters } from '../panel/types';
|
||||
|
||||
@ -24,14 +24,9 @@ export interface PanelApi {
|
||||
readonly onDidFocusChange: Event<FocusEvent>;
|
||||
readonly onDidVisibilityChange: Event<VisibilityEvent>;
|
||||
readonly onDidActiveChange: Event<ActiveEvent>;
|
||||
readonly onDidParametersChange: Event<Parameters>;
|
||||
setActive(): void;
|
||||
setVisible(isVisible: boolean): void;
|
||||
setActive(): void;
|
||||
updateParameters(parameters: Parameters): void;
|
||||
/**
|
||||
* The id of the component renderer
|
||||
*/
|
||||
readonly component: string;
|
||||
/**
|
||||
* The id of the panel that would have been assigned when the panel was created
|
||||
*/
|
||||
@ -56,16 +51,6 @@ export interface PanelApi {
|
||||
* The panel height in pixels
|
||||
*/
|
||||
readonly height: number;
|
||||
|
||||
readonly onWillFocus: Event<WillFocusEvent>;
|
||||
|
||||
getParameters<T extends Parameters = Parameters>(): T;
|
||||
}
|
||||
|
||||
export class WillFocusEvent extends DockviewEvent {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -77,59 +62,67 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi {
|
||||
private _isVisible = true;
|
||||
private _width = 0;
|
||||
private _height = 0;
|
||||
private _parameters: Parameters = {};
|
||||
|
||||
private readonly panelUpdatesDisposable = new MutableDisposable();
|
||||
|
||||
readonly _onDidDimensionChange = new Emitter<PanelDimensionChangeEvent>();
|
||||
readonly _onDidDimensionChange = new Emitter<PanelDimensionChangeEvent>({
|
||||
replay: true,
|
||||
});
|
||||
readonly onDidDimensionsChange = this._onDidDimensionChange.event;
|
||||
|
||||
readonly _onDidChangeFocus = new Emitter<FocusEvent>();
|
||||
//
|
||||
readonly _onDidChangeFocus = new Emitter<FocusEvent>({
|
||||
replay: true,
|
||||
});
|
||||
readonly onDidFocusChange: Event<FocusEvent> = this._onDidChangeFocus.event;
|
||||
//
|
||||
readonly _onWillFocus = new Emitter<WillFocusEvent>();
|
||||
readonly onWillFocus: Event<WillFocusEvent> = this._onWillFocus.event;
|
||||
readonly _onFocusEvent = new Emitter<void>();
|
||||
readonly onFocusEvent: Event<void> = this._onFocusEvent.event;
|
||||
//
|
||||
readonly _onDidVisibilityChange = new Emitter<VisibilityEvent>();
|
||||
readonly _onDidVisibilityChange = new Emitter<VisibilityEvent>({
|
||||
replay: true,
|
||||
});
|
||||
readonly onDidVisibilityChange: Event<VisibilityEvent> =
|
||||
this._onDidVisibilityChange.event;
|
||||
//
|
||||
|
||||
readonly _onWillVisibilityChange = new Emitter<VisibilityEvent>();
|
||||
readonly onWillVisibilityChange: Event<VisibilityEvent> =
|
||||
this._onWillVisibilityChange.event;
|
||||
|
||||
readonly _onDidActiveChange = new Emitter<ActiveEvent>();
|
||||
readonly _onVisibilityChange = new Emitter<VisibilityEvent>();
|
||||
readonly onVisibilityChange: Event<VisibilityEvent> =
|
||||
this._onVisibilityChange.event;
|
||||
//
|
||||
readonly _onDidActiveChange = new Emitter<ActiveEvent>({
|
||||
replay: true,
|
||||
});
|
||||
readonly onDidActiveChange: Event<ActiveEvent> =
|
||||
this._onDidActiveChange.event;
|
||||
|
||||
//
|
||||
readonly _onActiveChange = new Emitter<void>();
|
||||
readonly onActiveChange: Event<void> = this._onActiveChange.event;
|
||||
//
|
||||
readonly _onUpdateParameters = new Emitter<Parameters>();
|
||||
readonly onUpdateParameters: Event<Parameters> =
|
||||
this._onUpdateParameters.event;
|
||||
//
|
||||
|
||||
readonly _onDidParametersChange = new Emitter<Parameters>();
|
||||
readonly onDidParametersChange: Event<Parameters> =
|
||||
this._onDidParametersChange.event;
|
||||
|
||||
get isFocused(): boolean {
|
||||
get isFocused() {
|
||||
return this._isFocused;
|
||||
}
|
||||
|
||||
get isActive(): boolean {
|
||||
get isActive() {
|
||||
return this._isActive;
|
||||
}
|
||||
|
||||
get isVisible(): boolean {
|
||||
get isVisible() {
|
||||
return this._isVisible;
|
||||
}
|
||||
|
||||
get width(): number {
|
||||
get width() {
|
||||
return this._width;
|
||||
}
|
||||
|
||||
get height(): number {
|
||||
get height() {
|
||||
return this._height;
|
||||
}
|
||||
|
||||
constructor(readonly id: string, readonly component: string) {
|
||||
constructor(readonly id: string) {
|
||||
super();
|
||||
|
||||
this.addDisposables(
|
||||
@ -151,22 +144,16 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi {
|
||||
this._onDidChangeFocus,
|
||||
this._onDidVisibilityChange,
|
||||
this._onDidActiveChange,
|
||||
this._onWillFocus,
|
||||
this._onFocusEvent,
|
||||
this._onActiveChange,
|
||||
this._onWillFocus,
|
||||
this._onWillVisibilityChange,
|
||||
this._onDidParametersChange
|
||||
this._onVisibilityChange,
|
||||
this._onUpdateParameters
|
||||
);
|
||||
}
|
||||
|
||||
getParameters<T extends Parameters = Parameters>(): T {
|
||||
return this._parameters as T;
|
||||
}
|
||||
|
||||
public initialize(panel: IPanel): void {
|
||||
this.panelUpdatesDisposable.value = this._onDidParametersChange.event(
|
||||
this.panelUpdatesDisposable.value = this._onUpdateParameters.event(
|
||||
(parameters) => {
|
||||
this._parameters = parameters;
|
||||
panel.update({
|
||||
params: parameters,
|
||||
});
|
||||
@ -174,8 +161,8 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi {
|
||||
);
|
||||
}
|
||||
|
||||
setVisible(isVisible: boolean): void {
|
||||
this._onWillVisibilityChange.fire({ isVisible });
|
||||
setVisible(isVisible: boolean) {
|
||||
this._onVisibilityChange.fire({ isVisible });
|
||||
}
|
||||
|
||||
setActive(): void {
|
||||
@ -183,6 +170,10 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi {
|
||||
}
|
||||
|
||||
updateParameters(parameters: Parameters): void {
|
||||
this._onDidParametersChange.fire(parameters);
|
||||
this._onUpdateParameters.fire(parameters);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
@ -35,8 +35,8 @@ export class PaneviewPanelApiImpl
|
||||
this._pane = pane;
|
||||
}
|
||||
|
||||
constructor(id: string, component: string) {
|
||||
super(id, component);
|
||||
constructor(id: string) {
|
||||
super(id);
|
||||
|
||||
this.addDisposables(
|
||||
this._onDidExpansionChange,
|
||||
|
@ -45,8 +45,8 @@ export class SplitviewPanelApiImpl
|
||||
this._onDidSizeChange.event;
|
||||
//
|
||||
|
||||
constructor(id: string, component: string) {
|
||||
super(id, component);
|
||||
constructor(id: string) {
|
||||
super(id);
|
||||
|
||||
this.addDisposables(
|
||||
this._onDidConstraintsChangeInternal,
|
||||
|
@ -1,3 +0,0 @@
|
||||
export const DEFAULT_FLOATING_GROUP_OVERFLOW_SIZE = 100;
|
||||
|
||||
export const DEFAULT_FLOATING_GROUP_POSITION = { left: 100, top: 100, width: 300, height: 300 };
|
@ -1,4 +1,4 @@
|
||||
import { disableIframePointEvents } from '../dom';
|
||||
import { getElementsByTagName } from '../dom';
|
||||
import { addDisposableListener, Emitter } from '../events';
|
||||
import {
|
||||
CompositeDisposable,
|
||||
@ -10,7 +10,7 @@ export abstract class DragHandler extends CompositeDisposable {
|
||||
private readonly dataDisposable = new MutableDisposable();
|
||||
private readonly pointerEventsDisposable = new MutableDisposable();
|
||||
|
||||
private readonly _onDragStart = new Emitter<DragEvent>();
|
||||
private readonly _onDragStart = new Emitter<void>();
|
||||
readonly onDragStart = this._onDragStart.event;
|
||||
|
||||
constructor(protected readonly el: HTMLElement) {
|
||||
@ -25,7 +25,7 @@ export abstract class DragHandler extends CompositeDisposable {
|
||||
this.configure();
|
||||
}
|
||||
|
||||
abstract getData(event: DragEvent): IDisposable;
|
||||
abstract getData(dataTransfer?: DataTransfer | null): IDisposable;
|
||||
|
||||
protected isCancelled(_event: DragEvent): boolean {
|
||||
return false;
|
||||
@ -35,49 +35,54 @@ export abstract class DragHandler extends CompositeDisposable {
|
||||
this.addDisposables(
|
||||
this._onDragStart,
|
||||
addDisposableListener(this.el, 'dragstart', (event) => {
|
||||
if (event.defaultPrevented || this.isCancelled(event)) {
|
||||
if (this.isCancelled(event)) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
const iframes = disableIframePointEvents();
|
||||
const iframes = [
|
||||
...getElementsByTagName('iframe'),
|
||||
...getElementsByTagName('webview'),
|
||||
];
|
||||
|
||||
this.pointerEventsDisposable.value = {
|
||||
dispose: () => {
|
||||
iframes.release();
|
||||
for (const iframe of iframes) {
|
||||
iframe.style.pointerEvents = 'auto';
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
for (const iframe of iframes) {
|
||||
iframe.style.pointerEvents = 'none';
|
||||
}
|
||||
|
||||
this.el.classList.add('dv-dragged');
|
||||
setTimeout(() => this.el.classList.remove('dv-dragged'), 0);
|
||||
|
||||
this.dataDisposable.value = this.getData(event);
|
||||
this._onDragStart.fire(event);
|
||||
this.dataDisposable.value = this.getData(event.dataTransfer);
|
||||
|
||||
if (event.dataTransfer) {
|
||||
event.dataTransfer.effectAllowed = 'move';
|
||||
|
||||
const hasData = event.dataTransfer.items.length > 0;
|
||||
|
||||
if (!hasData) {
|
||||
/**
|
||||
* Although this is not used by dockview many third party dnd libraries will check
|
||||
* dataTransfer.types to determine valid drag events.
|
||||
*
|
||||
* For example: in react-dnd if dataTransfer.types is not set then the dragStart event will be cancelled
|
||||
* through .preventDefault(). Since this is applied globally to all drag events this would break dockviews
|
||||
* dnd logic. You can see the code at
|
||||
P * https://github.com/react-dnd/react-dnd/blob/main/packages/backend-html5/src/HTML5BackendImpl.ts#L542
|
||||
*/
|
||||
event.dataTransfer.setData('text/plain', '');
|
||||
}
|
||||
/**
|
||||
* Although this is not used by dockview many third party dnd libraries will check
|
||||
* dataTransfer.types to determine valid drag events.
|
||||
*
|
||||
* For example: in react-dnd if dataTransfer.types is not set then the dragStart event will be cancelled
|
||||
* through .preventDefault(). Since this is applied globally to all drag events this would break dockviews
|
||||
* dnd logic. You can see the code at
|
||||
* https://github.com/react-dnd/react-dnd/blob/main/packages/backend-html5/src/HTML5BackendImpl.ts#L542
|
||||
*/
|
||||
event.dataTransfer.setData(
|
||||
'text/plain',
|
||||
'__dockview_internal_drag_event__'
|
||||
);
|
||||
}
|
||||
}),
|
||||
addDisposableListener(this.el, 'dragend', () => {
|
||||
this.pointerEventsDisposable.dispose();
|
||||
setTimeout(() => {
|
||||
this.dataDisposable.dispose(); // allow the data to be read by other handlers before disposing
|
||||
}, 0);
|
||||
this.dataDisposable.dispose();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
class TransferObject {
|
||||
// intentionally empty class
|
||||
constructor() {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
export class PanelTransfer extends TransferObject {
|
||||
|
@ -13,51 +13,22 @@ export class DragAndDropObserver extends CompositeDisposable {
|
||||
private target: EventTarget | null = null;
|
||||
|
||||
constructor(
|
||||
private readonly element: HTMLElement,
|
||||
private readonly callbacks: IDragAndDropObserverCallbacks
|
||||
private element: HTMLElement,
|
||||
private callbacks: IDragAndDropObserverCallbacks
|
||||
) {
|
||||
super();
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
onDragEnter(e: DragEvent): void {
|
||||
this.target = e.target;
|
||||
this.callbacks.onDragEnter(e);
|
||||
}
|
||||
|
||||
onDragOver(e: DragEvent): void {
|
||||
e.preventDefault(); // needed so that the drop event fires (https://stackoverflow.com/questions/21339924/drop-event-not-firing-in-chrome)
|
||||
|
||||
if (this.callbacks.onDragOver) {
|
||||
this.callbacks.onDragOver(e);
|
||||
}
|
||||
}
|
||||
|
||||
onDragLeave(e: DragEvent): void {
|
||||
if (this.target === e.target) {
|
||||
this.target = null;
|
||||
|
||||
this.callbacks.onDragLeave(e);
|
||||
}
|
||||
}
|
||||
|
||||
onDragEnd(e: DragEvent): void {
|
||||
this.target = null;
|
||||
this.callbacks.onDragEnd(e);
|
||||
}
|
||||
|
||||
onDrop(e: DragEvent): void {
|
||||
this.callbacks.onDrop(e);
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
this.addDisposables(
|
||||
addDisposableListener(
|
||||
this.element,
|
||||
'dragenter',
|
||||
(e: DragEvent) => {
|
||||
this.onDragEnter(e);
|
||||
this.target = e.target;
|
||||
this.callbacks.onDragEnter(e);
|
||||
},
|
||||
true
|
||||
)
|
||||
@ -68,7 +39,11 @@ export class DragAndDropObserver extends CompositeDisposable {
|
||||
this.element,
|
||||
'dragover',
|
||||
(e: DragEvent) => {
|
||||
this.onDragOver(e);
|
||||
e.preventDefault(); // needed so that the drop event fires (https://stackoverflow.com/questions/21339924/drop-event-not-firing-in-chrome)
|
||||
|
||||
if (this.callbacks.onDragOver) {
|
||||
this.callbacks.onDragOver(e);
|
||||
}
|
||||
},
|
||||
true
|
||||
)
|
||||
@ -76,19 +51,24 @@ export class DragAndDropObserver extends CompositeDisposable {
|
||||
|
||||
this.addDisposables(
|
||||
addDisposableListener(this.element, 'dragleave', (e: DragEvent) => {
|
||||
this.onDragLeave(e);
|
||||
if (this.target === e.target) {
|
||||
this.target = null;
|
||||
|
||||
this.callbacks.onDragLeave(e);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
this.addDisposables(
|
||||
addDisposableListener(this.element, 'dragend', (e: DragEvent) => {
|
||||
this.onDragEnd(e);
|
||||
this.target = null;
|
||||
this.callbacks.onDragEnd(e);
|
||||
})
|
||||
);
|
||||
|
||||
this.addDisposables(
|
||||
addDisposableListener(this.element, 'drop', (e: DragEvent) => {
|
||||
this.onDrop(e);
|
||||
this.callbacks.onDrop(e);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
@ -1,23 +0,0 @@
|
||||
.dv-drop-target-container {
|
||||
position: absolute;
|
||||
z-index: 9999;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
pointer-events: none;
|
||||
overflow: hidden;
|
||||
--dv-transition-duration: 300ms;
|
||||
|
||||
.dv-drop-target-anchor {
|
||||
position: relative;
|
||||
border: var(--dv-drag-over-border);
|
||||
transition: opacity var(--dv-transition-duration) ease-in,
|
||||
top var(--dv-transition-duration) ease-out,
|
||||
left var(--dv-transition-duration) ease-out,
|
||||
width var(--dv-transition-duration) ease-out,
|
||||
height var(--dv-transition-duration) ease-out;
|
||||
background-color: var(--dv-drag-over-background-color);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
@ -1,102 +0,0 @@
|
||||
import { CompositeDisposable, Disposable } from '../lifecycle';
|
||||
import { DropTargetTargetModel } from './droptarget';
|
||||
|
||||
export class DropTargetAnchorContainer extends CompositeDisposable {
|
||||
private _model:
|
||||
| { root: HTMLElement; overlay: HTMLElement; changed: boolean }
|
||||
| undefined;
|
||||
|
||||
private _outline: HTMLElement | undefined;
|
||||
|
||||
private _disabled = false;
|
||||
|
||||
get disabled(): boolean {
|
||||
return this._disabled;
|
||||
}
|
||||
|
||||
set disabled(value: boolean) {
|
||||
if (this.disabled === value) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._disabled = value;
|
||||
|
||||
if (value) {
|
||||
this.model?.clear();
|
||||
}
|
||||
}
|
||||
|
||||
get model(): DropTargetTargetModel | undefined {
|
||||
if (this.disabled) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
clear: () => {
|
||||
if (this._model) {
|
||||
this._model.root.parentElement?.removeChild(
|
||||
this._model.root
|
||||
);
|
||||
}
|
||||
this._model = undefined;
|
||||
},
|
||||
exists: () => {
|
||||
return !!this._model;
|
||||
},
|
||||
getElements: (event?: DragEvent, outline?: HTMLElement) => {
|
||||
const changed = this._outline !== outline;
|
||||
this._outline = outline;
|
||||
|
||||
if (this._model) {
|
||||
this._model.changed = changed;
|
||||
return this._model;
|
||||
}
|
||||
|
||||
const container = this.createContainer();
|
||||
const anchor = this.createAnchor();
|
||||
|
||||
this._model = { root: container, overlay: anchor, changed };
|
||||
|
||||
container.appendChild(anchor);
|
||||
this.element.appendChild(container);
|
||||
|
||||
if (event?.target instanceof HTMLElement) {
|
||||
const targetBox = event.target.getBoundingClientRect();
|
||||
const box = this.element.getBoundingClientRect();
|
||||
|
||||
anchor.style.left = `${targetBox.left - box.left}px`;
|
||||
anchor.style.top = `${targetBox.top - box.top}px`;
|
||||
}
|
||||
|
||||
return this._model;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
constructor(readonly element: HTMLElement, options: { disabled: boolean }) {
|
||||
super();
|
||||
|
||||
this._disabled = options.disabled;
|
||||
|
||||
this.addDisposables(
|
||||
Disposable.from(() => {
|
||||
this.model?.clear();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private createContainer(): HTMLElement {
|
||||
const el = document.createElement('div');
|
||||
el.className = 'dv-drop-target-container';
|
||||
|
||||
return el;
|
||||
}
|
||||
|
||||
private createAnchor(): HTMLElement {
|
||||
const el = document.createElement('div');
|
||||
el.className = 'dv-drop-target-anchor';
|
||||
el.style.visibility = 'hidden';
|
||||
|
||||
return el;
|
||||
}
|
||||
}
|
@ -1,8 +1,7 @@
|
||||
.dv-drop-target {
|
||||
.drop-target {
|
||||
position: relative;
|
||||
--dv-transition-duration: 70ms;
|
||||
|
||||
> .dv-drop-target-dropzone {
|
||||
> .drop-target-dropzone {
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
@ -11,43 +10,32 @@
|
||||
z-index: 1000;
|
||||
pointer-events: none;
|
||||
|
||||
> .dv-drop-target-selection {
|
||||
> .drop-target-selection {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
border: var(--dv-drag-over-border);
|
||||
background-color: var(--dv-drag-over-background-color);
|
||||
transition: top var(--dv-transition-duration) ease-out,
|
||||
left var(--dv-transition-duration) ease-out,
|
||||
width var(--dv-transition-duration) ease-out,
|
||||
height var(--dv-transition-duration) ease-out,
|
||||
opacity var(--dv-transition-duration) ease-out;
|
||||
transition: top 70ms ease-out, left 70ms ease-out,
|
||||
width 70ms ease-out, height 70ms ease-out,
|
||||
opacity 0.15s ease-out;
|
||||
will-change: transform;
|
||||
pointer-events: none;
|
||||
|
||||
&.dv-drop-target-top {
|
||||
&.dv-drop-target-small-vertical {
|
||||
border-top: 1px solid var(--dv-drag-over-border-color);
|
||||
}
|
||||
&.small-top {
|
||||
border-top: 1px solid var(--dv-drag-over-border-color);
|
||||
}
|
||||
|
||||
&.dv-drop-target-bottom {
|
||||
&.dv-drop-target-small-vertical {
|
||||
border-bottom: 1px solid var(--dv-drag-over-border-color);
|
||||
}
|
||||
&.small-bottom {
|
||||
border-bottom: 1px solid var(--dv-drag-over-border-color);
|
||||
}
|
||||
|
||||
&.dv-drop-target-left {
|
||||
&.dv-drop-target-small-horizontal {
|
||||
border-left: 1px solid var(--dv-drag-over-border-color);
|
||||
}
|
||||
&.small-left {
|
||||
border-left: 1px solid var(--dv-drag-over-border-color);
|
||||
}
|
||||
|
||||
&.dv-drop-target-right {
|
||||
&.dv-drop-target-small-horizontal {
|
||||
border-right: 1px solid var(--dv-drag-over-border-color);
|
||||
}
|
||||
&.small-right {
|
||||
border-right: 1px solid var(--dv-drag-over-border-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,35 +1,12 @@
|
||||
import { toggleClass } from '../dom';
|
||||
import { DockviewEvent, Emitter, Event } from '../events';
|
||||
import { Emitter, Event } from '../events';
|
||||
import { CompositeDisposable } from '../lifecycle';
|
||||
import { DragAndDropObserver } from './dnd';
|
||||
import { clamp } from '../math';
|
||||
import { Direction } from '../gridview/baseComponentGridview';
|
||||
|
||||
export interface DroptargetEvent {
|
||||
readonly position: Position;
|
||||
readonly nativeEvent: DragEvent;
|
||||
}
|
||||
|
||||
export class WillShowOverlayEvent
|
||||
extends DockviewEvent
|
||||
implements DroptargetEvent
|
||||
{
|
||||
get nativeEvent(): DragEvent {
|
||||
return this.options.nativeEvent;
|
||||
}
|
||||
|
||||
get position(): Position {
|
||||
return this.options.position;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private readonly options: {
|
||||
nativeEvent: DragEvent;
|
||||
position: Position;
|
||||
}
|
||||
) {
|
||||
super();
|
||||
}
|
||||
function numberOrFallback(maybeNumber: any, fallback: number): number {
|
||||
return typeof maybeNumber === 'number' ? maybeNumber : fallback;
|
||||
}
|
||||
|
||||
export function directionToPosition(direction: Direction): Position {
|
||||
@ -66,54 +43,16 @@ export function positionToDirection(position: Position): Direction {
|
||||
}
|
||||
}
|
||||
|
||||
export interface DroptargetEvent {
|
||||
readonly position: Position;
|
||||
readonly nativeEvent: DragEvent;
|
||||
}
|
||||
|
||||
export type Position = 'top' | 'bottom' | 'left' | 'right' | 'center';
|
||||
|
||||
export type CanDisplayOverlay = (
|
||||
dragEvent: DragEvent,
|
||||
state: Position
|
||||
) => boolean;
|
||||
|
||||
export type MeasuredValue = { value: number; type: 'pixels' | 'percentage' };
|
||||
|
||||
export type DroptargetOverlayModel = {
|
||||
size?: MeasuredValue;
|
||||
activationSize?: MeasuredValue;
|
||||
};
|
||||
|
||||
const DEFAULT_ACTIVATION_SIZE: MeasuredValue = {
|
||||
value: 20,
|
||||
type: 'percentage',
|
||||
};
|
||||
|
||||
const DEFAULT_SIZE: MeasuredValue = {
|
||||
value: 50,
|
||||
type: 'percentage',
|
||||
};
|
||||
|
||||
const SMALL_WIDTH_BOUNDARY = 100;
|
||||
const SMALL_HEIGHT_BOUNDARY = 100;
|
||||
|
||||
export interface DropTargetTargetModel {
|
||||
getElements(
|
||||
event?: DragEvent,
|
||||
outline?: HTMLElement
|
||||
): {
|
||||
root: HTMLElement;
|
||||
overlay: HTMLElement;
|
||||
changed: boolean;
|
||||
};
|
||||
exists(): boolean;
|
||||
clear(): void;
|
||||
}
|
||||
|
||||
export interface DroptargetOptions {
|
||||
canDisplayOverlay: CanDisplayOverlay;
|
||||
acceptedTargetZones: Position[];
|
||||
overlayModel?: DroptargetOverlayModel;
|
||||
getOverrideTarget?: () => DropTargetTargetModel | undefined;
|
||||
className?: string;
|
||||
getOverlayOutline?: () => HTMLElement | null;
|
||||
}
|
||||
export type CanDisplayOverlay =
|
||||
| boolean
|
||||
| ((dragEvent: DragEvent, state: Position) => boolean);
|
||||
|
||||
export class Droptarget extends CompositeDisposable {
|
||||
private targetElement: HTMLElement | undefined;
|
||||
@ -124,204 +63,131 @@ export class Droptarget extends CompositeDisposable {
|
||||
private readonly _onDrop = new Emitter<DroptargetEvent>();
|
||||
readonly onDrop: Event<DroptargetEvent> = this._onDrop.event;
|
||||
|
||||
private readonly _onWillShowOverlay = new Emitter<WillShowOverlayEvent>();
|
||||
readonly onWillShowOverlay: Event<WillShowOverlayEvent> =
|
||||
this._onWillShowOverlay.event;
|
||||
|
||||
readonly dnd: DragAndDropObserver;
|
||||
|
||||
private static USED_EVENT_ID = '__dockview_droptarget_event_is_used__';
|
||||
|
||||
private static ACTUAL_TARGET: Droptarget | undefined;
|
||||
|
||||
private _disabled: boolean;
|
||||
|
||||
get disabled(): boolean {
|
||||
return this._disabled;
|
||||
}
|
||||
|
||||
set disabled(value: boolean) {
|
||||
this._disabled = value;
|
||||
}
|
||||
|
||||
get state(): Position | undefined {
|
||||
return this._state;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private readonly element: HTMLElement,
|
||||
private readonly options: DroptargetOptions
|
||||
private readonly options: {
|
||||
canDisplayOverlay: CanDisplayOverlay;
|
||||
acceptedTargetZones: Position[];
|
||||
overlayModel?: {
|
||||
size?: { value: number; type: 'pixels' | 'percentage' };
|
||||
activationSize?: {
|
||||
value: number;
|
||||
type: 'pixels' | 'percentage';
|
||||
};
|
||||
};
|
||||
}
|
||||
) {
|
||||
super();
|
||||
|
||||
this._disabled = false;
|
||||
|
||||
// use a set to take advantage of #<set>.has
|
||||
this._acceptedTargetZonesSet = new Set(
|
||||
this.options.acceptedTargetZones
|
||||
);
|
||||
|
||||
this.dnd = new DragAndDropObserver(this.element, {
|
||||
onDragEnter: () => {
|
||||
this.options.getOverrideTarget?.()?.getElements();
|
||||
},
|
||||
onDragOver: (e) => {
|
||||
Droptarget.ACTUAL_TARGET = this;
|
||||
|
||||
const overrideTraget = this.options.getOverrideTarget?.();
|
||||
|
||||
if (this._acceptedTargetZonesSet.size === 0) {
|
||||
if (overrideTraget) {
|
||||
this.addDisposables(
|
||||
this._onDrop,
|
||||
new DragAndDropObserver(this.element, {
|
||||
onDragEnter: () => undefined,
|
||||
onDragOver: (e) => {
|
||||
if (this._acceptedTargetZonesSet.size === 0) {
|
||||
this.removeDropTarget();
|
||||
return;
|
||||
}
|
||||
this.removeDropTarget();
|
||||
return;
|
||||
}
|
||||
|
||||
const target =
|
||||
this.options.getOverlayOutline?.() ?? this.element;
|
||||
const width = this.element.clientWidth;
|
||||
const height = this.element.clientHeight;
|
||||
|
||||
const width = target.offsetWidth;
|
||||
const height = target.offsetHeight;
|
||||
if (width === 0 || height === 0) {
|
||||
return; // avoid div!0
|
||||
}
|
||||
|
||||
if (width === 0 || height === 0) {
|
||||
return; // avoid div!0
|
||||
}
|
||||
const rect = (
|
||||
e.currentTarget as HTMLElement
|
||||
).getBoundingClientRect();
|
||||
const x = e.clientX - rect.left;
|
||||
const y = e.clientY - rect.top;
|
||||
|
||||
const rect = (
|
||||
e.currentTarget as HTMLElement
|
||||
).getBoundingClientRect();
|
||||
const x = (e.clientX ?? 0) - rect.left;
|
||||
const y = (e.clientY ?? 0) - rect.top;
|
||||
const quadrant = this.calculateQuadrant(
|
||||
this._acceptedTargetZonesSet,
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height
|
||||
);
|
||||
|
||||
const quadrant = this.calculateQuadrant(
|
||||
this._acceptedTargetZonesSet,
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height
|
||||
);
|
||||
|
||||
/**
|
||||
* If the event has already been used by another DropTarget instance
|
||||
* then don't show a second drop target, only one target should be
|
||||
* active at any one time
|
||||
*/
|
||||
if (this.isAlreadyUsed(e) || quadrant === null) {
|
||||
// no drop target should be displayed
|
||||
this.removeDropTarget();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.options.canDisplayOverlay(e, quadrant)) {
|
||||
if (overrideTraget) {
|
||||
/**
|
||||
* If the event has already been used by another DropTarget instance
|
||||
* then don't show a second drop target, only one target should be
|
||||
* active at any one time
|
||||
*/
|
||||
if (this.isAlreadyUsed(e) || quadrant === null) {
|
||||
// no drop target should be displayed
|
||||
this.removeDropTarget();
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof this.options.canDisplayOverlay === 'boolean') {
|
||||
if (!this.options.canDisplayOverlay) {
|
||||
this.removeDropTarget();
|
||||
return;
|
||||
}
|
||||
} else if (!this.options.canDisplayOverlay(e, quadrant)) {
|
||||
this.removeDropTarget();
|
||||
return;
|
||||
}
|
||||
|
||||
this.markAsUsed(e);
|
||||
|
||||
if (!this.targetElement) {
|
||||
this.targetElement = document.createElement('div');
|
||||
this.targetElement.className = 'drop-target-dropzone';
|
||||
this.overlayElement = document.createElement('div');
|
||||
this.overlayElement.className = 'drop-target-selection';
|
||||
this._state = 'center';
|
||||
this.targetElement.appendChild(this.overlayElement);
|
||||
|
||||
this.element.classList.add('drop-target');
|
||||
this.element.append(this.targetElement);
|
||||
}
|
||||
|
||||
this.toggleClasses(quadrant, width, height);
|
||||
|
||||
this.setState(quadrant);
|
||||
},
|
||||
onDragLeave: () => {
|
||||
this.removeDropTarget();
|
||||
return;
|
||||
}
|
||||
|
||||
const willShowOverlayEvent = new WillShowOverlayEvent({
|
||||
nativeEvent: e,
|
||||
position: quadrant,
|
||||
});
|
||||
|
||||
/**
|
||||
* Provide an opportunity to prevent the overlay appearing and in turn
|
||||
* any dnd behaviours
|
||||
*/
|
||||
this._onWillShowOverlay.fire(willShowOverlayEvent);
|
||||
|
||||
if (willShowOverlayEvent.defaultPrevented) {
|
||||
},
|
||||
onDragEnd: () => {
|
||||
this.removeDropTarget();
|
||||
return;
|
||||
}
|
||||
},
|
||||
onDrop: (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
this.markAsUsed(e);
|
||||
const state = this._state;
|
||||
|
||||
if (overrideTraget) {
|
||||
//
|
||||
} else if (!this.targetElement) {
|
||||
this.targetElement = document.createElement('div');
|
||||
this.targetElement.className = 'dv-drop-target-dropzone';
|
||||
this.overlayElement = document.createElement('div');
|
||||
this.overlayElement.className = 'dv-drop-target-selection';
|
||||
this._state = 'center';
|
||||
this.targetElement.appendChild(this.overlayElement);
|
||||
this.removeDropTarget();
|
||||
|
||||
target.classList.add('dv-drop-target');
|
||||
target.append(this.targetElement);
|
||||
|
||||
// this.overlayElement.style.opacity = '0';
|
||||
|
||||
// requestAnimationFrame(() => {
|
||||
// if (this.overlayElement) {
|
||||
// this.overlayElement.style.opacity = '';
|
||||
// }
|
||||
// });
|
||||
}
|
||||
|
||||
this.toggleClasses(quadrant, width, height);
|
||||
|
||||
this._state = quadrant;
|
||||
},
|
||||
onDragLeave: () => {
|
||||
const target = this.options.getOverrideTarget?.();
|
||||
|
||||
if (target) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.removeDropTarget();
|
||||
},
|
||||
onDragEnd: (e) => {
|
||||
const target = this.options.getOverrideTarget?.();
|
||||
|
||||
if (target && Droptarget.ACTUAL_TARGET === this) {
|
||||
if (this._state) {
|
||||
if (state) {
|
||||
// only stop the propagation of the event if we are dealing with it
|
||||
// which is only when the target has state
|
||||
e.stopPropagation();
|
||||
this._onDrop.fire({
|
||||
position: this._state,
|
||||
nativeEvent: e,
|
||||
});
|
||||
this._onDrop.fire({ position: state, nativeEvent: e });
|
||||
}
|
||||
}
|
||||
|
||||
this.removeDropTarget();
|
||||
|
||||
target?.clear();
|
||||
},
|
||||
onDrop: (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const state = this._state;
|
||||
|
||||
this.removeDropTarget();
|
||||
|
||||
this.options.getOverrideTarget?.()?.clear();
|
||||
|
||||
if (state) {
|
||||
// only stop the propagation of the event if we are dealing with it
|
||||
// which is only when the target has state
|
||||
e.stopPropagation();
|
||||
this._onDrop.fire({ position: state, nativeEvent: e });
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
this.addDisposables(this._onDrop, this._onWillShowOverlay, this.dnd);
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
setTargetZones(acceptedTargetZones: Position[]): void {
|
||||
this._acceptedTargetZonesSet = new Set(acceptedTargetZones);
|
||||
}
|
||||
|
||||
setOverlayModel(model: DroptargetOverlayModel): void {
|
||||
this.options.overlayModel = model;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.removeDropTarget();
|
||||
super.dispose();
|
||||
@ -335,7 +201,7 @@ export class Droptarget extends CompositeDisposable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check is the event has already been used by another instance of DropTarget
|
||||
* Check is the event has already been used by another instance od DropTarget
|
||||
*/
|
||||
private isAlreadyUsed(event: DragEvent): boolean {
|
||||
const value = (event as any)[Droptarget.USED_EVENT_ID];
|
||||
@ -347,14 +213,12 @@ export class Droptarget extends CompositeDisposable {
|
||||
width: number,
|
||||
height: number
|
||||
): void {
|
||||
const target = this.options.getOverrideTarget?.();
|
||||
|
||||
if (!target && !this.overlayElement) {
|
||||
if (!this.overlayElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isSmallX = width < SMALL_WIDTH_BOUNDARY;
|
||||
const isSmallY = height < SMALL_HEIGHT_BOUNDARY;
|
||||
const isSmallX = width < 100;
|
||||
const isSmallY = height < 100;
|
||||
|
||||
const isLeft = quadrant === 'left';
|
||||
const isRight = quadrant === 'right';
|
||||
@ -366,175 +230,68 @@ export class Droptarget extends CompositeDisposable {
|
||||
const topClass = !isSmallY && isTop;
|
||||
const bottomClass = !isSmallY && isBottom;
|
||||
|
||||
let size = 1;
|
||||
let size = 0.5;
|
||||
|
||||
const sizeOptions = this.options.overlayModel?.size ?? DEFAULT_SIZE;
|
||||
if (this.options.overlayModel?.size?.type === 'percentage') {
|
||||
size = clamp(this.options.overlayModel.size.value, 0, 100) / 100;
|
||||
}
|
||||
|
||||
if (sizeOptions.type === 'percentage') {
|
||||
size = clamp(sizeOptions.value, 0, 100) / 100;
|
||||
} else {
|
||||
if (this.options.overlayModel?.size?.type === 'pixels') {
|
||||
if (rightClass || leftClass) {
|
||||
size = clamp(0, sizeOptions.value, width) / width;
|
||||
size =
|
||||
clamp(0, this.options.overlayModel.size.value, width) /
|
||||
width;
|
||||
}
|
||||
if (topClass || bottomClass) {
|
||||
size = clamp(0, sizeOptions.value, height) / height;
|
||||
size =
|
||||
clamp(0, this.options.overlayModel.size.value, height) /
|
||||
height;
|
||||
}
|
||||
}
|
||||
|
||||
if (target) {
|
||||
const outlineEl =
|
||||
this.options.getOverlayOutline?.() ?? this.element;
|
||||
const elBox = outlineEl.getBoundingClientRect();
|
||||
const translate = (1 - size) / 2;
|
||||
const scale = size;
|
||||
|
||||
const ta = target.getElements(undefined, outlineEl);
|
||||
const el = ta.root;
|
||||
const overlay = ta.overlay;
|
||||
let transform: string;
|
||||
|
||||
const bigbox = el.getBoundingClientRect();
|
||||
|
||||
const rootTop = elBox.top - bigbox.top;
|
||||
const rootLeft = elBox.left - bigbox.left;
|
||||
|
||||
const box = {
|
||||
top: rootTop,
|
||||
left: rootLeft,
|
||||
width: width,
|
||||
height: height,
|
||||
};
|
||||
|
||||
if (rightClass) {
|
||||
box.left = rootLeft + width * (1 - size);
|
||||
box.width = width * size;
|
||||
} else if (leftClass) {
|
||||
box.width = width * size;
|
||||
} else if (topClass) {
|
||||
box.height = height * size;
|
||||
} else if (bottomClass) {
|
||||
box.top = rootTop + height * (1 - size);
|
||||
box.height = height * size;
|
||||
}
|
||||
|
||||
if (isSmallX && isLeft) {
|
||||
box.width = 4;
|
||||
}
|
||||
if (isSmallX && isRight) {
|
||||
box.left = rootLeft + width - 4;
|
||||
box.width = 4;
|
||||
}
|
||||
|
||||
const topPx = `${Math.round(box.top)}px`;
|
||||
const leftPx = `${Math.round(box.left)}px`;
|
||||
const widthPx = `${Math.round(box.width)}px`;
|
||||
const heightPx = `${Math.round(box.height)}px`;
|
||||
|
||||
if (
|
||||
overlay.style.top === topPx &&
|
||||
overlay.style.left === leftPx &&
|
||||
overlay.style.width === widthPx &&
|
||||
overlay.style.height === heightPx
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
overlay.style.top = topPx;
|
||||
overlay.style.left = leftPx;
|
||||
overlay.style.width = widthPx;
|
||||
overlay.style.height = heightPx;
|
||||
overlay.style.visibility = 'visible';
|
||||
|
||||
overlay.className = `dv-drop-target-anchor${
|
||||
this.options.className ? ` ${this.options.className}` : ''
|
||||
}`;
|
||||
|
||||
toggleClass(overlay, 'dv-drop-target-left', isLeft);
|
||||
toggleClass(overlay, 'dv-drop-target-right', isRight);
|
||||
toggleClass(overlay, 'dv-drop-target-top', isTop);
|
||||
toggleClass(overlay, 'dv-drop-target-bottom', isBottom);
|
||||
toggleClass(
|
||||
overlay,
|
||||
'dv-drop-target-center',
|
||||
quadrant === 'center'
|
||||
);
|
||||
|
||||
if (ta.changed) {
|
||||
toggleClass(
|
||||
overlay,
|
||||
'dv-drop-target-anchor-container-changed',
|
||||
true
|
||||
);
|
||||
setTimeout(() => {
|
||||
toggleClass(
|
||||
overlay,
|
||||
'dv-drop-target-anchor-container-changed',
|
||||
false
|
||||
);
|
||||
}, 10);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.overlayElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
const box = { top: '0px', left: '0px', width: '100%', height: '100%' };
|
||||
|
||||
/**
|
||||
* You can also achieve the overlay placement using the transform CSS property
|
||||
* to translate and scale the element however this has the undesired effect of
|
||||
* 'skewing' the element. Comment left here for anybody that ever revisits this.
|
||||
*
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/CSS/transform
|
||||
*
|
||||
* right
|
||||
* translateX(${100 * (1 - size) / 2}%) scaleX(${scale})
|
||||
*
|
||||
* left
|
||||
* translateX(-${100 * (1 - size) / 2}%) scaleX(${scale})
|
||||
*
|
||||
* top
|
||||
* translateY(-${100 * (1 - size) / 2}%) scaleY(${scale})
|
||||
*
|
||||
* bottom
|
||||
* translateY(${100 * (1 - size) / 2}%) scaleY(${scale})
|
||||
*/
|
||||
if (rightClass) {
|
||||
box.left = `${100 * (1 - size)}%`;
|
||||
box.width = `${100 * size}%`;
|
||||
transform = `translateX(${100 * translate}%) scaleX(${scale})`;
|
||||
} else if (leftClass) {
|
||||
box.width = `${100 * size}%`;
|
||||
transform = `translateX(-${100 * translate}%) scaleX(${scale})`;
|
||||
} else if (topClass) {
|
||||
box.height = `${100 * size}%`;
|
||||
transform = `translateY(-${100 * translate}%) scaleY(${scale})`;
|
||||
} else if (bottomClass) {
|
||||
box.top = `${100 * (1 - size)}%`;
|
||||
box.height = `${100 * size}%`;
|
||||
transform = `translateY(${100 * translate}%) scaleY(${scale})`;
|
||||
} else {
|
||||
transform = '';
|
||||
}
|
||||
|
||||
this.overlayElement.style.top = box.top;
|
||||
this.overlayElement.style.left = box.left;
|
||||
this.overlayElement.style.width = box.width;
|
||||
this.overlayElement.style.height = box.height;
|
||||
this.overlayElement.style.transform = transform;
|
||||
|
||||
toggleClass(
|
||||
this.overlayElement,
|
||||
'dv-drop-target-small-vertical',
|
||||
isSmallY
|
||||
);
|
||||
toggleClass(
|
||||
this.overlayElement,
|
||||
'dv-drop-target-small-horizontal',
|
||||
isSmallX
|
||||
);
|
||||
toggleClass(this.overlayElement, 'dv-drop-target-left', isLeft);
|
||||
toggleClass(this.overlayElement, 'dv-drop-target-right', isRight);
|
||||
toggleClass(this.overlayElement, 'dv-drop-target-top', isTop);
|
||||
toggleClass(this.overlayElement, 'dv-drop-target-bottom', isBottom);
|
||||
toggleClass(
|
||||
this.overlayElement,
|
||||
'dv-drop-target-center',
|
||||
quadrant === 'center'
|
||||
);
|
||||
toggleClass(this.overlayElement, 'small-right', isSmallX && isRight);
|
||||
toggleClass(this.overlayElement, 'small-left', isSmallX && isLeft);
|
||||
toggleClass(this.overlayElement, 'small-top', isSmallY && isTop);
|
||||
toggleClass(this.overlayElement, 'small-bottom', isSmallY && isBottom);
|
||||
}
|
||||
|
||||
private setState(quadrant: Position): void {
|
||||
switch (quadrant) {
|
||||
case 'top':
|
||||
this._state = 'top';
|
||||
break;
|
||||
case 'left':
|
||||
this._state = 'left';
|
||||
break;
|
||||
case 'bottom':
|
||||
this._state = 'bottom';
|
||||
break;
|
||||
case 'right':
|
||||
this._state = 'right';
|
||||
break;
|
||||
case 'center':
|
||||
this._state = 'center';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private calculateQuadrant(
|
||||
@ -544,11 +301,14 @@ export class Droptarget extends CompositeDisposable {
|
||||
width: number,
|
||||
height: number
|
||||
): Position | null {
|
||||
const activationSizeOptions =
|
||||
this.options.overlayModel?.activationSize ??
|
||||
DEFAULT_ACTIVATION_SIZE;
|
||||
const isPercentage =
|
||||
this.options.overlayModel?.activationSize === undefined ||
|
||||
this.options.overlayModel?.activationSize?.type === 'percentage';
|
||||
|
||||
const isPercentage = activationSizeOptions.type === 'percentage';
|
||||
const value = numberOrFallback(
|
||||
this.options?.overlayModel?.activationSize?.value,
|
||||
20
|
||||
);
|
||||
|
||||
if (isPercentage) {
|
||||
return calculateQuadrantAsPercentage(
|
||||
@ -557,7 +317,7 @@ export class Droptarget extends CompositeDisposable {
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
activationSizeOptions.value
|
||||
value
|
||||
);
|
||||
}
|
||||
|
||||
@ -567,19 +327,17 @@ export class Droptarget extends CompositeDisposable {
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
activationSizeOptions.value
|
||||
value
|
||||
);
|
||||
}
|
||||
|
||||
private removeDropTarget(): void {
|
||||
if (this.targetElement) {
|
||||
this._state = undefined;
|
||||
this.targetElement.parentElement?.classList.remove(
|
||||
'dv-drop-target'
|
||||
);
|
||||
this.targetElement.remove();
|
||||
this.element.removeChild(this.targetElement);
|
||||
this.targetElement = undefined;
|
||||
this.overlayElement = undefined;
|
||||
this.element.classList.remove('drop-target');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,17 +2,13 @@ import { addClasses, removeClasses } from '../dom';
|
||||
|
||||
export function addGhostImage(
|
||||
dataTransfer: DataTransfer,
|
||||
ghostElement: HTMLElement,
|
||||
options?: { x?: number; y?: number }
|
||||
ghostElement: HTMLElement
|
||||
): void {
|
||||
// class dockview provides to force ghost image to be drawn on a different layer and prevent weird rendering issues
|
||||
addClasses(ghostElement, 'dv-dragged');
|
||||
|
||||
// move the element off-screen initially otherwise it may in some cases be rendered at (0,0) momentarily
|
||||
ghostElement.style.top = '-9999px';
|
||||
|
||||
document.body.appendChild(ghostElement);
|
||||
dataTransfer.setDragImage(ghostElement, options?.x ?? 0, options?.y ?? 0);
|
||||
dataTransfer.setDragImage(ghostElement, 0, 0);
|
||||
|
||||
setTimeout(() => {
|
||||
removeClasses(ghostElement, 'dv-dragged');
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { DockviewComponent } from '../dockview/dockviewComponent';
|
||||
import { DockviewGroupPanel } from '../dockview/dockviewGroupPanel';
|
||||
import { quasiPreventDefault } from '../dom';
|
||||
import { addDisposableListener } from '../events';
|
||||
@ -13,7 +12,7 @@ export class GroupDragHandler extends DragHandler {
|
||||
|
||||
constructor(
|
||||
element: HTMLElement,
|
||||
private readonly accessor: DockviewComponent,
|
||||
private readonly accessorId: string,
|
||||
private readonly group: DockviewGroupPanel
|
||||
) {
|
||||
super(element);
|
||||
@ -21,7 +20,7 @@ export class GroupDragHandler extends DragHandler {
|
||||
this.addDisposables(
|
||||
addDisposableListener(
|
||||
element,
|
||||
'pointerdown',
|
||||
'mousedown',
|
||||
(e) => {
|
||||
if (e.shiftKey) {
|
||||
/**
|
||||
@ -38,17 +37,15 @@ export class GroupDragHandler extends DragHandler {
|
||||
}
|
||||
|
||||
override isCancelled(_event: DragEvent): boolean {
|
||||
if (this.group.api.location.type === 'floating' && !_event.shiftKey) {
|
||||
if (this.group.api.isFloating && !_event.shiftKey) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
getData(dragEvent: DragEvent): IDisposable {
|
||||
const dataTransfer = dragEvent.dataTransfer;
|
||||
|
||||
getData(dataTransfer: DataTransfer | null): IDisposable {
|
||||
this.panelTransfer.setData(
|
||||
[new PanelTransfer(this.accessor.id, this.group.id, null)],
|
||||
[new PanelTransfer(this.accessorId, this.group.id, null)],
|
||||
PanelTransfer.prototype
|
||||
);
|
||||
|
||||
@ -72,11 +69,9 @@ export class GroupDragHandler extends DragHandler {
|
||||
ghostElement.style.lineHeight = '20px';
|
||||
ghostElement.style.borderRadius = '12px';
|
||||
ghostElement.style.position = 'absolute';
|
||||
ghostElement.style.pointerEvents = 'none';
|
||||
ghostElement.style.top = '-9999px';
|
||||
ghostElement.textContent = `Multiple Panels (${this.group.size})`;
|
||||
|
||||
addGhostImage(dataTransfer, ghostElement, { y: -10, x: 30 });
|
||||
addGhostImage(dataTransfer, ghostElement);
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -26,18 +26,16 @@
|
||||
}
|
||||
|
||||
.dv-resize-container {
|
||||
--dv-overlay-z-index: var(--dv-overlay-z-index, 999);
|
||||
|
||||
position: absolute;
|
||||
z-index: calc(var(--dv-overlay-z-index) - 2);
|
||||
z-index: 997;
|
||||
|
||||
&.dv-bring-to-front {
|
||||
z-index: 998;
|
||||
}
|
||||
|
||||
border: 1px solid var(--dv-tab-divider-color);
|
||||
box-shadow: var(--dv-floating-box-shadow);
|
||||
|
||||
&.dv-hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.dv-resize-container-dragging {
|
||||
opacity: 0.5;
|
||||
}
|
||||
@ -47,7 +45,7 @@
|
||||
width: calc(100% - 8px);
|
||||
left: 4px;
|
||||
top: -2px;
|
||||
z-index: var(--dv-overlay-z-index);
|
||||
z-index: 999;
|
||||
position: absolute;
|
||||
cursor: ns-resize;
|
||||
}
|
||||
@ -57,7 +55,7 @@
|
||||
width: calc(100% - 8px);
|
||||
left: 4px;
|
||||
bottom: -2px;
|
||||
z-index: var(--dv-overlay-z-index);
|
||||
z-index: 999;
|
||||
position: absolute;
|
||||
cursor: ns-resize;
|
||||
}
|
||||
@ -67,7 +65,7 @@
|
||||
width: 4px;
|
||||
left: -2px;
|
||||
top: 4px;
|
||||
z-index: var(--dv-overlay-z-index);
|
||||
z-index: 999;
|
||||
position: absolute;
|
||||
cursor: ew-resize;
|
||||
}
|
||||
@ -77,7 +75,7 @@
|
||||
width: 4px;
|
||||
right: -2px;
|
||||
top: 4px;
|
||||
z-index: var(--dv-overlay-z-index);
|
||||
z-index: 999;
|
||||
position: absolute;
|
||||
cursor: ew-resize;
|
||||
}
|
||||
@ -87,7 +85,7 @@
|
||||
width: 4px;
|
||||
top: -2px;
|
||||
left: -2px;
|
||||
z-index: var(--dv-overlay-z-index);
|
||||
z-index: 999;
|
||||
position: absolute;
|
||||
cursor: nw-resize;
|
||||
}
|
||||
@ -97,7 +95,7 @@
|
||||
width: 4px;
|
||||
right: -2px;
|
||||
top: -2px;
|
||||
z-index: var(--dv-overlay-z-index);
|
||||
z-index: 999;
|
||||
position: absolute;
|
||||
cursor: ne-resize;
|
||||
}
|
||||
@ -107,7 +105,7 @@
|
||||
width: 4px;
|
||||
left: -2px;
|
||||
bottom: -2px;
|
||||
z-index: var(--dv-overlay-z-index);
|
||||
z-index: 999;
|
||||
position: absolute;
|
||||
cursor: sw-resize;
|
||||
}
|
||||
@ -117,7 +115,7 @@
|
||||
width: 4px;
|
||||
right: -2px;
|
||||
bottom: -2px;
|
||||
z-index: var(--dv-overlay-z-index);
|
||||
z-index: 999;
|
||||
position: absolute;
|
||||
cursor: se-resize;
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import {
|
||||
disableIframePointEvents,
|
||||
getElementsByTagName,
|
||||
quasiDefaultPrevented,
|
||||
toggleClass,
|
||||
} from '../dom';
|
||||
@ -7,44 +7,28 @@ import {
|
||||
Emitter,
|
||||
Event,
|
||||
addDisposableListener,
|
||||
addDisposableWindowListener,
|
||||
} from '../events';
|
||||
import { CompositeDisposable, MutableDisposable } from '../lifecycle';
|
||||
import { clamp } from '../math';
|
||||
import { AnchoredBox } from '../types';
|
||||
|
||||
class AriaLevelTracker {
|
||||
private _orderedList: HTMLElement[] = [];
|
||||
const bringElementToFront = (() => {
|
||||
let previous: HTMLElement | null = null;
|
||||
|
||||
push(element: HTMLElement): void {
|
||||
this._orderedList = [
|
||||
...this._orderedList.filter((item) => item !== element),
|
||||
element,
|
||||
];
|
||||
|
||||
this.update();
|
||||
}
|
||||
|
||||
destroy(element: HTMLElement): void {
|
||||
this._orderedList = this._orderedList.filter(
|
||||
(item) => item !== element
|
||||
);
|
||||
this.update();
|
||||
}
|
||||
|
||||
private update(): void {
|
||||
for (let i = 0; i < this._orderedList.length; i++) {
|
||||
this._orderedList[i].setAttribute('aria-level', `${i}`);
|
||||
this._orderedList[
|
||||
i
|
||||
].style.zIndex = `calc(var(--dv-overlay-z-index, 999) + ${i * 2})`;
|
||||
function pushToTop(element: HTMLElement) {
|
||||
if (previous !== element && previous !== null) {
|
||||
toggleClass(previous, 'dv-bring-to-front', false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const arialLevelTracker = new AriaLevelTracker();
|
||||
toggleClass(element, 'dv-bring-to-front', true);
|
||||
previous = element;
|
||||
}
|
||||
|
||||
return pushToTop;
|
||||
})();
|
||||
|
||||
export class Overlay extends CompositeDisposable {
|
||||
private readonly _element: HTMLElement = document.createElement('div');
|
||||
private _element: HTMLElement = document.createElement('div');
|
||||
|
||||
private readonly _onDidChange = new Emitter<void>();
|
||||
readonly onDidChange: Event<void> = this._onDidChange.event;
|
||||
@ -52,36 +36,19 @@ export class Overlay extends CompositeDisposable {
|
||||
private readonly _onDidChangeEnd = new Emitter<void>();
|
||||
readonly onDidChangeEnd: Event<void> = this._onDidChangeEnd.event;
|
||||
|
||||
private static readonly MINIMUM_HEIGHT = 20;
|
||||
private static readonly MINIMUM_WIDTH = 20;
|
||||
|
||||
private verticalAlignment: 'top' | 'bottom' | undefined;
|
||||
private horiziontalAlignment: 'left' | 'right' | undefined;
|
||||
|
||||
private _isVisible: boolean;
|
||||
|
||||
set minimumInViewportWidth(value: number | undefined) {
|
||||
this.options.minimumInViewportWidth = value;
|
||||
}
|
||||
|
||||
set minimumInViewportHeight(value: number | undefined) {
|
||||
this.options.minimumInViewportHeight = value;
|
||||
}
|
||||
|
||||
get element(): HTMLElement {
|
||||
return this._element;
|
||||
}
|
||||
|
||||
get isVisible(): boolean {
|
||||
return this._isVisible;
|
||||
}
|
||||
private static MINIMUM_HEIGHT = 20;
|
||||
private static MINIMUM_WIDTH = 20;
|
||||
|
||||
constructor(
|
||||
private readonly options: AnchoredBox & {
|
||||
private readonly options: {
|
||||
height: number;
|
||||
width: number;
|
||||
left: number;
|
||||
top: number;
|
||||
container: HTMLElement;
|
||||
content: HTMLElement;
|
||||
minimumInViewportWidth?: number;
|
||||
minimumInViewportHeight?: number;
|
||||
minimumInViewportWidth: number;
|
||||
minimumInViewportHeight: number;
|
||||
}
|
||||
) {
|
||||
super();
|
||||
@ -89,7 +56,6 @@ export class Overlay extends CompositeDisposable {
|
||||
this.addDisposables(this._onDidChange, this._onDidChangeEnd);
|
||||
|
||||
this._element.className = 'dv-resize-container';
|
||||
this._isVisible = true;
|
||||
|
||||
this.setupResize('top');
|
||||
this.setupResize('bottom');
|
||||
@ -107,55 +73,30 @@ export class Overlay extends CompositeDisposable {
|
||||
this.setBounds({
|
||||
height: this.options.height,
|
||||
width: this.options.width,
|
||||
...('top' in this.options && { top: this.options.top }),
|
||||
...('bottom' in this.options && { bottom: this.options.bottom }),
|
||||
...('left' in this.options && { left: this.options.left }),
|
||||
...('right' in this.options && { right: this.options.right }),
|
||||
top: this.options.top,
|
||||
left: this.options.left,
|
||||
});
|
||||
|
||||
arialLevelTracker.push(this._element);
|
||||
}
|
||||
|
||||
setVisible(isVisible: boolean): void {
|
||||
if (isVisible === this.isVisible) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._isVisible = isVisible;
|
||||
|
||||
toggleClass(this.element, 'dv-hidden', !this.isVisible);
|
||||
}
|
||||
|
||||
bringToFront(): void {
|
||||
arialLevelTracker.push(this._element);
|
||||
}
|
||||
|
||||
setBounds(bounds: Partial<AnchoredBox> = {}): void {
|
||||
setBounds(
|
||||
bounds: Partial<{
|
||||
height: number;
|
||||
width: number;
|
||||
top: number;
|
||||
left: number;
|
||||
}> = {}
|
||||
): void {
|
||||
if (typeof bounds.height === 'number') {
|
||||
this._element.style.height = `${bounds.height}px`;
|
||||
}
|
||||
if (typeof bounds.width === 'number') {
|
||||
this._element.style.width = `${bounds.width}px`;
|
||||
}
|
||||
if ('top' in bounds && typeof bounds.top === 'number') {
|
||||
if (typeof bounds.top === 'number') {
|
||||
this._element.style.top = `${bounds.top}px`;
|
||||
this._element.style.bottom = 'auto';
|
||||
this.verticalAlignment = 'top';
|
||||
}
|
||||
if ('bottom' in bounds && typeof bounds.bottom === 'number') {
|
||||
this._element.style.bottom = `${bounds.bottom}px`;
|
||||
this._element.style.top = 'auto';
|
||||
this.verticalAlignment = 'bottom';
|
||||
}
|
||||
if ('left' in bounds && typeof bounds.left === 'number') {
|
||||
if (typeof bounds.left === 'number') {
|
||||
this._element.style.left = `${bounds.left}px`;
|
||||
this._element.style.right = 'auto';
|
||||
this.horiziontalAlignment = 'left';
|
||||
}
|
||||
if ('right' in bounds && typeof bounds.right === 'number') {
|
||||
this._element.style.right = `${bounds.right}px`;
|
||||
this._element.style.left = 'auto';
|
||||
this.horiziontalAlignment = 'right';
|
||||
}
|
||||
|
||||
const containerRect = this.options.container.getBoundingClientRect();
|
||||
@ -164,80 +105,45 @@ export class Overlay extends CompositeDisposable {
|
||||
// region: ensure bounds within allowable limits
|
||||
|
||||
// a minimum width of minimumViewportWidth must be inside the viewport
|
||||
const xOffset = Math.max(0, this.getMinimumWidth(overlayRect.width));
|
||||
const xOffset = Math.max(
|
||||
0,
|
||||
overlayRect.width - this.options.minimumInViewportWidth
|
||||
);
|
||||
|
||||
// a minimum height of minimumViewportHeight must be inside the viewport
|
||||
const yOffset = Math.max(0, this.getMinimumHeight(overlayRect.height));
|
||||
const yOffset = Math.max(
|
||||
0,
|
||||
overlayRect.height - this.options.minimumInViewportHeight
|
||||
);
|
||||
|
||||
if (this.verticalAlignment === 'top') {
|
||||
const top = clamp(
|
||||
overlayRect.top - containerRect.top,
|
||||
-yOffset,
|
||||
Math.max(0, containerRect.height - overlayRect.height + yOffset)
|
||||
);
|
||||
this._element.style.top = `${top}px`;
|
||||
this._element.style.bottom = 'auto';
|
||||
}
|
||||
const left = clamp(
|
||||
overlayRect.left - containerRect.left,
|
||||
-xOffset,
|
||||
Math.max(0, containerRect.width - overlayRect.width + xOffset)
|
||||
);
|
||||
|
||||
if (this.verticalAlignment === 'bottom') {
|
||||
const bottom = clamp(
|
||||
containerRect.bottom - overlayRect.bottom,
|
||||
-yOffset,
|
||||
Math.max(0, containerRect.height - overlayRect.height + yOffset)
|
||||
);
|
||||
this._element.style.bottom = `${bottom}px`;
|
||||
this._element.style.top = 'auto';
|
||||
}
|
||||
const top = clamp(
|
||||
overlayRect.top - containerRect.top,
|
||||
-yOffset,
|
||||
Math.max(0, containerRect.height - overlayRect.height + yOffset)
|
||||
);
|
||||
|
||||
if (this.horiziontalAlignment === 'left') {
|
||||
const left = clamp(
|
||||
overlayRect.left - containerRect.left,
|
||||
-xOffset,
|
||||
Math.max(0, containerRect.width - overlayRect.width + xOffset)
|
||||
);
|
||||
this._element.style.left = `${left}px`;
|
||||
this._element.style.right = 'auto';
|
||||
}
|
||||
|
||||
if (this.horiziontalAlignment === 'right') {
|
||||
const right = clamp(
|
||||
containerRect.right - overlayRect.right,
|
||||
-xOffset,
|
||||
Math.max(0, containerRect.width - overlayRect.width + xOffset)
|
||||
);
|
||||
this._element.style.right = `${right}px`;
|
||||
this._element.style.left = 'auto';
|
||||
}
|
||||
this._element.style.left = `${left}px`;
|
||||
this._element.style.top = `${top}px`;
|
||||
|
||||
this._onDidChange.fire();
|
||||
}
|
||||
|
||||
toJSON(): AnchoredBox {
|
||||
toJSON(): { top: number; left: number; height: number; width: number } {
|
||||
const container = this.options.container.getBoundingClientRect();
|
||||
const element = this._element.getBoundingClientRect();
|
||||
|
||||
const result: any = {};
|
||||
|
||||
if (this.verticalAlignment === 'top') {
|
||||
result.top = parseFloat(this._element.style.top);
|
||||
} else if (this.verticalAlignment === 'bottom') {
|
||||
result.bottom = parseFloat(this._element.style.bottom);
|
||||
} else {
|
||||
result.top = element.top - container.top;
|
||||
}
|
||||
|
||||
if (this.horiziontalAlignment === 'left') {
|
||||
result.left = parseFloat(this._element.style.left);
|
||||
} else if (this.horiziontalAlignment === 'right') {
|
||||
result.right = parseFloat(this._element.style.right);
|
||||
} else {
|
||||
result.left = element.left - container.left;
|
||||
}
|
||||
|
||||
result.width = element.width;
|
||||
result.height = element.height;
|
||||
|
||||
return result;
|
||||
return {
|
||||
top: element.top - container.top,
|
||||
left: element.left - container.left,
|
||||
width: element.width,
|
||||
height: element.height,
|
||||
};
|
||||
}
|
||||
|
||||
setupDrag(
|
||||
@ -249,15 +155,24 @@ export class Overlay extends CompositeDisposable {
|
||||
const track = () => {
|
||||
let offset: { x: number; y: number } | null = null;
|
||||
|
||||
const iframes = disableIframePointEvents();
|
||||
const iframes = [
|
||||
...getElementsByTagName('iframe'),
|
||||
...getElementsByTagName('webview'),
|
||||
];
|
||||
|
||||
for (const iframe of iframes) {
|
||||
iframe.style.pointerEvents = 'none';
|
||||
}
|
||||
|
||||
move.value = new CompositeDisposable(
|
||||
{
|
||||
dispose: () => {
|
||||
iframes.release();
|
||||
for (const iframe of iframes) {
|
||||
iframe.style.pointerEvents = 'auto';
|
||||
}
|
||||
},
|
||||
},
|
||||
addDisposableListener(window, 'pointermove', (e) => {
|
||||
addDisposableWindowListener(window, 'mousemove', (e) => {
|
||||
const containerRect =
|
||||
this.options.container.getBoundingClientRect();
|
||||
const x = e.clientX - containerRect.left;
|
||||
@ -279,32 +194,12 @@ export class Overlay extends CompositeDisposable {
|
||||
|
||||
const xOffset = Math.max(
|
||||
0,
|
||||
this.getMinimumWidth(overlayRect.width)
|
||||
overlayRect.width - this.options.minimumInViewportWidth
|
||||
);
|
||||
const yOffset = Math.max(
|
||||
0,
|
||||
this.getMinimumHeight(overlayRect.height)
|
||||
);
|
||||
|
||||
const top = clamp(
|
||||
y - offset.y,
|
||||
-yOffset,
|
||||
Math.max(
|
||||
0,
|
||||
containerRect.height - overlayRect.height + yOffset
|
||||
)
|
||||
);
|
||||
|
||||
const bottom = clamp(
|
||||
offset.y -
|
||||
y +
|
||||
containerRect.height -
|
||||
overlayRect.height,
|
||||
-yOffset,
|
||||
Math.max(
|
||||
0,
|
||||
containerRect.height - overlayRect.height + yOffset
|
||||
)
|
||||
overlayRect.height -
|
||||
this.options.minimumInViewportHeight
|
||||
);
|
||||
|
||||
const left = clamp(
|
||||
@ -316,34 +211,18 @@ export class Overlay extends CompositeDisposable {
|
||||
)
|
||||
);
|
||||
|
||||
const right = clamp(
|
||||
offset.x - x + containerRect.width - overlayRect.width,
|
||||
-xOffset,
|
||||
const top = clamp(
|
||||
y - offset.y,
|
||||
-yOffset,
|
||||
Math.max(
|
||||
0,
|
||||
containerRect.width - overlayRect.width + xOffset
|
||||
containerRect.height - overlayRect.height + yOffset
|
||||
)
|
||||
);
|
||||
|
||||
const bounds: any = {};
|
||||
|
||||
// Anchor to top or to bottom depending on which one is closer
|
||||
if (top <= bottom) {
|
||||
bounds.top = top;
|
||||
} else {
|
||||
bounds.bottom = bottom;
|
||||
}
|
||||
|
||||
// Anchor to left or to right depending on which one is closer
|
||||
if (left <= right) {
|
||||
bounds.left = left;
|
||||
} else {
|
||||
bounds.right = right;
|
||||
}
|
||||
|
||||
this.setBounds(bounds);
|
||||
this.setBounds({ top, left });
|
||||
}),
|
||||
addDisposableListener(window, 'pointerup', () => {
|
||||
addDisposableWindowListener(window, 'mouseup', () => {
|
||||
toggleClass(
|
||||
this._element,
|
||||
'dv-resize-container-dragging',
|
||||
@ -358,7 +237,7 @@ export class Overlay extends CompositeDisposable {
|
||||
|
||||
this.addDisposables(
|
||||
move,
|
||||
addDisposableListener(dragTarget, 'pointerdown', (event) => {
|
||||
addDisposableListener(dragTarget, 'mousedown', (event) => {
|
||||
if (event.defaultPrevented) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
@ -374,7 +253,7 @@ export class Overlay extends CompositeDisposable {
|
||||
}),
|
||||
addDisposableListener(
|
||||
this.options.content,
|
||||
'pointerdown',
|
||||
'mousedown',
|
||||
(event) => {
|
||||
if (event.defaultPrevented) {
|
||||
return;
|
||||
@ -393,14 +272,16 @@ export class Overlay extends CompositeDisposable {
|
||||
),
|
||||
addDisposableListener(
|
||||
this.options.content,
|
||||
'pointerdown',
|
||||
'mousedown',
|
||||
() => {
|
||||
arialLevelTracker.push(this._element);
|
||||
bringElementToFront(this._element);
|
||||
},
|
||||
true
|
||||
)
|
||||
);
|
||||
|
||||
bringElementToFront(this._element);
|
||||
|
||||
if (options.inDragMode) {
|
||||
track();
|
||||
}
|
||||
@ -425,7 +306,7 @@ export class Overlay extends CompositeDisposable {
|
||||
|
||||
this.addDisposables(
|
||||
move,
|
||||
addDisposableListener(resizeHandleElement, 'pointerdown', (e) => {
|
||||
addDisposableListener(resizeHandleElement, 'mousedown', (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
let startPosition: {
|
||||
@ -435,10 +316,17 @@ export class Overlay extends CompositeDisposable {
|
||||
originalWidth: number;
|
||||
} | null = null;
|
||||
|
||||
const iframes = disableIframePointEvents();
|
||||
const iframes = [
|
||||
...getElementsByTagName('iframe'),
|
||||
...getElementsByTagName('webview'),
|
||||
];
|
||||
|
||||
for (const iframe of iframes) {
|
||||
iframe.style.pointerEvents = 'none';
|
||||
}
|
||||
|
||||
move.value = new CompositeDisposable(
|
||||
addDisposableListener(window, 'pointermove', (e) => {
|
||||
addDisposableWindowListener(window, 'mousemove', (e) => {
|
||||
const containerRect =
|
||||
this.options.container.getBoundingClientRect();
|
||||
const overlayRect =
|
||||
@ -458,22 +346,24 @@ export class Overlay extends CompositeDisposable {
|
||||
}
|
||||
|
||||
let top: number | undefined = undefined;
|
||||
let bottom: number | undefined = undefined;
|
||||
let height: number | undefined = undefined;
|
||||
let left: number | undefined = undefined;
|
||||
let right: number | undefined = undefined;
|
||||
let width: number | undefined = undefined;
|
||||
|
||||
const moveTop = () => {
|
||||
const minimumInViewportHeight =
|
||||
this.options.minimumInViewportHeight;
|
||||
const minimumInViewportWidth =
|
||||
this.options.minimumInViewportWidth;
|
||||
|
||||
function moveTop(): void {
|
||||
top = clamp(
|
||||
y,
|
||||
-Number.MAX_VALUE,
|
||||
startPosition!.originalY +
|
||||
startPosition!.originalHeight >
|
||||
containerRect.height
|
||||
? this.getMinimumHeight(
|
||||
containerRect.height
|
||||
)
|
||||
? containerRect.height -
|
||||
minimumInViewportHeight
|
||||
: Math.max(
|
||||
0,
|
||||
startPosition!.originalY +
|
||||
@ -481,42 +371,35 @@ export class Overlay extends CompositeDisposable {
|
||||
Overlay.MINIMUM_HEIGHT
|
||||
)
|
||||
);
|
||||
|
||||
height =
|
||||
startPosition!.originalY +
|
||||
startPosition!.originalHeight -
|
||||
top;
|
||||
}
|
||||
|
||||
bottom = containerRect.height - top - height;
|
||||
};
|
||||
|
||||
const moveBottom = () => {
|
||||
function moveBottom(): void {
|
||||
top =
|
||||
startPosition!.originalY -
|
||||
startPosition!.originalHeight;
|
||||
|
||||
height = clamp(
|
||||
y - top,
|
||||
top < 0 &&
|
||||
typeof this.options
|
||||
.minimumInViewportHeight === 'number'
|
||||
? -top +
|
||||
this.options.minimumInViewportHeight
|
||||
top < 0
|
||||
? -top + minimumInViewportHeight
|
||||
: Overlay.MINIMUM_HEIGHT,
|
||||
Number.MAX_VALUE
|
||||
);
|
||||
}
|
||||
|
||||
bottom = containerRect.height - top - height;
|
||||
};
|
||||
|
||||
const moveLeft = () => {
|
||||
function moveLeft(): void {
|
||||
left = clamp(
|
||||
x,
|
||||
-Number.MAX_VALUE,
|
||||
startPosition!.originalX +
|
||||
startPosition!.originalWidth >
|
||||
containerRect.width
|
||||
? this.getMinimumWidth(containerRect.width)
|
||||
? containerRect.width -
|
||||
minimumInViewportWidth
|
||||
: Math.max(
|
||||
0,
|
||||
startPosition!.originalX +
|
||||
@ -529,28 +412,21 @@ export class Overlay extends CompositeDisposable {
|
||||
startPosition!.originalX +
|
||||
startPosition!.originalWidth -
|
||||
left;
|
||||
}
|
||||
|
||||
right = containerRect.width - left - width;
|
||||
};
|
||||
|
||||
const moveRight = () => {
|
||||
function moveRight(): void {
|
||||
left =
|
||||
startPosition!.originalX -
|
||||
startPosition!.originalWidth;
|
||||
|
||||
width = clamp(
|
||||
x - left,
|
||||
left < 0 &&
|
||||
typeof this.options
|
||||
.minimumInViewportWidth === 'number'
|
||||
? -left +
|
||||
this.options.minimumInViewportWidth
|
||||
left < 0
|
||||
? -left + minimumInViewportWidth
|
||||
: Overlay.MINIMUM_WIDTH,
|
||||
Number.MAX_VALUE
|
||||
);
|
||||
|
||||
right = containerRect.width - left - width;
|
||||
};
|
||||
}
|
||||
|
||||
switch (direction) {
|
||||
case 'top':
|
||||
@ -583,33 +459,16 @@ export class Overlay extends CompositeDisposable {
|
||||
break;
|
||||
}
|
||||
|
||||
const bounds: any = {};
|
||||
|
||||
// Anchor to top or to bottom depending on which one is closer
|
||||
if (top! <= bottom!) {
|
||||
bounds.top = top;
|
||||
} else {
|
||||
bounds.bottom = bottom;
|
||||
}
|
||||
|
||||
// Anchor to left or to right depending on which one is closer
|
||||
if (left! <= right!) {
|
||||
bounds.left = left;
|
||||
} else {
|
||||
bounds.right = right;
|
||||
}
|
||||
|
||||
bounds.height = height;
|
||||
bounds.width = width;
|
||||
|
||||
this.setBounds(bounds);
|
||||
this.setBounds({ height, width, top, left });
|
||||
}),
|
||||
{
|
||||
dispose: () => {
|
||||
iframes.release();
|
||||
for (const iframe of iframes) {
|
||||
iframe.style.pointerEvents = 'auto';
|
||||
}
|
||||
},
|
||||
},
|
||||
addDisposableListener(window, 'pointerup', () => {
|
||||
addDisposableWindowListener(window, 'mouseup', () => {
|
||||
move.dispose();
|
||||
this._onDidChangeEnd.fire();
|
||||
})
|
||||
@ -618,22 +477,7 @@ export class Overlay extends CompositeDisposable {
|
||||
);
|
||||
}
|
||||
|
||||
private getMinimumWidth(width: number) {
|
||||
if (typeof this.options.minimumInViewportWidth === 'number') {
|
||||
return width - this.options.minimumInViewportWidth;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private getMinimumHeight(height: number) {
|
||||
if (typeof this.options.minimumInViewportHeight === 'number') {
|
||||
return height - this.options.minimumInViewportHeight;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
override dispose(): void {
|
||||
arialLevelTracker.destroy(this._element);
|
||||
this._element.remove();
|
||||
super.dispose();
|
||||
}
|
@ -6,13 +6,8 @@ import {
|
||||
import { Emitter, Event } from '../../../events';
|
||||
import { trackFocus } from '../../../dom';
|
||||
import { IDockviewPanel } from '../../dockviewPanel';
|
||||
import { DockviewComponent } from '../../dockviewComponent';
|
||||
import { Droptarget } from '../../../dnd/droptarget';
|
||||
import { DockviewGroupPanelModel } from '../../dockviewGroupPanelModel';
|
||||
import { getPanelData } from '../../../dnd/dataTransfer';
|
||||
|
||||
export interface IContentContainer extends IDisposable {
|
||||
readonly dropTarget: Droptarget;
|
||||
onDidFocus: Event<void>;
|
||||
onDidBlur: Event<void>;
|
||||
element: HTMLElement;
|
||||
@ -21,16 +16,15 @@ export interface IContentContainer extends IDisposable {
|
||||
closePanel: () => void;
|
||||
show(): void;
|
||||
hide(): void;
|
||||
renderPanel(panel: IDockviewPanel, options: { asActive: boolean }): void;
|
||||
}
|
||||
|
||||
export class ContentContainer
|
||||
extends CompositeDisposable
|
||||
implements IContentContainer
|
||||
{
|
||||
private readonly _element: HTMLElement;
|
||||
private _element: HTMLElement;
|
||||
private panel: IDockviewPanel | undefined;
|
||||
private readonly disposable = new MutableDisposable();
|
||||
private disposable = new MutableDisposable();
|
||||
|
||||
private readonly _onDidFocus = new Emitter<void>();
|
||||
readonly onDidFocus: Event<void> = this._onDidFocus.event;
|
||||
@ -42,57 +36,19 @@ export class ContentContainer
|
||||
return this._element;
|
||||
}
|
||||
|
||||
readonly dropTarget: Droptarget;
|
||||
|
||||
constructor(
|
||||
private readonly accessor: DockviewComponent,
|
||||
private readonly group: DockviewGroupPanelModel
|
||||
) {
|
||||
constructor() {
|
||||
super();
|
||||
this._element = document.createElement('div');
|
||||
this._element.className = 'dv-content-container';
|
||||
this._element.className = 'content-container';
|
||||
this._element.tabIndex = -1;
|
||||
|
||||
this.addDisposables(this._onDidFocus, this._onDidBlur);
|
||||
|
||||
const target = group.dropTargetContainer;
|
||||
|
||||
this.dropTarget = new Droptarget(this.element, {
|
||||
getOverlayOutline: () => {
|
||||
return accessor.options.theme?.dndPanelOverlay === 'group'
|
||||
? this.element.parentElement
|
||||
: null;
|
||||
},
|
||||
className: 'dv-drop-target-content',
|
||||
acceptedTargetZones: ['top', 'bottom', 'left', 'right', 'center'],
|
||||
canDisplayOverlay: (event, position) => {
|
||||
if (
|
||||
this.group.locked === 'no-drop-target' ||
|
||||
(this.group.locked && position === 'center')
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const data = getPanelData();
|
||||
|
||||
if (
|
||||
!data &&
|
||||
event.shiftKey &&
|
||||
this.group.location.type !== 'floating'
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (data && data.viewId === this.accessor.id) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return this.group.canDisplayOverlay(event, position, 'content');
|
||||
},
|
||||
getOverrideTarget: target ? () => target.model : undefined,
|
||||
});
|
||||
|
||||
this.addDisposables(this.dropTarget);
|
||||
// for hosted containers
|
||||
// 1) register a drop target on the host
|
||||
// 2) register window dragStart events to disable pointer events
|
||||
// 3) register dragEnd events
|
||||
// 4) register mouseMove events (if no buttons are present we take this as a dragEnd event)
|
||||
}
|
||||
|
||||
show(): void {
|
||||
@ -103,60 +59,25 @@ export class ContentContainer
|
||||
this.element.style.display = 'none';
|
||||
}
|
||||
|
||||
renderPanel(
|
||||
panel: IDockviewPanel,
|
||||
options: { asActive: boolean } = { asActive: true }
|
||||
): void {
|
||||
const doRender =
|
||||
options.asActive ||
|
||||
(this.panel && this.group.isPanelActive(this.panel));
|
||||
|
||||
if (
|
||||
this.panel &&
|
||||
this.panel.view.content.element.parentElement === this._element
|
||||
) {
|
||||
/**
|
||||
* If the currently attached panel is mounted directly to the content then remove it
|
||||
*/
|
||||
this._element.removeChild(this.panel.view.content.element);
|
||||
public openPanel(panel: IDockviewPanel): void {
|
||||
if (this.panel === panel) {
|
||||
return;
|
||||
}
|
||||
if (this.panel) {
|
||||
if (this.panel.view?.content) {
|
||||
this._element.removeChild(this.panel.view.content.element);
|
||||
}
|
||||
this.panel = undefined;
|
||||
}
|
||||
|
||||
this.panel = panel;
|
||||
|
||||
let container: HTMLElement;
|
||||
const disposable = new CompositeDisposable();
|
||||
|
||||
switch (panel.api.renderer) {
|
||||
case 'onlyWhenVisible':
|
||||
this.group.renderContainer.detatch(panel);
|
||||
if (this.panel) {
|
||||
if (doRender) {
|
||||
this._element.appendChild(
|
||||
this.panel.view.content.element
|
||||
);
|
||||
}
|
||||
}
|
||||
container = this._element;
|
||||
break;
|
||||
case 'always':
|
||||
if (
|
||||
panel.view.content.element.parentElement === this._element
|
||||
) {
|
||||
this._element.removeChild(panel.view.content.element);
|
||||
}
|
||||
container = this.group.renderContainer.attach({
|
||||
panel,
|
||||
referenceContainer: this,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
throw new Error(
|
||||
`dockview: invalid renderer type '${panel.api.renderer}'`
|
||||
);
|
||||
}
|
||||
if (this.panel.view) {
|
||||
const _onDidFocus = this.panel.view.content.onDidFocus;
|
||||
const _onDidBlur = this.panel.view.content.onDidBlur;
|
||||
|
||||
if (doRender) {
|
||||
const focusTracker = trackFocus(container);
|
||||
const disposable = new CompositeDisposable();
|
||||
const focusTracker = trackFocus(this._element);
|
||||
|
||||
disposable.addDisposables(
|
||||
focusTracker,
|
||||
@ -164,16 +85,21 @@ export class ContentContainer
|
||||
focusTracker.onDidBlur(() => this._onDidBlur.fire())
|
||||
);
|
||||
|
||||
this.disposable.value = disposable;
|
||||
}
|
||||
}
|
||||
if (_onDidFocus) {
|
||||
disposable.addDisposables(
|
||||
_onDidFocus(() => this._onDidFocus.fire())
|
||||
);
|
||||
}
|
||||
if (_onDidBlur) {
|
||||
disposable.addDisposables(
|
||||
_onDidBlur(() => this._onDidBlur.fire())
|
||||
);
|
||||
}
|
||||
|
||||
public openPanel(panel: IDockviewPanel): void {
|
||||
if (this.panel === panel) {
|
||||
return;
|
||||
this._element.appendChild(this.panel.view.content.element);
|
||||
}
|
||||
|
||||
this.renderPanel(panel);
|
||||
this.disposable.value = disposable;
|
||||
}
|
||||
|
||||
public layout(_width: number, _height: number): void {
|
||||
@ -181,14 +107,10 @@ export class ContentContainer
|
||||
}
|
||||
|
||||
public closePanel(): void {
|
||||
if (this.panel) {
|
||||
if (this.panel.api.renderer === 'onlyWhenVisible') {
|
||||
this.panel.view.content.element.parentElement?.removeChild(
|
||||
this.panel.view.content.element
|
||||
);
|
||||
}
|
||||
if (this.panel?.view?.content?.element) {
|
||||
this._element.removeChild(this.panel.view.content.element);
|
||||
this.panel = undefined;
|
||||
}
|
||||
this.panel = undefined;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
|
@ -1,87 +0,0 @@
|
||||
import { shiftAbsoluteElementIntoView } from '../../dom';
|
||||
import { addDisposableListener } from '../../events';
|
||||
import {
|
||||
CompositeDisposable,
|
||||
Disposable,
|
||||
MutableDisposable,
|
||||
} from '../../lifecycle';
|
||||
|
||||
export class PopupService extends CompositeDisposable {
|
||||
private readonly _element: HTMLElement;
|
||||
private _active: HTMLElement | null = null;
|
||||
private readonly _activeDisposable = new MutableDisposable();
|
||||
|
||||
constructor(private readonly root: HTMLElement) {
|
||||
super();
|
||||
|
||||
this._element = document.createElement('div');
|
||||
this._element.className = 'dv-popover-anchor';
|
||||
this._element.style.position = 'relative';
|
||||
|
||||
this.root.prepend(this._element);
|
||||
|
||||
this.addDisposables(
|
||||
Disposable.from(() => {
|
||||
this.close();
|
||||
}),
|
||||
this._activeDisposable
|
||||
);
|
||||
}
|
||||
|
||||
openPopover(
|
||||
element: HTMLElement,
|
||||
position: { x: number; y: number; zIndex?: string }
|
||||
): void {
|
||||
this.close();
|
||||
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.style.position = 'absolute';
|
||||
wrapper.style.zIndex = position.zIndex ?? 'var(--dv-overlay-z-index)';
|
||||
wrapper.appendChild(element);
|
||||
|
||||
const anchorBox = this._element.getBoundingClientRect();
|
||||
const offsetX = anchorBox.left;
|
||||
const offsetY = anchorBox.top;
|
||||
|
||||
wrapper.style.top = `${position.y - offsetY}px`;
|
||||
wrapper.style.left = `${position.x - offsetX}px`;
|
||||
|
||||
this._element.appendChild(wrapper);
|
||||
|
||||
this._active = wrapper;
|
||||
|
||||
this._activeDisposable.value = new CompositeDisposable(
|
||||
addDisposableListener(window, 'pointerdown', (event) => {
|
||||
const target = event.target;
|
||||
|
||||
if (!(target instanceof HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let el: HTMLElement | null = target;
|
||||
|
||||
while (el && el !== wrapper) {
|
||||
el = el?.parentElement ?? null;
|
||||
}
|
||||
|
||||
if (el) {
|
||||
return; // clicked within popover
|
||||
}
|
||||
|
||||
this.close();
|
||||
})
|
||||
);
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
shiftAbsoluteElementIntoView(wrapper, this.root);
|
||||
});
|
||||
}
|
||||
|
||||
close(): void {
|
||||
if (this._active) {
|
||||
this._active.remove();
|
||||
this._activeDisposable.dispose();
|
||||
this._active = null;
|
||||
}
|
||||
}
|
||||
}
|
@ -6,78 +6,70 @@
|
||||
); /* forces tab to be drawn on a separate layer (see https://github.com/microsoft/vscode/issues/18733) */
|
||||
}
|
||||
|
||||
.dv-tab {
|
||||
.tab {
|
||||
flex-shrink: 0;
|
||||
|
||||
&:focus-within,
|
||||
&:focus {
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
content: '';
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
pointer-events: none;
|
||||
outline: 1px solid var(--dv-tab-divider-color) !important;
|
||||
outline-offset: -1px;
|
||||
z-index: 5;
|
||||
}
|
||||
}
|
||||
|
||||
&.dv-tab-dragging {
|
||||
.dv-default-tab-action {
|
||||
.tab-action {
|
||||
background-color: var(--dv-activegroup-visiblepanel-tab-color);
|
||||
}
|
||||
}
|
||||
|
||||
&.dv-active-tab {
|
||||
.dv-default-tab {
|
||||
.dv-default-tab-action {
|
||||
&.active-tab > .default-tab {
|
||||
.tab-action {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
&.inactive-tab > .default-tab {
|
||||
.tab-action {
|
||||
visibility: hidden;
|
||||
}
|
||||
&:hover {
|
||||
.tab-action {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.dv-inactive-tab {
|
||||
.dv-default-tab {
|
||||
.dv-default-tab-action {
|
||||
visibility: hidden;
|
||||
}
|
||||
&:hover {
|
||||
.dv-default-tab-action {
|
||||
visibility: visible;
|
||||
.default-tab {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
min-width: 80px;
|
||||
align-items: center;
|
||||
padding: 0px 8px;
|
||||
white-space: nowrap;
|
||||
text-overflow: elipsis;
|
||||
|
||||
.tab-content {
|
||||
padding: 0px 8px;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.action-container {
|
||||
text-align: right;
|
||||
display: flex;
|
||||
|
||||
.tab-list {
|
||||
display: flex;
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
justify-content: flex-end;
|
||||
|
||||
.tab-action {
|
||||
padding: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
|
||||
&:hover {
|
||||
border-radius: 2px;
|
||||
background-color: var(--dv-icon-hover-background-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dv-default-tab {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
.dv-default-tab-content {
|
||||
flex-grow: 1;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.dv-default-tab-action {
|
||||
padding: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
|
||||
&:hover {
|
||||
border-radius: 2px;
|
||||
background-color: var(--dv-icon-hover-background-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,18 @@
|
||||
import { CompositeDisposable } from '../../../lifecycle';
|
||||
import { ITabRenderer, GroupPanelPartInitParameters } from '../../types';
|
||||
import { addDisposableListener } from '../../../events';
|
||||
import { PanelUpdateEvent } from '../../../panel/types';
|
||||
import { DockviewGroupPanel } from '../../dockviewGroupPanel';
|
||||
import { createCloseButton } from '../../../svg';
|
||||
|
||||
export class DefaultTab extends CompositeDisposable implements ITabRenderer {
|
||||
private readonly _element: HTMLElement;
|
||||
private readonly _content: HTMLElement;
|
||||
private readonly action: HTMLElement;
|
||||
private _title: string | undefined;
|
||||
private _element: HTMLElement;
|
||||
private _content: HTMLElement;
|
||||
private _actionContainer: HTMLElement;
|
||||
private _list: HTMLElement;
|
||||
private action: HTMLElement;
|
||||
//
|
||||
private params: GroupPanelPartInitParameters = {} as any;
|
||||
|
||||
get element(): HTMLElement {
|
||||
return this._element;
|
||||
@ -17,48 +22,70 @@ export class DefaultTab extends CompositeDisposable implements ITabRenderer {
|
||||
super();
|
||||
|
||||
this._element = document.createElement('div');
|
||||
this._element.className = 'dv-default-tab';
|
||||
|
||||
this._element.className = 'default-tab';
|
||||
//
|
||||
this._content = document.createElement('div');
|
||||
this._content.className = 'dv-default-tab-content';
|
||||
|
||||
this._content.className = 'tab-content';
|
||||
//
|
||||
this._actionContainer = document.createElement('div');
|
||||
this._actionContainer.className = 'action-container';
|
||||
//
|
||||
this._list = document.createElement('ul');
|
||||
this._list.className = 'tab-list';
|
||||
//
|
||||
this.action = document.createElement('div');
|
||||
this.action.className = 'dv-default-tab-action';
|
||||
this.action.className = 'tab-action';
|
||||
this.action.appendChild(createCloseButton());
|
||||
|
||||
//
|
||||
this._element.appendChild(this._content);
|
||||
this._element.appendChild(this.action);
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
init(params: GroupPanelPartInitParameters): void {
|
||||
this._title = params.title;
|
||||
|
||||
this._element.appendChild(this._actionContainer);
|
||||
this._actionContainer.appendChild(this._list);
|
||||
this._list.appendChild(this.action);
|
||||
//
|
||||
this.addDisposables(
|
||||
params.api.onDidTitleChange((event) => {
|
||||
this._title = event.title;
|
||||
this.render();
|
||||
}),
|
||||
addDisposableListener(this.action, 'pointerdown', (ev) => {
|
||||
addDisposableListener(this._actionContainer, 'mousedown', (ev) => {
|
||||
ev.preventDefault();
|
||||
}),
|
||||
addDisposableListener(this.action, 'click', (ev) => {
|
||||
if (ev.defaultPrevented) {
|
||||
return;
|
||||
}
|
||||
|
||||
ev.preventDefault();
|
||||
params.api.close();
|
||||
})
|
||||
);
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
public update(event: PanelUpdateEvent): void {
|
||||
this.params = { ...this.params, ...event.params };
|
||||
this.render();
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
//noop
|
||||
}
|
||||
|
||||
public init(params: GroupPanelPartInitParameters): void {
|
||||
this.params = params;
|
||||
this._content.textContent = params.title;
|
||||
|
||||
addDisposableListener(this.action, 'click', (ev) => {
|
||||
ev.preventDefault(); //
|
||||
this.params.api.close();
|
||||
});
|
||||
}
|
||||
|
||||
onGroupChange(_group: DockviewGroupPanel): void {
|
||||
this.render();
|
||||
}
|
||||
|
||||
onPanelVisibleChange(_isPanelVisible: boolean): void {
|
||||
this.render();
|
||||
}
|
||||
|
||||
public layout(_width: number, _height: number): void {
|
||||
// noop
|
||||
}
|
||||
|
||||
private render(): void {
|
||||
if (this._content.textContent !== this._title) {
|
||||
this._content.textContent = this._title ?? '';
|
||||
if (this._content.textContent !== this.params.title) {
|
||||
this._content.textContent = this.params.title;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,88 +7,85 @@ import {
|
||||
} from '../../../dnd/dataTransfer';
|
||||
import { toggleClass } from '../../../dom';
|
||||
import { DockviewComponent } from '../../dockviewComponent';
|
||||
import { ITabRenderer } from '../../types';
|
||||
import { DockviewDropTargets, ITabRenderer } from '../../types';
|
||||
import { DockviewGroupPanel } from '../../dockviewGroupPanel';
|
||||
import {
|
||||
DroptargetEvent,
|
||||
Droptarget,
|
||||
WillShowOverlayEvent,
|
||||
} from '../../../dnd/droptarget';
|
||||
import { DroptargetEvent, Droptarget } from '../../../dnd/droptarget';
|
||||
import { DragHandler } from '../../../dnd/abstractDragHandler';
|
||||
import { IDockviewPanel } from '../../dockviewPanel';
|
||||
import { addGhostImage } from '../../../dnd/ghost';
|
||||
|
||||
class TabDragHandler extends DragHandler {
|
||||
private readonly panelTransfer =
|
||||
LocalSelectionTransfer.getInstance<PanelTransfer>();
|
||||
|
||||
constructor(
|
||||
element: HTMLElement,
|
||||
private readonly accessor: DockviewComponent,
|
||||
private readonly group: DockviewGroupPanel,
|
||||
private readonly panel: IDockviewPanel
|
||||
) {
|
||||
super(element);
|
||||
}
|
||||
|
||||
getData(event: DragEvent): IDisposable {
|
||||
this.panelTransfer.setData(
|
||||
[new PanelTransfer(this.accessor.id, this.group.id, this.panel.id)],
|
||||
PanelTransfer.prototype
|
||||
);
|
||||
|
||||
return {
|
||||
dispose: () => {
|
||||
this.panelTransfer.clearData(PanelTransfer.prototype);
|
||||
},
|
||||
};
|
||||
}
|
||||
export interface ITab extends IDisposable {
|
||||
readonly panelId: string;
|
||||
readonly element: HTMLElement;
|
||||
setContent: (element: ITabRenderer) => void;
|
||||
onChanged: Event<MouseEvent>;
|
||||
onDrop: Event<DroptargetEvent>;
|
||||
setActive(isActive: boolean): void;
|
||||
}
|
||||
|
||||
export class Tab extends CompositeDisposable {
|
||||
export class Tab extends CompositeDisposable implements ITab {
|
||||
private readonly _element: HTMLElement;
|
||||
private readonly dropTarget: Droptarget;
|
||||
private content: ITabRenderer | undefined = undefined;
|
||||
private readonly droptarget: Droptarget;
|
||||
private content?: ITabRenderer;
|
||||
|
||||
private readonly _onPointDown = new Emitter<MouseEvent>();
|
||||
readonly onPointerDown: Event<MouseEvent> = this._onPointDown.event;
|
||||
private readonly _onChanged = new Emitter<MouseEvent>();
|
||||
readonly onChanged: Event<MouseEvent> = this._onChanged.event;
|
||||
|
||||
private readonly _onDropped = new Emitter<DroptargetEvent>();
|
||||
readonly onDrop: Event<DroptargetEvent> = this._onDropped.event;
|
||||
|
||||
private readonly _onDragStart = new Emitter<DragEvent>();
|
||||
readonly onDragStart = this._onDragStart.event;
|
||||
|
||||
readonly onWillShowOverlay: Event<WillShowOverlayEvent>;
|
||||
|
||||
public get element(): HTMLElement {
|
||||
return this._element;
|
||||
}
|
||||
|
||||
constructor(
|
||||
public readonly panel: IDockviewPanel,
|
||||
public readonly panelId: string,
|
||||
private readonly accessor: DockviewComponent,
|
||||
private readonly group: DockviewGroupPanel
|
||||
) {
|
||||
super();
|
||||
|
||||
this._element = document.createElement('div');
|
||||
this._element.className = 'dv-tab';
|
||||
this._element.className = 'tab';
|
||||
this._element.tabIndex = 0;
|
||||
this._element.draggable = true;
|
||||
|
||||
toggleClass(this.element, 'dv-inactive-tab', true);
|
||||
toggleClass(this.element, 'inactive-tab', true);
|
||||
|
||||
const dragHandler = new TabDragHandler(
|
||||
this._element,
|
||||
this.accessor,
|
||||
this.group,
|
||||
this.panel
|
||||
this.addDisposables(
|
||||
this._onChanged,
|
||||
this._onDropped,
|
||||
new (class Handler extends DragHandler {
|
||||
private readonly panelTransfer =
|
||||
LocalSelectionTransfer.getInstance<PanelTransfer>();
|
||||
|
||||
getData(): IDisposable {
|
||||
this.panelTransfer.setData(
|
||||
[new PanelTransfer(accessor.id, group.id, panelId)],
|
||||
PanelTransfer.prototype
|
||||
);
|
||||
|
||||
return {
|
||||
dispose: () => {
|
||||
this.panelTransfer.clearData(
|
||||
PanelTransfer.prototype
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
})(this._element)
|
||||
);
|
||||
|
||||
this.dropTarget = new Droptarget(this._element, {
|
||||
acceptedTargetZones: ['left', 'right'],
|
||||
overlayModel: { activationSize: { value: 50, type: 'percentage' } },
|
||||
this.addDisposables(
|
||||
addDisposableListener(this._element, 'mousedown', (event) => {
|
||||
if (event.defaultPrevented) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._onChanged.fire(event);
|
||||
})
|
||||
);
|
||||
|
||||
this.droptarget = new Droptarget(this._element, {
|
||||
acceptedTargetZones: ['center'],
|
||||
canDisplayOverlay: (event, position) => {
|
||||
if (this.group.locked) {
|
||||
return false;
|
||||
@ -97,58 +94,36 @@ export class Tab extends CompositeDisposable {
|
||||
const data = getPanelData();
|
||||
|
||||
if (data && this.accessor.id === data.viewId) {
|
||||
return true;
|
||||
if (
|
||||
data.panelId === null &&
|
||||
data.groupId === this.group.id
|
||||
) {
|
||||
// don't allow group move to drop on self
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.panelId !== data.panelId;
|
||||
}
|
||||
|
||||
return this.group.model.canDisplayOverlay(
|
||||
event,
|
||||
position,
|
||||
'tab'
|
||||
DockviewDropTargets.Tab
|
||||
);
|
||||
},
|
||||
getOverrideTarget: () => group.model.dropTargetContainer?.model,
|
||||
});
|
||||
|
||||
this.onWillShowOverlay = this.dropTarget.onWillShowOverlay;
|
||||
|
||||
this.addDisposables(
|
||||
this._onPointDown,
|
||||
this._onDropped,
|
||||
this._onDragStart,
|
||||
dragHandler.onDragStart((event) => {
|
||||
if (event.dataTransfer) {
|
||||
const style = getComputedStyle(this.element);
|
||||
const newNode = this.element.cloneNode(true) as HTMLElement;
|
||||
Array.from(style).forEach((key) =>
|
||||
newNode.style.setProperty(
|
||||
key,
|
||||
style.getPropertyValue(key),
|
||||
style.getPropertyPriority(key)
|
||||
)
|
||||
);
|
||||
newNode.style.position = 'absolute';
|
||||
|
||||
addGhostImage(event.dataTransfer, newNode, {
|
||||
y: -10,
|
||||
x: 30,
|
||||
});
|
||||
}
|
||||
this._onDragStart.fire(event);
|
||||
}),
|
||||
dragHandler,
|
||||
addDisposableListener(this._element, 'pointerdown', (event) => {
|
||||
this._onPointDown.fire(event);
|
||||
}),
|
||||
this.dropTarget.onDrop((event) => {
|
||||
this.droptarget.onDrop((event) => {
|
||||
this._onDropped.fire(event);
|
||||
}),
|
||||
this.dropTarget
|
||||
this.droptarget
|
||||
);
|
||||
}
|
||||
|
||||
public setActive(isActive: boolean): void {
|
||||
toggleClass(this.element, 'dv-active-tab', isActive);
|
||||
toggleClass(this.element, 'dv-inactive-tab', !isActive);
|
||||
toggleClass(this.element, 'active-tab', isActive);
|
||||
toggleClass(this.element, 'inactive-tab', !isActive);
|
||||
}
|
||||
|
||||
public setContent(part: ITabRenderer): void {
|
||||
|
@ -1,19 +0,0 @@
|
||||
.dv-tabs-overflow-dropdown-default {
|
||||
height: 100%;
|
||||
color: var(--dv-activegroup-hiddenpanel-tab-color);
|
||||
|
||||
margin: var(--dv-tab-margin);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
padding: 0.25rem 0.5rem;
|
||||
cursor: pointer;
|
||||
|
||||
> span {
|
||||
padding-left: 0.25rem;
|
||||
}
|
||||
|
||||
> svg {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
import { createChevronRightButton } from '../../../svg';
|
||||
|
||||
export type DropdownElement = {
|
||||
element: HTMLElement;
|
||||
update: (params: { tabs: number }) => void;
|
||||
dispose?: () => void;
|
||||
};
|
||||
|
||||
export function createDropdownElementHandle(): DropdownElement {
|
||||
const el = document.createElement('div');
|
||||
el.className = 'dv-tabs-overflow-dropdown-default';
|
||||
|
||||
const text = document.createElement('span');
|
||||
text.textContent = ``;
|
||||
const icon = createChevronRightButton();
|
||||
el.appendChild(icon);
|
||||
el.appendChild(text);
|
||||
|
||||
return {
|
||||
element: el,
|
||||
update: (params: { tabs: number }) => {
|
||||
text.textContent = `${params.tabs}`;
|
||||
},
|
||||
};
|
||||
}
|
@ -1,79 +0,0 @@
|
||||
.dv-tabs-container {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
scrollbar-width: thin; // firefox
|
||||
|
||||
&.dv-horizontal {
|
||||
.dv-tab {
|
||||
&:not(:first-child)::before {
|
||||
content: ' ';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 5;
|
||||
pointer-events: none;
|
||||
background-color: var(--dv-tab-divider-color);
|
||||
width: 1px;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
height: 3px;
|
||||
}
|
||||
|
||||
/* Track */
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/* Handle */
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: var(--dv-tabs-container-scrollbar-color);
|
||||
}
|
||||
}
|
||||
|
||||
.dv-scrollable {
|
||||
> .dv-tabs-container {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.dv-tab {
|
||||
-webkit-user-drag: element;
|
||||
outline: none;
|
||||
padding: 0.25rem 0.5rem;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
font-size: var(--dv-tab-font-size);
|
||||
margin: var(--dv-tab-margin);
|
||||
}
|
||||
|
||||
.dv-tabs-overflow-container {
|
||||
flex-direction: column;
|
||||
height: unset;
|
||||
border: 1px solid var(--dv-tab-divider-color);
|
||||
background-color: var(--dv-group-view-background-color);
|
||||
|
||||
.dv-tab {
|
||||
&:not(:last-child) {
|
||||
border-bottom: 1px solid var(--dv-tab-divider-color);
|
||||
}
|
||||
}
|
||||
|
||||
.dv-active-tab {
|
||||
background-color: var(
|
||||
--dv-activegroup-visiblepanel-tab-background-color
|
||||
);
|
||||
color: var(--dv-activegroup-visiblepanel-tab-color);
|
||||
}
|
||||
.dv-inactive-tab {
|
||||
background-color: var(
|
||||
--dv-activegroup-hiddenpanel-tab-background-color
|
||||
);
|
||||
color: var(--dv-activegroup-hiddenpanel-tab-color);
|
||||
}
|
||||
}
|
@ -1,301 +0,0 @@
|
||||
import { getPanelData } from '../../../dnd/dataTransfer';
|
||||
import {
|
||||
isChildEntirelyVisibleWithinParent,
|
||||
OverflowObserver,
|
||||
} from '../../../dom';
|
||||
import { addDisposableListener, Emitter, Event } from '../../../events';
|
||||
import {
|
||||
CompositeDisposable,
|
||||
Disposable,
|
||||
IValueDisposable,
|
||||
MutableDisposable,
|
||||
} from '../../../lifecycle';
|
||||
import { Scrollbar } from '../../../scrollbar';
|
||||
import { DockviewComponent } from '../../dockviewComponent';
|
||||
import { DockviewGroupPanel } from '../../dockviewGroupPanel';
|
||||
import { WillShowOverlayLocationEvent } from '../../dockviewGroupPanelModel';
|
||||
import { DockviewPanel, IDockviewPanel } from '../../dockviewPanel';
|
||||
import { Tab } from '../tab/tab';
|
||||
import { TabDragEvent, TabDropIndexEvent } from './tabsContainer';
|
||||
|
||||
export class Tabs extends CompositeDisposable {
|
||||
private readonly _element: HTMLElement;
|
||||
private readonly _tabsList: HTMLElement;
|
||||
private readonly _observerDisposable = new MutableDisposable();
|
||||
|
||||
private _tabs: IValueDisposable<Tab>[] = [];
|
||||
private selectedIndex = -1;
|
||||
private _showTabsOverflowControl = false;
|
||||
|
||||
private readonly _onTabDragStart = new Emitter<TabDragEvent>();
|
||||
readonly onTabDragStart: Event<TabDragEvent> = this._onTabDragStart.event;
|
||||
|
||||
private readonly _onDrop = new Emitter<TabDropIndexEvent>();
|
||||
readonly onDrop: Event<TabDropIndexEvent> = this._onDrop.event;
|
||||
|
||||
private readonly _onWillShowOverlay =
|
||||
new Emitter<WillShowOverlayLocationEvent>();
|
||||
readonly onWillShowOverlay: Event<WillShowOverlayLocationEvent> =
|
||||
this._onWillShowOverlay.event;
|
||||
|
||||
private readonly _onOverflowTabsChange = new Emitter<{
|
||||
tabs: string[];
|
||||
reset: boolean;
|
||||
}>();
|
||||
readonly onOverflowTabsChange = this._onOverflowTabsChange.event;
|
||||
|
||||
get showTabsOverflowControl(): boolean {
|
||||
return this._showTabsOverflowControl;
|
||||
}
|
||||
|
||||
set showTabsOverflowControl(value: boolean) {
|
||||
if (this._showTabsOverflowControl == value) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._showTabsOverflowControl = value;
|
||||
|
||||
if (value) {
|
||||
const observer = new OverflowObserver(this._tabsList);
|
||||
|
||||
this._observerDisposable.value = new CompositeDisposable(
|
||||
observer,
|
||||
observer.onDidChange((event) => {
|
||||
const hasOverflow = event.hasScrollX || event.hasScrollY;
|
||||
this.toggleDropdown({ reset: !hasOverflow });
|
||||
}),
|
||||
addDisposableListener(this._tabsList, 'scroll', () => {
|
||||
this.toggleDropdown({ reset: false });
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
get element(): HTMLElement {
|
||||
return this._element;
|
||||
}
|
||||
|
||||
get panels(): string[] {
|
||||
return this._tabs.map((_) => _.value.panel.id);
|
||||
}
|
||||
|
||||
get size(): number {
|
||||
return this._tabs.length;
|
||||
}
|
||||
|
||||
get tabs(): Tab[] {
|
||||
return this._tabs.map((_) => _.value);
|
||||
}
|
||||
|
||||
constructor(
|
||||
private readonly group: DockviewGroupPanel,
|
||||
private readonly accessor: DockviewComponent,
|
||||
options: {
|
||||
showTabsOverflowControl: boolean;
|
||||
}
|
||||
) {
|
||||
super();
|
||||
|
||||
this._tabsList = document.createElement('div');
|
||||
this._tabsList.className = 'dv-tabs-container dv-horizontal';
|
||||
|
||||
this.showTabsOverflowControl = options.showTabsOverflowControl;
|
||||
|
||||
if (accessor.options.scrollbars === 'native') {
|
||||
this._element = this._tabsList;
|
||||
} else {
|
||||
const scrollbar = new Scrollbar(this._tabsList);
|
||||
this._element = scrollbar.element;
|
||||
this.addDisposables(scrollbar);
|
||||
}
|
||||
|
||||
this.addDisposables(
|
||||
this._onOverflowTabsChange,
|
||||
this._observerDisposable,
|
||||
this._onWillShowOverlay,
|
||||
this._onDrop,
|
||||
this._onTabDragStart,
|
||||
addDisposableListener(this.element, 'pointerdown', (event) => {
|
||||
if (event.defaultPrevented) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isLeftClick = event.button === 0;
|
||||
|
||||
if (isLeftClick) {
|
||||
this.accessor.doSetGroupActive(this.group);
|
||||
}
|
||||
}),
|
||||
Disposable.from(() => {
|
||||
for (const { value, disposable } of this._tabs) {
|
||||
disposable.dispose();
|
||||
value.dispose();
|
||||
}
|
||||
|
||||
this._tabs = [];
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
indexOf(id: string): number {
|
||||
return this._tabs.findIndex((tab) => tab.value.panel.id === id);
|
||||
}
|
||||
|
||||
isActive(tab: Tab): boolean {
|
||||
return (
|
||||
this.selectedIndex > -1 &&
|
||||
this._tabs[this.selectedIndex].value === tab
|
||||
);
|
||||
}
|
||||
|
||||
setActivePanel(panel: IDockviewPanel): void {
|
||||
let runningWidth = 0;
|
||||
|
||||
for (const tab of this._tabs) {
|
||||
const isActivePanel = panel.id === tab.value.panel.id;
|
||||
tab.value.setActive(isActivePanel);
|
||||
|
||||
if (isActivePanel) {
|
||||
const element = tab.value.element;
|
||||
const parentElement = element.parentElement!;
|
||||
|
||||
if (
|
||||
runningWidth < parentElement.scrollLeft ||
|
||||
runningWidth + element.clientWidth >
|
||||
parentElement.scrollLeft + parentElement.clientWidth
|
||||
) {
|
||||
parentElement.scrollLeft = runningWidth;
|
||||
}
|
||||
}
|
||||
|
||||
runningWidth += tab.value.element.clientWidth;
|
||||
}
|
||||
}
|
||||
|
||||
openPanel(panel: IDockviewPanel, index: number = this._tabs.length): void {
|
||||
if (this._tabs.find((tab) => tab.value.panel.id === panel.id)) {
|
||||
return;
|
||||
}
|
||||
const tab = new Tab(panel, this.accessor, this.group);
|
||||
tab.setContent(panel.view.tab);
|
||||
|
||||
const disposable = new CompositeDisposable(
|
||||
tab.onDragStart((event) => {
|
||||
this._onTabDragStart.fire({ nativeEvent: event, panel });
|
||||
}),
|
||||
tab.onPointerDown((event) => {
|
||||
if (event.defaultPrevented) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isFloatingGroupsEnabled =
|
||||
!this.accessor.options.disableFloatingGroups;
|
||||
|
||||
const isFloatingWithOnePanel =
|
||||
this.group.api.location.type === 'floating' &&
|
||||
this.size === 1;
|
||||
|
||||
if (
|
||||
isFloatingGroupsEnabled &&
|
||||
!isFloatingWithOnePanel &&
|
||||
event.shiftKey
|
||||
) {
|
||||
event.preventDefault();
|
||||
|
||||
const panel = this.accessor.getGroupPanel(tab.panel.id);
|
||||
|
||||
const { top, left } = tab.element.getBoundingClientRect();
|
||||
const { top: rootTop, left: rootLeft } =
|
||||
this.accessor.element.getBoundingClientRect();
|
||||
|
||||
this.accessor.addFloatingGroup(panel as DockviewPanel, {
|
||||
x: left - rootLeft,
|
||||
y: top - rootTop,
|
||||
inDragMode: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event.button) {
|
||||
case 0: // left click or touch
|
||||
if (this.group.activePanel !== panel) {
|
||||
this.group.model.openPanel(panel);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}),
|
||||
tab.onDrop((event) => {
|
||||
this._onDrop.fire({
|
||||
event: event.nativeEvent,
|
||||
index: this._tabs.findIndex((x) => x.value === tab),
|
||||
});
|
||||
}),
|
||||
tab.onWillShowOverlay((event) => {
|
||||
this._onWillShowOverlay.fire(
|
||||
new WillShowOverlayLocationEvent(event, {
|
||||
kind: 'tab',
|
||||
panel: this.group.activePanel,
|
||||
api: this.accessor.api,
|
||||
group: this.group,
|
||||
getData: getPanelData,
|
||||
})
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
const value: IValueDisposable<Tab> = { value: tab, disposable };
|
||||
|
||||
this.addTab(value, index);
|
||||
}
|
||||
|
||||
delete(id: string): void {
|
||||
const index = this.indexOf(id);
|
||||
const tabToRemove = this._tabs.splice(index, 1)[0];
|
||||
|
||||
const { value, disposable } = tabToRemove;
|
||||
|
||||
disposable.dispose();
|
||||
value.dispose();
|
||||
value.element.remove();
|
||||
}
|
||||
|
||||
private addTab(
|
||||
tab: IValueDisposable<Tab>,
|
||||
index: number = this._tabs.length
|
||||
): void {
|
||||
if (index < 0 || index > this._tabs.length) {
|
||||
throw new Error('invalid location');
|
||||
}
|
||||
|
||||
this._tabsList.insertBefore(
|
||||
tab.value.element,
|
||||
this._tabsList.children[index]
|
||||
);
|
||||
|
||||
this._tabs = [
|
||||
...this._tabs.slice(0, index),
|
||||
tab,
|
||||
...this._tabs.slice(index),
|
||||
];
|
||||
|
||||
if (this.selectedIndex < 0) {
|
||||
this.selectedIndex = index;
|
||||
}
|
||||
}
|
||||
|
||||
private toggleDropdown(options: { reset: boolean }): void {
|
||||
const tabs = options.reset
|
||||
? []
|
||||
: this._tabs
|
||||
.filter(
|
||||
(tab) =>
|
||||
!isChildEntirelyVisibleWithinParent(
|
||||
tab.value.element,
|
||||
this._tabsList
|
||||
)
|
||||
)
|
||||
.map((x) => x.value.panel.id);
|
||||
|
||||
this._onOverflowTabsChange.fire({ tabs, reset: options.reset });
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
.dv-tabs-and-actions-container {
|
||||
.tabs-and-actions-container {
|
||||
display: flex;
|
||||
background-color: var(--dv-tabs-and-actions-container-background-color);
|
||||
flex-shrink: 0;
|
||||
@ -6,32 +6,70 @@
|
||||
height: var(--dv-tabs-and-actions-container-height);
|
||||
font-size: var(--dv-tabs-and-actions-container-font-size);
|
||||
|
||||
&.dv-single-tab.dv-full-width-single-tab {
|
||||
.dv-scrollable {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.dv-tabs-container {
|
||||
flex-grow: 1;
|
||||
|
||||
.dv-tab {
|
||||
flex-grow: 1;
|
||||
padding: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.dv-void-container {
|
||||
flex-grow: 0;
|
||||
}
|
||||
&.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dv-void-container {
|
||||
&.dv-single-tab.dv-full-width-single-tab {
|
||||
.tabs-container {
|
||||
flex-grow: 1;
|
||||
|
||||
.tab {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.void-container {
|
||||
flex-grow: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.void-container {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.dv-right-actions-container {
|
||||
.tabs-container {
|
||||
display: flex;
|
||||
overflow-x: overlay;
|
||||
overflow-y: hidden;
|
||||
|
||||
scrollbar-width: thin; // firefox
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
height: 3px;
|
||||
}
|
||||
|
||||
/* Track */
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/* Handle */
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: var(--dv-tabs-container-scrollbar-color);
|
||||
}
|
||||
|
||||
.tab {
|
||||
-webkit-user-drag: element;
|
||||
outline: none;
|
||||
min-width: 75px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
|
||||
&:not(:first-child)::before {
|
||||
content: ' ';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 5;
|
||||
pointer-events: none;
|
||||
background-color: var(--dv-tab-divider-color);
|
||||
width: 1px;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,58 +1,36 @@
|
||||
import {
|
||||
IDisposable,
|
||||
CompositeDisposable,
|
||||
Disposable,
|
||||
MutableDisposable,
|
||||
IValueDisposable,
|
||||
} from '../../../lifecycle';
|
||||
import { addDisposableListener, Emitter, Event } from '../../../events';
|
||||
import { Tab } from '../tab/tab';
|
||||
import { ITab, Tab } from '../tab/tab';
|
||||
import { DockviewComponent } from '../../dockviewComponent';
|
||||
import { DockviewGroupPanel } from '../../dockviewGroupPanel';
|
||||
import { VoidContainer } from './voidContainer';
|
||||
import { findRelativeZIndexParent, toggleClass } from '../../../dom';
|
||||
import { IDockviewPanel } from '../../dockviewPanel';
|
||||
import { DockviewComponent } from '../../dockviewComponent';
|
||||
import { WillShowOverlayLocationEvent } from '../../dockviewGroupPanelModel';
|
||||
import { getPanelData } from '../../../dnd/dataTransfer';
|
||||
import { Tabs } from './tabs';
|
||||
import {
|
||||
createDropdownElementHandle,
|
||||
DropdownElement,
|
||||
} from './tabOverflowControl';
|
||||
import { toggleClass } from '../../../dom';
|
||||
import { DockviewPanel, IDockviewPanel } from '../../dockviewPanel';
|
||||
|
||||
export interface TabDropIndexEvent {
|
||||
readonly event: DragEvent;
|
||||
readonly index: number;
|
||||
}
|
||||
|
||||
export interface TabDragEvent {
|
||||
readonly nativeEvent: DragEvent;
|
||||
readonly panel: IDockviewPanel;
|
||||
}
|
||||
|
||||
export interface GroupDragEvent {
|
||||
readonly nativeEvent: DragEvent;
|
||||
readonly group: DockviewGroupPanel;
|
||||
}
|
||||
|
||||
export interface ITabsContainer extends IDisposable {
|
||||
readonly element: HTMLElement;
|
||||
readonly panels: string[];
|
||||
readonly size: number;
|
||||
readonly onDrop: Event<TabDropIndexEvent>;
|
||||
readonly onTabDragStart: Event<TabDragEvent>;
|
||||
readonly onGroupDragStart: Event<GroupDragEvent>;
|
||||
readonly onWillShowOverlay: Event<WillShowOverlayLocationEvent>;
|
||||
hidden: boolean;
|
||||
delete(id: string): void;
|
||||
indexOf(id: string): number;
|
||||
setActive(isGroupActive: boolean): void;
|
||||
setActivePanel(panel: IDockviewPanel): void;
|
||||
isActive(tab: Tab): boolean;
|
||||
closePanel(panel: IDockviewPanel): void;
|
||||
openPanel(panel: IDockviewPanel, index?: number): void;
|
||||
delete: (id: string) => void;
|
||||
indexOf: (id: string) => number;
|
||||
onDrop: Event<TabDropIndexEvent>;
|
||||
setActive: (isGroupActive: boolean) => void;
|
||||
setActivePanel: (panel: IDockviewPanel) => void;
|
||||
isActive: (tab: ITab) => boolean;
|
||||
closePanel: (panel: IDockviewPanel) => void;
|
||||
openPanel: (panel: IDockviewPanel, index?: number) => void;
|
||||
setRightActionsElement(element: HTMLElement | undefined): void;
|
||||
setLeftActionsElement(element: HTMLElement | undefined): void;
|
||||
setPrefixActionsElement(element: HTMLElement | undefined): void;
|
||||
hidden: boolean;
|
||||
show(): void;
|
||||
hide(): void;
|
||||
}
|
||||
@ -62,44 +40,27 @@ export class TabsContainer
|
||||
implements ITabsContainer
|
||||
{
|
||||
private readonly _element: HTMLElement;
|
||||
private readonly tabs: Tabs;
|
||||
private readonly tabContainer: HTMLElement;
|
||||
private readonly rightActionsContainer: HTMLElement;
|
||||
private readonly leftActionsContainer: HTMLElement;
|
||||
private readonly preActionsContainer: HTMLElement;
|
||||
private readonly voidContainer: VoidContainer;
|
||||
|
||||
private tabs: IValueDisposable<ITab>[] = [];
|
||||
private selectedIndex = -1;
|
||||
private rightActions: HTMLElement | undefined;
|
||||
private leftActions: HTMLElement | undefined;
|
||||
private preActions: HTMLElement | undefined;
|
||||
|
||||
private _hidden = false;
|
||||
|
||||
private dropdownPart: DropdownElement | null = null;
|
||||
private _overflowTabs: string[] = [];
|
||||
private readonly _dropdownDisposable = new MutableDisposable();
|
||||
|
||||
private readonly _onDrop = new Emitter<TabDropIndexEvent>();
|
||||
readonly onDrop: Event<TabDropIndexEvent> = this._onDrop.event;
|
||||
|
||||
get onTabDragStart(): Event<TabDragEvent> {
|
||||
return this.tabs.onTabDragStart;
|
||||
}
|
||||
|
||||
private readonly _onGroupDragStart = new Emitter<GroupDragEvent>();
|
||||
readonly onGroupDragStart: Event<GroupDragEvent> =
|
||||
this._onGroupDragStart.event;
|
||||
|
||||
private readonly _onWillShowOverlay =
|
||||
new Emitter<WillShowOverlayLocationEvent>();
|
||||
readonly onWillShowOverlay: Event<WillShowOverlayLocationEvent> =
|
||||
this._onWillShowOverlay.event;
|
||||
|
||||
get panels(): string[] {
|
||||
return this.tabs.panels;
|
||||
return this.tabs.map((_) => _.value.panelId);
|
||||
}
|
||||
|
||||
get size(): number {
|
||||
return this.tabs.size;
|
||||
return this.tabs.length;
|
||||
}
|
||||
|
||||
get hidden(): boolean {
|
||||
@ -111,118 +72,6 @@ export class TabsContainer
|
||||
this.element.style.display = value ? 'none' : '';
|
||||
}
|
||||
|
||||
get element(): HTMLElement {
|
||||
return this._element;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private readonly accessor: DockviewComponent,
|
||||
private readonly group: DockviewGroupPanel
|
||||
) {
|
||||
super();
|
||||
|
||||
this._element = document.createElement('div');
|
||||
this._element.className = 'dv-tabs-and-actions-container';
|
||||
|
||||
toggleClass(
|
||||
this._element,
|
||||
'dv-full-width-single-tab',
|
||||
this.accessor.options.singleTabMode === 'fullwidth'
|
||||
);
|
||||
|
||||
this.rightActionsContainer = document.createElement('div');
|
||||
this.rightActionsContainer.className = 'dv-right-actions-container';
|
||||
|
||||
this.leftActionsContainer = document.createElement('div');
|
||||
this.leftActionsContainer.className = 'dv-left-actions-container';
|
||||
|
||||
this.preActionsContainer = document.createElement('div');
|
||||
this.preActionsContainer.className = 'dv-pre-actions-container';
|
||||
|
||||
this.tabs = new Tabs(group, accessor, {
|
||||
showTabsOverflowControl: !accessor.options.disableTabsOverflowList,
|
||||
});
|
||||
|
||||
this.voidContainer = new VoidContainer(this.accessor, this.group);
|
||||
|
||||
this._element.appendChild(this.preActionsContainer);
|
||||
this._element.appendChild(this.tabs.element);
|
||||
this._element.appendChild(this.leftActionsContainer);
|
||||
this._element.appendChild(this.voidContainer.element);
|
||||
this._element.appendChild(this.rightActionsContainer);
|
||||
|
||||
this.addDisposables(
|
||||
this.tabs.onDrop((e) => this._onDrop.fire(e)),
|
||||
this.tabs.onWillShowOverlay((e) => this._onWillShowOverlay.fire(e)),
|
||||
accessor.onDidOptionsChange(() => {
|
||||
this.tabs.showTabsOverflowControl =
|
||||
!accessor.options.disableTabsOverflowList;
|
||||
}),
|
||||
this.tabs.onOverflowTabsChange((event) => {
|
||||
this.toggleDropdown(event);
|
||||
}),
|
||||
this.tabs,
|
||||
this._onWillShowOverlay,
|
||||
this._onDrop,
|
||||
this._onGroupDragStart,
|
||||
this.voidContainer,
|
||||
this.voidContainer.onDragStart((event) => {
|
||||
this._onGroupDragStart.fire({
|
||||
nativeEvent: event,
|
||||
group: this.group,
|
||||
});
|
||||
}),
|
||||
this.voidContainer.onDrop((event) => {
|
||||
this._onDrop.fire({
|
||||
event: event.nativeEvent,
|
||||
index: this.tabs.size,
|
||||
});
|
||||
}),
|
||||
this.voidContainer.onWillShowOverlay((event) => {
|
||||
this._onWillShowOverlay.fire(
|
||||
new WillShowOverlayLocationEvent(event, {
|
||||
kind: 'header_space',
|
||||
panel: this.group.activePanel,
|
||||
api: this.accessor.api,
|
||||
group: this.group,
|
||||
getData: getPanelData,
|
||||
})
|
||||
);
|
||||
}),
|
||||
addDisposableListener(
|
||||
this.voidContainer.element,
|
||||
'pointerdown',
|
||||
(event) => {
|
||||
if (event.defaultPrevented) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isFloatingGroupsEnabled =
|
||||
!this.accessor.options.disableFloatingGroups;
|
||||
|
||||
if (
|
||||
isFloatingGroupsEnabled &&
|
||||
event.shiftKey &&
|
||||
this.group.api.location.type !== 'floating'
|
||||
) {
|
||||
event.preventDefault();
|
||||
|
||||
const { top, left } =
|
||||
this.element.getBoundingClientRect();
|
||||
const { top: rootTop, left: rootLeft } =
|
||||
this.accessor.element.getBoundingClientRect();
|
||||
|
||||
this.accessor.addFloatingGroup(this.group, {
|
||||
x: left - rootLeft + 20,
|
||||
y: top - rootTop + 20,
|
||||
inDragMode: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
show(): void {
|
||||
if (!this.hidden) {
|
||||
this.element.style.display = '';
|
||||
@ -261,143 +110,259 @@ export class TabsContainer
|
||||
}
|
||||
}
|
||||
|
||||
setPrefixActionsElement(element: HTMLElement | undefined): void {
|
||||
if (this.preActions === element) {
|
||||
return;
|
||||
}
|
||||
if (this.preActions) {
|
||||
this.preActions.remove();
|
||||
this.preActions = undefined;
|
||||
}
|
||||
if (element) {
|
||||
this.preActionsContainer.appendChild(element);
|
||||
this.preActions = element;
|
||||
}
|
||||
get element(): HTMLElement {
|
||||
return this._element;
|
||||
}
|
||||
|
||||
isActive(tab: Tab): boolean {
|
||||
return this.tabs.isActive(tab);
|
||||
public isActive(tab: ITab): boolean {
|
||||
return (
|
||||
this.selectedIndex > -1 &&
|
||||
this.tabs[this.selectedIndex].value === tab
|
||||
);
|
||||
}
|
||||
|
||||
indexOf(id: string): number {
|
||||
return this.tabs.indexOf(id);
|
||||
public indexOf(id: string): number {
|
||||
return this.tabs.findIndex((tab) => tab.value.panelId === id);
|
||||
}
|
||||
|
||||
setActive(_isGroupActive: boolean) {
|
||||
// noop
|
||||
}
|
||||
constructor(
|
||||
private readonly accessor: DockviewComponent,
|
||||
private readonly group: DockviewGroupPanel
|
||||
) {
|
||||
super();
|
||||
|
||||
delete(id: string): void {
|
||||
this.tabs.delete(id);
|
||||
this.updateClassnames();
|
||||
}
|
||||
this.addDisposables(this._onDrop);
|
||||
|
||||
setActivePanel(panel: IDockviewPanel): void {
|
||||
this.tabs.setActivePanel(panel);
|
||||
}
|
||||
this._element = document.createElement('div');
|
||||
this._element.className = 'tabs-and-actions-container';
|
||||
|
||||
openPanel(panel: IDockviewPanel, index: number = this.tabs.size): void {
|
||||
this.tabs.openPanel(panel, index);
|
||||
this.updateClassnames();
|
||||
}
|
||||
toggleClass(
|
||||
this._element,
|
||||
'dv-full-width-single-tab',
|
||||
this.accessor.options.singleTabMode === 'fullwidth'
|
||||
);
|
||||
|
||||
closePanel(panel: IDockviewPanel): void {
|
||||
this.delete(panel.id);
|
||||
}
|
||||
this.addDisposables(
|
||||
this.accessor.onDidAddPanel((e) => {
|
||||
if (e.api.group === this.group) {
|
||||
toggleClass(
|
||||
this._element,
|
||||
'dv-single-tab',
|
||||
this.size === 1
|
||||
);
|
||||
}
|
||||
}),
|
||||
this.accessor.onDidRemovePanel((e) => {
|
||||
if (e.api.group === this.group) {
|
||||
toggleClass(
|
||||
this._element,
|
||||
'dv-single-tab',
|
||||
this.size === 1
|
||||
);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
private updateClassnames(): void {
|
||||
toggleClass(this._element, 'dv-single-tab', this.size === 1);
|
||||
}
|
||||
this.rightActionsContainer = document.createElement('div');
|
||||
this.rightActionsContainer.className = 'right-actions-container';
|
||||
|
||||
private toggleDropdown(options: { tabs: string[]; reset: boolean }): void {
|
||||
const tabs = options.reset ? [] : options.tabs;
|
||||
this._overflowTabs = tabs;
|
||||
this.leftActionsContainer = document.createElement('div');
|
||||
this.leftActionsContainer.className = 'left-actions-container';
|
||||
|
||||
if (this._overflowTabs.length > 0 && this.dropdownPart) {
|
||||
this.dropdownPart.update({ tabs: tabs.length });
|
||||
return;
|
||||
}
|
||||
this.tabContainer = document.createElement('div');
|
||||
this.tabContainer.className = 'tabs-container';
|
||||
|
||||
if (this._overflowTabs.length === 0) {
|
||||
this._dropdownDisposable.dispose();
|
||||
return;
|
||||
}
|
||||
this.voidContainer = new VoidContainer(this.accessor, this.group);
|
||||
|
||||
const root = document.createElement('div');
|
||||
root.className = 'dv-tabs-overflow-dropdown-root';
|
||||
this._element.appendChild(this.tabContainer);
|
||||
this._element.appendChild(this.leftActionsContainer);
|
||||
this._element.appendChild(this.voidContainer.element);
|
||||
this._element.appendChild(this.rightActionsContainer);
|
||||
|
||||
const part = createDropdownElementHandle();
|
||||
part.update({ tabs: tabs.length });
|
||||
|
||||
this.dropdownPart = part;
|
||||
|
||||
root.appendChild(part.element);
|
||||
this.rightActionsContainer.prepend(root);
|
||||
|
||||
this._dropdownDisposable.value = new CompositeDisposable(
|
||||
Disposable.from(() => {
|
||||
root.remove();
|
||||
this.dropdownPart?.dispose?.();
|
||||
this.dropdownPart = null;
|
||||
this.addDisposables(
|
||||
this.voidContainer,
|
||||
this.voidContainer.onDrop((event) => {
|
||||
this._onDrop.fire({
|
||||
event: event.nativeEvent,
|
||||
index: this.tabs.length,
|
||||
});
|
||||
}),
|
||||
addDisposableListener(
|
||||
root,
|
||||
'pointerdown',
|
||||
this.voidContainer.element,
|
||||
'mousedown',
|
||||
(event) => {
|
||||
event.preventDefault();
|
||||
},
|
||||
{ capture: true }
|
||||
const isFloatingGroupsEnabled =
|
||||
!this.accessor.options.disableFloatingGroups;
|
||||
|
||||
if (
|
||||
isFloatingGroupsEnabled &&
|
||||
event.shiftKey &&
|
||||
!this.group.api.isFloating
|
||||
) {
|
||||
event.preventDefault();
|
||||
|
||||
const { top, left } =
|
||||
this.element.getBoundingClientRect();
|
||||
const { top: rootTop, left: rootLeft } =
|
||||
this.accessor.element.getBoundingClientRect();
|
||||
|
||||
this.accessor.addFloatingGroup(
|
||||
this.group,
|
||||
{
|
||||
x: left - rootLeft + 20,
|
||||
y: top - rootTop + 20,
|
||||
},
|
||||
{ inDragMode: true }
|
||||
);
|
||||
}
|
||||
}
|
||||
),
|
||||
addDisposableListener(root, 'click', (event) => {
|
||||
const el = document.createElement('div');
|
||||
el.style.overflow = 'auto';
|
||||
el.className = 'dv-tabs-overflow-container';
|
||||
|
||||
for (const tab of this.tabs.tabs.filter((tab) =>
|
||||
this._overflowTabs.includes(tab.panel.id)
|
||||
)) {
|
||||
const panelObject = this.group.panels.find(
|
||||
(panel) => panel === tab.panel
|
||||
)!;
|
||||
|
||||
const tabComponent =
|
||||
panelObject.view.createTabRenderer('headerOverflow');
|
||||
|
||||
const child = tabComponent.element;
|
||||
|
||||
const wrapper = document.createElement('div');
|
||||
toggleClass(wrapper, 'dv-tab', true);
|
||||
toggleClass(
|
||||
wrapper,
|
||||
'dv-active-tab',
|
||||
panelObject.api.isActive
|
||||
);
|
||||
toggleClass(
|
||||
wrapper,
|
||||
'dv-inactive-tab',
|
||||
!panelObject.api.isActive
|
||||
);
|
||||
|
||||
wrapper.addEventListener('pointerdown', () => {
|
||||
this.accessor.popupService.close();
|
||||
tab.element.scrollIntoView();
|
||||
tab.panel.api.setActive();
|
||||
});
|
||||
wrapper.appendChild(child);
|
||||
|
||||
el.appendChild(wrapper);
|
||||
addDisposableListener(this.tabContainer, 'mousedown', (event) => {
|
||||
if (event.defaultPrevented) {
|
||||
return;
|
||||
}
|
||||
|
||||
const relativeParent = findRelativeZIndexParent(root);
|
||||
const isLeftClick = event.button === 0;
|
||||
|
||||
this.accessor.popupService.openPopover(el, {
|
||||
x: event.clientX,
|
||||
y: event.clientY,
|
||||
zIndex: relativeParent?.style.zIndex
|
||||
? `calc(${relativeParent.style.zIndex} * 2)`
|
||||
: undefined,
|
||||
});
|
||||
if (isLeftClick) {
|
||||
this.accessor.doSetGroupActive(this.group);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
public setActive(_isGroupActive: boolean) {
|
||||
// noop
|
||||
}
|
||||
|
||||
private addTab(
|
||||
tab: IValueDisposable<ITab>,
|
||||
index: number = this.tabs.length
|
||||
): void {
|
||||
if (index < 0 || index > this.tabs.length) {
|
||||
throw new Error('invalid location');
|
||||
}
|
||||
|
||||
this.tabContainer.insertBefore(
|
||||
tab.value.element,
|
||||
this.tabContainer.children[index]
|
||||
);
|
||||
|
||||
this.tabs = [
|
||||
...this.tabs.slice(0, index),
|
||||
tab,
|
||||
...this.tabs.slice(index),
|
||||
];
|
||||
|
||||
if (this.selectedIndex < 0) {
|
||||
this.selectedIndex = index;
|
||||
}
|
||||
}
|
||||
|
||||
public delete(id: string): void {
|
||||
const index = this.tabs.findIndex((tab) => tab.value.panelId === id);
|
||||
|
||||
const tabToRemove = this.tabs.splice(index, 1)[0];
|
||||
|
||||
const { value, disposable } = tabToRemove;
|
||||
|
||||
disposable.dispose();
|
||||
value.dispose();
|
||||
value.element.remove();
|
||||
}
|
||||
|
||||
public setActivePanel(panel: IDockviewPanel): void {
|
||||
this.tabs.forEach((tab) => {
|
||||
const isActivePanel = panel.id === tab.value.panelId;
|
||||
tab.value.setActive(isActivePanel);
|
||||
});
|
||||
}
|
||||
|
||||
public openPanel(
|
||||
panel: IDockviewPanel,
|
||||
index: number = this.tabs.length
|
||||
): void {
|
||||
if (this.tabs.find((tab) => tab.value.panelId === panel.id)) {
|
||||
return;
|
||||
}
|
||||
const tabToAdd = new Tab(panel.id, this.accessor, this.group);
|
||||
if (!panel.view?.tab) {
|
||||
throw new Error('invalid header component');
|
||||
}
|
||||
tabToAdd.setContent(panel.view.tab);
|
||||
|
||||
const disposable = CompositeDisposable.from(
|
||||
tabToAdd.onChanged((event) => {
|
||||
const isFloatingGroupsEnabled =
|
||||
!this.accessor.options.disableFloatingGroups;
|
||||
|
||||
const isFloatingWithOnePanel =
|
||||
this.group.api.isFloating && this.size === 1;
|
||||
|
||||
if (
|
||||
isFloatingGroupsEnabled &&
|
||||
!isFloatingWithOnePanel &&
|
||||
event.shiftKey
|
||||
) {
|
||||
event.preventDefault();
|
||||
|
||||
const panel = this.accessor.getGroupPanel(tabToAdd.panelId);
|
||||
|
||||
const { top, left } =
|
||||
tabToAdd.element.getBoundingClientRect();
|
||||
const { top: rootTop, left: rootLeft } =
|
||||
this.accessor.element.getBoundingClientRect();
|
||||
|
||||
this.accessor.addFloatingGroup(
|
||||
panel as DockviewPanel,
|
||||
{
|
||||
x: left - rootLeft,
|
||||
y: top - rootTop,
|
||||
},
|
||||
{ inDragMode: true }
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const alreadyFocused =
|
||||
panel.id === this.group.model.activePanel?.id &&
|
||||
this.group.model.isContentFocused;
|
||||
|
||||
const isLeftClick = event.button === 0;
|
||||
|
||||
if (!isLeftClick || event.defaultPrevented) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.group.model.openPanel(panel, {
|
||||
skipFocus: alreadyFocused,
|
||||
});
|
||||
}),
|
||||
tabToAdd.onDrop((event) => {
|
||||
this._onDrop.fire({
|
||||
event: event.nativeEvent,
|
||||
index: this.tabs.findIndex((x) => x.value === tabToAdd),
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
const value: IValueDisposable<ITab> = { value: tabToAdd, disposable };
|
||||
|
||||
this.addTab(value, index);
|
||||
}
|
||||
|
||||
public closePanel(panel: IDockviewPanel): void {
|
||||
this.delete(panel.id);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
|
||||
for (const { value, disposable } of this.tabs) {
|
||||
disposable.dispose();
|
||||
value.dispose();
|
||||
}
|
||||
|
||||
this.tabs = [];
|
||||
}
|
||||
}
|
||||
|
@ -1,28 +1,20 @@
|
||||
import { last } from '../../../array';
|
||||
import { getPanelData } from '../../../dnd/dataTransfer';
|
||||
import {
|
||||
Droptarget,
|
||||
DroptargetEvent,
|
||||
WillShowOverlayEvent,
|
||||
} from '../../../dnd/droptarget';
|
||||
import { Droptarget, DroptargetEvent } from '../../../dnd/droptarget';
|
||||
import { GroupDragHandler } from '../../../dnd/groupDragHandler';
|
||||
import { DockviewComponent } from '../../dockviewComponent';
|
||||
import { addDisposableListener, Emitter, Event } from '../../../events';
|
||||
import { CompositeDisposable } from '../../../lifecycle';
|
||||
import { DockviewGroupPanel } from '../../dockviewGroupPanel';
|
||||
import { DockviewGroupPanelModel } from '../../dockviewGroupPanelModel';
|
||||
import { DockviewDropTargets } from '../../types';
|
||||
|
||||
export class VoidContainer extends CompositeDisposable {
|
||||
private readonly _element: HTMLElement;
|
||||
private readonly dropTraget: Droptarget;
|
||||
private readonly voidDropTarget: Droptarget;
|
||||
|
||||
private readonly _onDrop = new Emitter<DroptargetEvent>();
|
||||
readonly onDrop: Event<DroptargetEvent> = this._onDrop.event;
|
||||
|
||||
private readonly _onDragStart = new Emitter<DragEvent>();
|
||||
readonly onDragStart = this._onDragStart.event;
|
||||
|
||||
readonly onWillShowOverlay: Event<WillShowOverlayEvent>;
|
||||
|
||||
get element(): HTMLElement {
|
||||
return this._element;
|
||||
}
|
||||
@ -35,48 +27,51 @@ export class VoidContainer extends CompositeDisposable {
|
||||
|
||||
this._element = document.createElement('div');
|
||||
|
||||
this._element.className = 'dv-void-container';
|
||||
this._element.className = 'void-container';
|
||||
this._element.tabIndex = 0;
|
||||
this._element.draggable = true;
|
||||
|
||||
this.addDisposables(
|
||||
this._onDrop,
|
||||
this._onDragStart,
|
||||
addDisposableListener(this._element, 'pointerdown', () => {
|
||||
addDisposableListener(this._element, 'click', () => {
|
||||
this.accessor.doSetGroupActive(this.group);
|
||||
})
|
||||
);
|
||||
|
||||
const handler = new GroupDragHandler(this._element, accessor, group);
|
||||
const handler = new GroupDragHandler(this._element, accessor.id, group);
|
||||
|
||||
this.dropTraget = new Droptarget(this._element, {
|
||||
this.voidDropTarget = new Droptarget(this._element, {
|
||||
acceptedTargetZones: ['center'],
|
||||
canDisplayOverlay: (event, position) => {
|
||||
const data = getPanelData();
|
||||
|
||||
if (data && this.accessor.id === data.viewId) {
|
||||
return true;
|
||||
if (
|
||||
data.panelId === null &&
|
||||
data.groupId === this.group.id
|
||||
) {
|
||||
// don't allow group move to drop on self
|
||||
return false;
|
||||
}
|
||||
|
||||
// don't show the overlay if the tab being dragged is the last panel of this group
|
||||
return last(this.group.panels)?.id !== data.panelId;
|
||||
}
|
||||
|
||||
return group.model.canDisplayOverlay(
|
||||
event,
|
||||
position,
|
||||
'header_space'
|
||||
DockviewDropTargets.Panel
|
||||
);
|
||||
},
|
||||
getOverrideTarget: () => group.model.dropTargetContainer?.model,
|
||||
});
|
||||
|
||||
this.onWillShowOverlay = this.dropTraget.onWillShowOverlay;
|
||||
|
||||
this.addDisposables(
|
||||
handler,
|
||||
handler.onDragStart((event) => {
|
||||
this._onDragStart.fire(event);
|
||||
}),
|
||||
this.dropTraget.onDrop((event) => {
|
||||
this.voidDropTarget.onDrop((event) => {
|
||||
this._onDrop.fire(event);
|
||||
}),
|
||||
this.dropTraget
|
||||
this.voidDropTarget
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,45 @@
|
||||
.dv-watermark {
|
||||
.watermark {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
&.has-actions {
|
||||
.watermark-title {
|
||||
.actions-container {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.watermark-title {
|
||||
height: 35px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
}
|
||||
.watermark-content {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.actions-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0px 8px;
|
||||
// padding: 0px;
|
||||
// margin: 0px;
|
||||
// justify-content: flex-end;
|
||||
|
||||
.close-action {
|
||||
padding: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
color: var(--dv-activegroup-hiddenpanel-tab-color);
|
||||
|
||||
&:hover {
|
||||
border-radius: 2px;
|
||||
background-color: var(--dv-icon-hover-background-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,13 +2,21 @@ import {
|
||||
IWatermarkRenderer,
|
||||
WatermarkRendererInitParameters,
|
||||
} from '../../types';
|
||||
import { addDisposableListener } from '../../../events';
|
||||
import { toggleClass } from '../../../dom';
|
||||
import { CompositeDisposable } from '../../../lifecycle';
|
||||
import { DockviewGroupPanel } from '../../dockviewGroupPanel';
|
||||
import { PanelUpdateEvent } from '../../../panel/types';
|
||||
import { createCloseButton } from '../../../svg';
|
||||
import { DockviewApi } from '../../../api/component.api';
|
||||
|
||||
export class Watermark
|
||||
extends CompositeDisposable
|
||||
implements IWatermarkRenderer
|
||||
{
|
||||
private readonly _element: HTMLElement;
|
||||
private _element: HTMLElement;
|
||||
private _group: DockviewGroupPanel | undefined;
|
||||
private _api: DockviewApi | undefined;
|
||||
|
||||
get element(): HTMLElement {
|
||||
return this._element;
|
||||
@ -17,10 +25,70 @@ export class Watermark
|
||||
constructor() {
|
||||
super();
|
||||
this._element = document.createElement('div');
|
||||
this._element.className = 'dv-watermark';
|
||||
this._element.className = 'watermark';
|
||||
|
||||
const title = document.createElement('div');
|
||||
title.className = 'watermark-title';
|
||||
|
||||
const emptySpace = document.createElement('span');
|
||||
emptySpace.style.flexGrow = '1';
|
||||
|
||||
const content = document.createElement('div');
|
||||
content.className = 'watermark-content';
|
||||
|
||||
this._element.appendChild(title);
|
||||
this._element.appendChild(content);
|
||||
|
||||
const actionsContainer = document.createElement('div');
|
||||
actionsContainer.className = 'actions-container';
|
||||
|
||||
const closeAnchor = document.createElement('div');
|
||||
closeAnchor.className = 'close-action';
|
||||
closeAnchor.appendChild(createCloseButton());
|
||||
|
||||
actionsContainer.appendChild(closeAnchor);
|
||||
|
||||
title.appendChild(emptySpace);
|
||||
title.appendChild(actionsContainer);
|
||||
|
||||
this.addDisposables(
|
||||
addDisposableListener(closeAnchor, 'click', (ev) => {
|
||||
ev.preventDefault();
|
||||
if (this._group) {
|
||||
this._api?.removeGroup(this._group);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
update(_event: PanelUpdateEvent): void {
|
||||
// noop
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
// noop
|
||||
}
|
||||
|
||||
layout(_width: number, _height: number): void {
|
||||
// noop
|
||||
}
|
||||
|
||||
init(_params: WatermarkRendererInitParameters): void {
|
||||
// noop
|
||||
this._api = _params.containerApi;
|
||||
this.render();
|
||||
}
|
||||
|
||||
updateParentGroup(group: DockviewGroupPanel, _visible: boolean): void {
|
||||
this._group = group;
|
||||
this.render();
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
private render(): void {
|
||||
const isOneGroup = !!(this._api && this._api.size <= 1);
|
||||
toggleClass(this.element, 'has-actions', isOneGroup);
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ interface LegacyState extends GroupviewPanelState {
|
||||
}
|
||||
|
||||
export class DefaultDockviewDeserialzier implements IPanelDeserializer {
|
||||
constructor(private readonly accessor: DockviewComponent) {}
|
||||
constructor(private readonly layout: DockviewComponent) {}
|
||||
|
||||
public fromJSON(
|
||||
panelData: GroupviewPanelState,
|
||||
@ -35,13 +35,13 @@ export class DefaultDockviewDeserialzier implements IPanelDeserializer {
|
||||
|
||||
const contentComponent = viewData
|
||||
? viewData.content.id
|
||||
: panelData.contentComponent ?? 'unknown';
|
||||
: panelData.contentComponent || 'unknown';
|
||||
const tabComponent = viewData
|
||||
? viewData.tab?.id
|
||||
: panelData.tabComponent;
|
||||
|
||||
const view = new DockviewPanelModel(
|
||||
this.accessor,
|
||||
this.layout,
|
||||
panelId,
|
||||
contentComponent,
|
||||
tabComponent
|
||||
@ -49,24 +49,15 @@ export class DefaultDockviewDeserialzier implements IPanelDeserializer {
|
||||
|
||||
const panel = new DockviewPanel(
|
||||
panelId,
|
||||
contentComponent,
|
||||
tabComponent,
|
||||
this.accessor,
|
||||
new DockviewApi(this.accessor),
|
||||
this.layout,
|
||||
new DockviewApi(this.layout),
|
||||
group,
|
||||
view,
|
||||
{
|
||||
renderer: panelData.renderer,
|
||||
minimumWidth: panelData.minimumWidth,
|
||||
minimumHeight: panelData.minimumHeight,
|
||||
maximumWidth: panelData.maximumWidth,
|
||||
maximumHeight: panelData.maximumHeight,
|
||||
}
|
||||
view
|
||||
);
|
||||
|
||||
panel.init({
|
||||
title: title ?? panelId,
|
||||
params: params ?? {},
|
||||
title: title || panelId,
|
||||
params: params || {},
|
||||
});
|
||||
|
||||
return panel;
|
||||
|
@ -10,46 +10,38 @@
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.dv-overlay-render-container {
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
.dv-groupview {
|
||||
&.dv-active-group {
|
||||
> .dv-tabs-and-actions-container {
|
||||
.dv-tabs-container > .dv-tab {
|
||||
&.dv-active-tab {
|
||||
background-color: var(
|
||||
--dv-activegroup-visiblepanel-tab-background-color
|
||||
);
|
||||
color: var(--dv-activegroup-visiblepanel-tab-color);
|
||||
}
|
||||
&.dv-inactive-tab {
|
||||
background-color: var(
|
||||
--dv-activegroup-hiddenpanel-tab-background-color
|
||||
);
|
||||
color: var(--dv-activegroup-hiddenpanel-tab-color);
|
||||
}
|
||||
.groupview {
|
||||
&.active-group {
|
||||
> .tabs-and-actions-container > .tabs-container > .tab {
|
||||
&.active-tab {
|
||||
background-color: var(
|
||||
--dv-activegroup-visiblepanel-tab-background-color
|
||||
);
|
||||
color: var(--dv-activegroup-visiblepanel-tab-color);
|
||||
}
|
||||
&.inactive-tab {
|
||||
background-color: var(
|
||||
--dv-activegroup-hiddenpanel-tab-background-color
|
||||
);
|
||||
color: var(--dv-activegroup-hiddenpanel-tab-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
&.dv-inactive-group {
|
||||
> .dv-tabs-and-actions-container {
|
||||
.dv-tabs-container > .dv-tab {
|
||||
&.dv-active-tab {
|
||||
background-color: var(
|
||||
--dv-inactivegroup-visiblepanel-tab-background-color
|
||||
);
|
||||
color: var(--dv-inactivegroup-visiblepanel-tab-color);
|
||||
}
|
||||
&.dv-inactive-tab {
|
||||
background-color: var(
|
||||
--dv-inactivegroup-hiddenpanel-tab-background-color
|
||||
);
|
||||
color: var(--dv-inactivegroup-hiddenpanel-tab-color);
|
||||
}
|
||||
&.inactive-group {
|
||||
> .tabs-and-actions-container > .tabs-container > .tab {
|
||||
&.active-tab {
|
||||
background-color: var(
|
||||
--dv-inactivegroup-visiblepanel-tab-background-color
|
||||
);
|
||||
color: var(--dv-inactivegroup-visiblepanel-tab-color);
|
||||
}
|
||||
&.inactive-tab {
|
||||
background-color: var(
|
||||
--dv-inactivegroup-hiddenpanel-tab-background-color
|
||||
);
|
||||
color: var(--dv-inactivegroup-hiddenpanel-tab-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -59,7 +51,7 @@
|
||||
* when a tab is dragged we lose the above stylings because they are conditional on parent elements
|
||||
* therefore we also set some stylings for the dragging event
|
||||
**/
|
||||
.dv-tab {
|
||||
.tab {
|
||||
&.dv-tab-dragging {
|
||||
background-color: var(
|
||||
--dv-activegroup-visiblepanel-tab-background-color
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user