This commit is contained in:
mathuo 2020-09-20 20:00:31 +01:00
parent 62909463ed
commit 9fc0603d61
104 changed files with 27018 additions and 26799 deletions

3
.prettierignore Normal file
View File

@ -0,0 +1,3 @@
typedocs/
dist/
build/

6
.prettierrc Normal file
View File

@ -0,0 +1,6 @@
{
"trailingComma": "es5",
"tabWidth": 4,
"semi": false,
"singleQuote": true
}

13
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,13 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
// List of extensions which should be recommended for users of this workspace.
"recommendations": [
"esbenp.prettier-vscode"
],
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
"unwantedRecommendations": [
]
}

View File

@ -1,16 +1,16 @@
module.exports = { module.exports = {
transform: { transform: {
"^.+\\.tsx?$": "ts-jest", '^.+\\.tsx?$': 'ts-jest',
}, },
testEnvironment: "jsdom", testEnvironment: 'jsdom',
collectCoverageFrom: ["**/*.{ts,tsx}", "!**/node_modules/**"], collectCoverageFrom: ['**/*.{ts,tsx}', '!**/node_modules/**'],
moduleNameMapper: { moduleNameMapper: {
"\\.(css|less|sass|scss)$": '\\.(css|less|sass|scss)$':
"<rootDir>/src/__tests__/__mocks__/styleMock.js", '<rootDir>/src/__tests__/__mocks__/styleMock.js',
}, },
testMatch: [ testMatch: [
"<rootDir>/src/__tests__/**/*.spec.ts", '<rootDir>/src/__tests__/**/*.spec.ts',
"<rootDir>/src/__tests__/**/*.spec.tsx", '<rootDir>/src/__tests__/**/*.spec.tsx',
], ],
setupFilesAfterEnv: ["<rootDir>/src/__tests__/setupTests.ts"], setupFilesAfterEnv: ['<rootDir>/src/__tests__/setupTests.ts'],
}; }

View File

@ -1,6 +1,4 @@
{ {
"packages": [ "packages": ["packages/*"],
"packages/*" "version": "0.0.0"
],
"version": "0.0.0"
} }

36436
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,44 +1,44 @@
{ {
"name": "splitview-root", "name": "splitview-root",
"version": "1.0.0", "version": "1.0.0",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1" "test": "echo \"Error: no test specified\" && exit 1"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/mathuo/splitview.git" "url": "git+https://github.com/mathuo/splitview.git"
}, },
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"bugs": { "bugs": {
"url": "https://github.com/mathuo/splitview/issues" "url": "https://github.com/mathuo/splitview/issues"
}, },
"homepage": "https://github.com/mathuo/splitview#readme", "homepage": "https://github.com/mathuo/splitview#readme",
"devDependencies": { "devDependencies": {
"@testing-library/jest-dom": "^5.10.1", "@testing-library/jest-dom": "^5.10.1",
"@types/jest": "^26.0.3", "@types/jest": "^26.0.3",
"css-loader": "^3.6.0", "css-loader": "^3.6.0",
"gulp": "^4.0.2", "gulp": "^4.0.2",
"gulp-clean": "^0.4.0", "gulp-clean": "^0.4.0",
"gulp-concat": "^2.6.1", "gulp-concat": "^2.6.1",
"gulp-header": "^2.0.9", "gulp-header": "^2.0.9",
"gulp-sass": "^4.1.0", "gulp-sass": "^4.1.0",
"gulp-sourcemaps": "^2.6.5", "gulp-sourcemaps": "^2.6.5",
"gulp-typescript": "^6.0.0-alpha.1", "gulp-typescript": "^6.0.0-alpha.1",
"jest": "^26.1.0", "jest": "^26.1.0",
"jsdom": "^16.2.2", "jsdom": "^16.2.2",
"lerna": "^3.22.1", "lerna": "^3.22.1",
"merge2": "^1.4.1", "merge2": "^1.4.1",
"node-sass": "^4.14.1", "node-sass": "^4.14.1",
"sass-loader": "^8.0.2", "sass-loader": "^8.0.2",
"style-loader": "^1.2.1", "style-loader": "^1.2.1",
"ts-jest": "^26.1.1", "ts-jest": "^26.1.1",
"ts-loader": "^7.0.5", "ts-loader": "^7.0.5",
"typescript": "^3.9.5", "typescript": "^3.9.5",
"webpack": "^4.43.0", "webpack": "^4.43.0",
"webpack-cli": "^3.3.12", "webpack-cli": "^3.3.12",
"webpack-dev-server": "^3.11.0" "webpack-dev-server": "^3.11.0"
} }
} }

View File

@ -1,22 +1,22 @@
{ {
"name": "splitview-demo", "name": "splitview-demo",
"version": "1.0.0", "version": "1.0.0",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"build": "bash ../../node_modules/.bin/webpack --config webpack.config.js", "build": "bash ../../node_modules/.bin/webpack --config webpack.config.js",
"start": "bash ../../node_modules/.bin/webpack-dev-server --config webpack.config.js" "start": "../../node_modules/.bin/webpack-dev-server --config webpack.config.js"
}, },
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"react": "^16.13.1", "react": "^16.13.1",
"react-dom": "^16.13.1", "react-dom": "^16.13.1",
"splitview": "*", "splitview": "*",
"splitview-react": "*" "splitview-react": "*"
}, },
"devDependencies": { "devDependencies": {
"@types/react": "^16.9.41", "@types/react": "^16.9.41",
"@types/react-dom": "^16.9.8" "@types/react-dom": "^16.9.8"
} }
} }

View File

@ -1,6 +1,6 @@
<html> <html>
<body> <body>
<div id="app"></div> <div id="app"></div>
<script src="/dist/bundle.js"></script> <script src="/dist/bundle.js"></script>
</body> </body>
</html> </html>

View File

@ -1,52 +1,52 @@
import * as React from "react"; import * as React from 'react'
// import { LoadFromConfig } from "./loadFromConfig"; // import { LoadFromConfig } from "./loadFromConfig";
// import { FromApi } from "./fromApi"; // import { FromApi } from "./fromApi";
// import { PaneDemo } from "./pane"; // import { PaneDemo } from "./pane";
import { TestGrid } from "./layout-grid/reactgrid"; import { TestGrid } from './layout-grid/reactgrid'
import { Application } from "./layout-grid/application"; import { Application } from './layout-grid/application'
const options = [ const options = [
// { id: "config", component: LoadFromConfig }, // { id: "config", component: LoadFromConfig },
// { id: "api", component: FromApi }, // { id: "api", component: FromApi },
// { id: "pane", component: PaneDemo }, // { id: "pane", component: PaneDemo },
{ id: "grid", component: Application }, { id: 'grid', component: Application },
]; ]
export const App = () => { export const App = () => {
const [value, setValue] = React.useState<string>(options[0].id); const [value, setValue] = React.useState<string>(options[0].id)
const onChange = (event: React.ChangeEvent<HTMLSelectElement>) => const onChange = (event: React.ChangeEvent<HTMLSelectElement>) =>
setValue(event.target.value); setValue(event.target.value)
const Component = React.useMemo( const Component = React.useMemo(
() => options.find((o) => o.id === value)?.component, () => options.find((o) => o.id === value)?.component,
[value] [value]
); )
return ( return (
<div <div
style={{ style={{
height: "100vh", height: '100vh',
width: "100vw", width: '100vw',
display: "flex", display: 'flex',
flexDirection: "column", flexDirection: 'column',
}} }}
> >
<div style={{ height: "20px", flexShrink: 0 }}> <div style={{ height: '20px', flexShrink: 0 }}>
<select onChange={onChange} value={value}> <select onChange={onChange} value={value}>
{options.map((option, i) => ( {options.map((option, i) => (
<option key={i} value={option.id}> <option key={i} value={option.id}>
{option.id}{" "} {option.id}{' '}
</option> </option>
))} ))}
</select> </select>
</div> </div>
{Component && ( {Component && (
<div style={{ width: "100%", flexGrow: 1 }}> <div style={{ width: '100%', flexGrow: 1 }}>
<Component /> <Component />
</div>
)}
</div> </div>
)} )
</div> }
);
};

View File

@ -1,9 +1,9 @@
.header { .header {
padding-left: 10px; padding-left: 10px;
display: flex; display: flex;
span { span {
margin-top: auto; margin-top: auto;
margin-bottom: auto; margin-bottom: auto;
} }
} }

View File

@ -1,15 +1,15 @@
@import "~splitview/dist/styles.css"; @import '~splitview/dist/styles.css';
body { body {
margin: 0; margin: 0;
font-family: Arial, "Helvetica Neue", Helvetica, sans-serif; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif;
overflow: hidden; overflow: hidden;
} }
*, *,
*::after, *::after,
*::before { *::before {
// -webkit-user-drag: none; // -webkit-user-drag: none;
-webkit-app-region: no-drag; -webkit-app-region: no-drag;
-webkit-user-select: none; -webkit-user-select: none;
} }

View File

@ -1,6 +1,6 @@
import * as React from "react"; import * as React from 'react'
import * as ReactDOM from "react-dom"; import * as ReactDOM from 'react-dom'
import { App } from "./app"; import { App } from './app'
import "./index.scss"; import './index.scss'
ReactDOM.render(<App />, document.getElementById("app")); ReactDOM.render(<App />, document.getElementById('app'))

View File

@ -1,75 +1,83 @@
import * as React from "react"; import * as React from 'react'
import { import {
Orientation, Orientation,
GridviewComponent, GridviewComponent,
LayoutPriority, LayoutPriority,
GridviewReadyEvent, GridviewReadyEvent,
ComponentGridview, ComponentGridview,
IGridviewPanelProps, IGridviewPanelProps,
} from "splitview"; } from 'splitview'
import { TestGrid } from "./reactgrid"; import { TestGrid } from './reactgrid'
const rootcomponents: { const rootcomponents: {
[index: string]: React.FunctionComponent<IGridviewPanelProps>; [index: string]: React.FunctionComponent<IGridviewPanelProps>
} = { } = {
sidebar: (props: IGridviewPanelProps) => { sidebar: (props: IGridviewPanelProps) => {
return <div style={{backgroundColor: "rgb(37,37,38)", height:"100%"}}>sidebar</div>; return (
}, <div style={{ backgroundColor: 'rgb(37,37,38)', height: '100%' }}>
editor: TestGrid, sidebar
panel: () => { </div>
return <div style={{backgroundColor: "rgb(30,30,30)", height: "100%"}}>panel</div>; )
}, },
}; editor: TestGrid,
panel: () => {
return (
<div style={{ backgroundColor: 'rgb(30,30,30)', height: '100%' }}>
panel
</div>
)
},
}
export const Application = () => { export const Application = () => {
const api = React.useRef<ComponentGridview>(); const api = React.useRef<ComponentGridview>()
const onReady = (event: GridviewReadyEvent) => { const onReady = (event: GridviewReadyEvent) => {
// event.api.deserialize(rootLayout); // event.api.deserialize(rootLayout);
event.api.addComponent({ event.api.addComponent({
id: "1", id: '1',
component: "sidebar", component: 'sidebar',
snap: true, snap: true,
}); })
event.api.addComponent({ event.api.addComponent({
id: "2", id: '2',
component: "editor", component: 'editor',
snap: true, snap: true,
position: { reference: "1", direction: "right" }, position: { reference: '1', direction: 'right' },
priority: LayoutPriority.High, priority: LayoutPriority.High,
}); })
api.current = event.api as ComponentGridview; api.current = event.api as ComponentGridview
}; }
React.useEffect(() => { React.useEffect(() => {
const callback = (ev: UIEvent) => { const callback = (ev: UIEvent) => {
const height = window.innerHeight - 20; const height = window.innerHeight - 20
const width = window.innerWidth; const width = window.innerWidth
api.current?.layout(width, height); api.current?.layout(width, height)
}; }
window.addEventListener("resize", callback); window.addEventListener('resize', callback)
callback(undefined); callback(undefined)
api.current.addComponent({ api.current.addComponent({
id: "3", id: '3',
component: "panel", component: 'panel',
position: { reference: "2", direction: "below" }, position: { reference: '2', direction: 'below' },
size: 200, size: 200,
snap: true, snap: true,
}); })
return () => { return () => {
window.removeEventListener("resize", callback); window.removeEventListener('resize', callback)
}; }
}, []); }, [])
return ( return (
<GridviewComponent <GridviewComponent
components={rootcomponents} components={rootcomponents}
onReady={onReady} onReady={onReady}
orientation={Orientation.HORIZONTAL} orientation={Orientation.HORIZONTAL}
/> />
); )
}; }

View File

@ -1,6 +1,6 @@
import * as React from "react"; import * as React from 'react'
import { IPanelProps } from "splitview"; import { IPanelProps } from 'splitview'
export const CustomTab = (props: IPanelProps) => { export const CustomTab = (props: IPanelProps) => {
return <div>hello</div>; return <div>hello</div>
}; }

View File

@ -1,33 +1,39 @@
import * as React from "react"; import * as React from 'react'
import { Api, IPanelProps } from "splitview"; import { Api, IPanelProps } from 'splitview'
export const Editor = (props: IPanelProps & { layoutApi: Api }) => { export const Editor = (props: IPanelProps & { layoutApi: Api }) => {
const [tabHeight, setTabHeight] = React.useState<number>(0); const [tabHeight, setTabHeight] = React.useState<number>(0)
React.useEffect(() => { React.useEffect(() => {
if (props.layoutApi) { if (props.layoutApi) {
setTabHeight(props.layoutApi.getTabHeight()); setTabHeight(props.layoutApi.getTabHeight())
}
}, [props.layoutApi])
const onTabHeightChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const value = Number(event.target.value)
if (!Number.isNaN(value)) {
setTabHeight(value)
}
} }
}, [props.layoutApi]);
const onTabHeightChange = (event: React.ChangeEvent<HTMLInputElement>) => { const onClick = () => {
const value = Number(event.target.value); props.layoutApi.setTabHeight(tabHeight)
if (!Number.isNaN(value)) {
setTabHeight(value);
} }
};
const onClick = () => { return (
props.layoutApi.setTabHeight(tabHeight); <div
}; style={{ height: '100%', backgroundColor: 'white', color: 'black' }}
>
return ( <label>
<div style={{ height: "100%", backgroundColor: "white", color: "black" }}> Tab height
<label> <input
Tab height onChange={onTabHeightChange}
<input onChange={onTabHeightChange} value={tabHeight} type="number" /> value={tabHeight}
<button onClick={onClick}>Apply</button> type="number"
</label> />
</div> <button onClick={onClick}>Apply</button>
); </label>
}; </div>
)
}

View File

@ -1,410 +1,425 @@
import * as React from "react"; import * as React from 'react'
import { import {
ReactGrid, ReactGrid,
OnReadyEvent, OnReadyEvent,
Api, Api,
IPanelProps, IPanelProps,
ClosePanelResult, ClosePanelResult,
CompositeDisposable, CompositeDisposable,
GroupChangeKind, GroupChangeKind,
IGridviewPanelProps, IGridviewPanelProps,
TabContextMenuEvent TabContextMenuEvent,
} from "splitview"; } from 'splitview'
import { CustomTab } from "./customTab"; import { CustomTab } from './customTab'
import { Editor } from "./editorPanel"; import { Editor } from './editorPanel'
import { SplitPanel } from "./splitPanel"; import { SplitPanel } from './splitPanel'
const components = { const components = {
inner_component: (props: IPanelProps) => { inner_component: (props: IPanelProps) => {
const _api = React.useRef<Api>(); const _api = React.useRef<Api>()
const [api, setApi] = React.useState<Api>(); const [api, setApi] = React.useState<Api>()
const onReady = (event: OnReadyEvent) => { const onReady = (event: OnReadyEvent) => {
_api.current = event.api; _api.current = event.api
const layout = props.api.getStateKey<object>("layout"); const layout = props.api.getStateKey<object>('layout')
if (layout) { if (layout) {
event.api.deserialize(layout); event.api.deserialize(layout)
} else { } else {
event.api.addPanelFromComponent({ event.api.addPanelFromComponent({
componentName: "test_component", componentName: 'test_component',
id: "inner-1", id: 'inner-1',
title: "inner-1", title: 'inner-1',
}); })
event.api.addPanelFromComponent({ event.api.addPanelFromComponent({
componentName: "test_component", componentName: 'test_component',
id: "inner-2", id: 'inner-2',
title: "inner-2", title: 'inner-2',
}); })
event.api.addPanelFromComponent({ event.api.addPanelFromComponent({
componentName: "test_component", componentName: 'test_component',
id: nextGuid(), id: nextGuid(),
title: "inner-3", title: 'inner-3',
position: { direction: "within", referencePanel: "inner-1" }, position: {
}); direction: 'within',
event.api.addPanelFromComponent({ referencePanel: 'inner-1',
componentName: "test_component", },
id: nextGuid(), })
title: "inner-4", event.api.addPanelFromComponent({
position: { direction: "within", referencePanel: "inner-2" }, componentName: 'test_component',
}); id: nextGuid(),
} title: 'inner-4',
setApi(event.api); position: {
}; direction: 'within',
referencePanel: 'inner-2',
React.useEffect(() => { },
const compDis = new CompositeDisposable( })
props.api.onDidDimensionsChange((event) => { }
_api.current?.layout(event.width, event.height); setApi(event.api)
}),
_api.current.onDidLayoutChange((event) => {
if (event.kind === GroupChangeKind.LAYOUT_CONFIG_UPDATED) {
props.api.setState("layout", _api.current.toJSON());
}
})
);
return () => {
compDis.dispose();
};
}, []);
React.useEffect(() => {
if (!api) {
return;
}
api.onDidLayoutChange((event) => {
// on inner grid changes
});
}, [api]);
return (
<div
style={{
boxSizing: "border-box",
// borderTop: "1px solid var(--splitview-divider-color)",
}}
>
<ReactGrid
onReady={onReady}
components={components}
tabHeight={20}
debug={true}
/>
</div>
);
},
test_component: (props: IPanelProps & { [key: string]: any }) => {
const [panelState, setPanelState] = React.useState<{
isGroupActive: boolean;
isPanelVisible: boolean;
}>({
isGroupActive: false,
isPanelVisible: false,
});
React.useEffect(() => {
const disposable = new CompositeDisposable(
props.api.onDidFocusChange((event) => {
setPanelState((_) => ({ ..._, isGroupActive: event.isFocused }));
}),
props.api.onDidChangeVisibility((x) => {
setPanelState((_) => ({ ..._, isPanelVisible: x.isVisible }));
})
);
props.api.setClosePanelHook(() => {
if (confirm("close?")) {
return Promise.resolve(ClosePanelResult.CLOSE);
} }
return Promise.resolve(ClosePanelResult.DONT_CLOSE);
});
return () => { React.useEffect(() => {
disposable.dispose(); const compDis = new CompositeDisposable(
}; props.api.onDidDimensionsChange((event) => {
}, []); _api.current?.layout(event.width, event.height)
}),
_api.current.onDidLayoutChange((event) => {
if (event.kind === GroupChangeKind.LAYOUT_CONFIG_UPDATED) {
props.api.setState('layout', _api.current.toJSON())
}
})
)
const onClick = () => { return () => {
props.api.setState("test_key", "hello"); compDis.dispose()
}; }
}, [])
const backgroundColor = React.useMemo( React.useEffect(() => {
() => if (!api) {
// "#1e1e1e", return
`rgb(${Math.floor(Math.random() * 256)},${Math.floor( }
Math.random() * 256
)},${Math.floor(Math.random() * 256)})`,
[]
);
return (
<div
style={{
backgroundColor,
height: "100%",
}}
>
<div>test component</div>
<button onClick={onClick}>set state</button>
{/* {props.api.getState()["test_key"]} */}
<div>{`G:${panelState.isGroupActive} P:${panelState.isPanelVisible}`}</div> api.onDidLayoutChange((event) => {
<div>{props.text || "-"}</div> // on inner grid changes
</div> })
); }, [api])
},
editor: Editor, return (
split_panel: SplitPanel, <div
}; style={{
boxSizing: 'border-box',
// borderTop: "1px solid var(--splitview-divider-color)",
}}
>
<ReactGrid
onReady={onReady}
components={components}
tabHeight={20}
debug={true}
/>
</div>
)
},
test_component: (props: IPanelProps & { [key: string]: any }) => {
const [panelState, setPanelState] = React.useState<{
isGroupActive: boolean
isPanelVisible: boolean
}>({
isGroupActive: false,
isPanelVisible: false,
})
React.useEffect(() => {
const disposable = new CompositeDisposable(
props.api.onDidFocusChange((event) => {
setPanelState((_) => ({
..._,
isGroupActive: event.isFocused,
}))
}),
props.api.onDidChangeVisibility((x) => {
setPanelState((_) => ({
..._,
isPanelVisible: x.isVisible,
}))
})
)
props.api.setClosePanelHook(() => {
if (confirm('close?')) {
return Promise.resolve(ClosePanelResult.CLOSE)
}
return Promise.resolve(ClosePanelResult.DONT_CLOSE)
})
return () => {
disposable.dispose()
}
}, [])
const onClick = () => {
props.api.setState('test_key', 'hello')
}
const backgroundColor = React.useMemo(
() =>
// "#1e1e1e",
`rgb(${Math.floor(Math.random() * 256)},${Math.floor(
Math.random() * 256
)},${Math.floor(Math.random() * 256)})`,
[]
)
return (
<div
style={{
backgroundColor,
height: '100%',
}}
>
<div>test component</div>
<button onClick={onClick}>set state</button>
{/* {props.api.getState()["test_key"]} */}
<div>{`G:${panelState.isGroupActive} P:${panelState.isPanelVisible}`}</div>
<div>{props.text || '-'}</div>
</div>
)
},
editor: Editor,
split_panel: SplitPanel,
}
const tabComponents = { const tabComponents = {
default: CustomTab, default: CustomTab,
}; }
const nextGuid = (() => { const nextGuid = (() => {
let counter = 0; let counter = 0
return () => "panel_" + (counter++).toString(); return () => 'panel_' + (counter++).toString()
})(); })()
export const TestGrid = (props: IGridviewPanelProps) => { export const TestGrid = (props: IGridviewPanelProps) => {
const _api = React.useRef<Api>(); const _api = React.useRef<Api>()
const [api, setApi] = React.useState<Api>(); const [api, setApi] = React.useState<Api>()
const onReady = (event: OnReadyEvent) => { const onReady = (event: OnReadyEvent) => {
_api.current = event.api; _api.current = event.api
setApi(event.api); setApi(event.api)
};
React.useEffect(() => {
if (!api) {
return;
} }
const panelReference = api.addPanelFromComponent({ React.useEffect(() => {
componentName: "test_component", if (!api) {
id: nextGuid(), return
title: "Item 1", }
params: { text: "how low?" },
});
api.addPanelFromComponent({
componentName: "test_component",
id: "item2",
title: "Item 2",
});
api.addPanelFromComponent({
componentName: "split_panel",
id: nextGuid(),
title: "Item 3 with a long title",
});
api.addPanelFromComponent({
componentName: "test_component",
id: nextGuid(),
title: "Item 3",
position: { direction: "below", referencePanel: "item2" },
suppressClosable: true,
});
// setInterval(() => { const panelReference = api.addPanelFromComponent({
// panelReference.update({ params: { text: `Tick ${Date.now()}` } }); componentName: 'test_component',
// // panelReference.remove(); id: nextGuid(),
// }, 1000); title: 'Item 1',
params: { text: 'how low?' },
})
api.addPanelFromComponent({
componentName: 'test_component',
id: 'item2',
title: 'Item 2',
})
api.addPanelFromComponent({
componentName: 'split_panel',
id: nextGuid(),
title: 'Item 3 with a long title',
})
api.addPanelFromComponent({
componentName: 'test_component',
id: nextGuid(),
title: 'Item 3',
position: { direction: 'below', referencePanel: 'item2' },
suppressClosable: true,
})
api.addDndHandle("text/plain", (ev) => { // setInterval(() => {
const { event } = ev; // panelReference.update({ params: { text: `Tick ${Date.now()}` } });
// // panelReference.remove();
// }, 1000);
return { api.addDndHandle('text/plain', (ev) => {
id: "yellow", const { event } = ev
componentName: "test_component",
};
});
api.addDndHandle("Files", (ev) => { return {
const { event } = ev; id: 'yellow',
componentName: 'test_component',
}
})
ev.event.event.preventDefault(); api.addDndHandle('Files', (ev) => {
const { event } = ev
return { ev.event.event.preventDefault()
id: Date.now().toString(),
title: event.event.dataTransfer.files[0].name,
componentName: "test_component",
};
});
}, [api]);
const onAdd = () => { return {
const id = nextGuid(); id: Date.now().toString(),
api.addPanelFromComponent({ title: event.event.dataTransfer.files[0].name,
componentName: "test_component", componentName: 'test_component',
id, }
}); })
}; }, [api])
const onAddEmpty = () => { const onAdd = () => {
api.addEmptyGroup(); const id = nextGuid()
}; api.addPanelFromComponent({
componentName: 'test_component',
React.useEffect(() => { id,
// const callback = (ev: UIEvent) => { })
// const height = window.innerHeight - 40;
// const width = window.innerWidth;
// _api.current?.layout(width, height);
// };
// window.addEventListener("resize", callback);
// callback(undefined);
props.api.setConstraints({
minimumWidth: () => _api.current.minimumWidth,
minimumHeight: () => _api.current.minimumHeight,
});
const disposable = new CompositeDisposable(
_api.current.onDidLayoutChange((event) => {
console.log(event.kind);
}),
props.api.onDidDimensionsChange((event) => {
const { width, height } = event;
_api.current.layout(width, height - 20);
})
);
return () => {
disposable.dispose();
// window.removeEventListener("resize", callback);
};
}, []);
const onConfig = () => {
const data = api.toJSON();
const stringData = JSON.stringify(data, null, 4);
console.log(stringData);
localStorage.setItem("layout", stringData);
};
const onLoad = async () => {
const didClose = await api.closeAllGroups();
if (!didClose) {
return;
} }
const data = localStorage.getItem("layout");
if (data) { const onAddEmpty = () => {
const jsonData = JSON.parse(data); api.addEmptyGroup()
api.deserialize(jsonData);
} }
};
const onClear = () => { React.useEffect(() => {
api.closeAllGroups(); // const callback = (ev: UIEvent) => {
}; // const height = window.innerHeight - 40;
// const width = window.innerWidth;
const onNextGroup = () => { // _api.current?.layout(width, height);
api.moveToNext({ includePanel: true }); // };
}; // window.addEventListener("resize", callback);
// callback(undefined);
const onPreviousGroup = () => { props.api.setConstraints({
api.moveToPrevious({ includePanel: true }); minimumWidth: () => _api.current.minimumWidth,
}; minimumHeight: () => _api.current.minimumHeight,
})
const onNextPanel = () => { const disposable = new CompositeDisposable(
api.activeGroup?.moveToNext(); _api.current.onDidLayoutChange((event) => {
}; console.log(event.kind)
}),
props.api.onDidDimensionsChange((event) => {
const { width, height } = event
_api.current.layout(width, height - 20)
})
)
const onPreviousPanel = () => { return () => {
api.activeGroup?.moveToPrevious(); disposable.dispose()
}; // window.removeEventListener("resize", callback);
}
}, [])
const dragRef = React.useRef<HTMLDivElement>(); const onConfig = () => {
const data = api.toJSON()
React.useEffect(() => { const stringData = JSON.stringify(data, null, 4)
if (!api) { console.log(stringData)
return; localStorage.setItem('layout', stringData)
} }
api.createDragTarget(
{ element: dragRef.current, content: "drag me" },
() => ({
id: "yellow",
componentName: "test_component",
})
);
}, [api]);
const onDragStart = (event: React.DragEvent) => { const onLoad = async () => {
event.dataTransfer.setData("text/plain", "Panel2"); const didClose = await api.closeAllGroups()
}; if (!didClose) {
return
}
const data = localStorage.getItem('layout')
if (data) {
const jsonData = JSON.parse(data)
api.deserialize(jsonData)
}
}
const onAddEditor = () => { const onClear = () => {
api.addPanelFromComponent({ api.closeAllGroups()
id: "editor", }
componentName: "editor",
tabComponentName: "default",
params: { layoutApi: api },
});
};
const onTabContextMenu = React.useMemo(() => (event: TabContextMenuEvent) => { const onNextGroup = () => {
console.log(event); api.moveToNext({ includePanel: true })
},[]) }
return ( const onPreviousGroup = () => {
<div api.moveToPrevious({ includePanel: true })
// className="visual-studio-theme" }
style={{ width: "100%", overflow: "hidden" }}
> const onNextPanel = () => {
<div api.activeGroup?.moveToNext()
style={{ }
height: "20px",
display: "flex", const onPreviousPanel = () => {
maxHeight: "20px", api.activeGroup?.moveToPrevious()
minHeight: "20px", }
}}
> const dragRef = React.useRef<HTMLDivElement>()
<button onClick={onAdd}>Add</button>
<button onClick={onAddEditor}>Expr</button> React.useEffect(() => {
<button onClick={onAddEmpty}>Add empty</button> if (!api) {
<button onClick={onConfig}>Save</button> return
<button onClick={onLoad}>Load</button> }
<button onClick={onClear}>Clear</button> api.createDragTarget(
<button onClick={onNextGroup}>Next</button> { element: dragRef.current, content: 'drag me' },
<button onClick={onPreviousGroup}>Before</button> () => ({
<button onClick={onNextPanel}>NextPanel</button> id: 'yellow',
<button onClick={onPreviousPanel}>BeforePanel</button> componentName: 'test_component',
})
)
}, [api])
const onDragStart = (event: React.DragEvent) => {
event.dataTransfer.setData('text/plain', 'Panel2')
}
const onAddEditor = () => {
api.addPanelFromComponent({
id: 'editor',
componentName: 'editor',
tabComponentName: 'default',
params: { layoutApi: api },
})
}
const onTabContextMenu = React.useMemo(
() => (event: TabContextMenuEvent) => {
console.log(event)
},
[]
)
return (
<div <div
draggable={true} // className="visual-studio-theme"
className="my-dragger" style={{ width: '100%', overflow: 'hidden' }}
style={{
backgroundColor: "dodgerblue",
borderRadius: "10px",
color: " white",
}}
ref={dragRef}
> >
Drag me <div
style={{
height: '20px',
display: 'flex',
maxHeight: '20px',
minHeight: '20px',
}}
>
<button onClick={onAdd}>Add</button>
<button onClick={onAddEditor}>Expr</button>
<button onClick={onAddEmpty}>Add empty</button>
<button onClick={onConfig}>Save</button>
<button onClick={onLoad}>Load</button>
<button onClick={onClear}>Clear</button>
<button onClick={onNextGroup}>Next</button>
<button onClick={onPreviousGroup}>Before</button>
<button onClick={onNextPanel}>NextPanel</button>
<button onClick={onPreviousPanel}>BeforePanel</button>
<div
draggable={true}
className="my-dragger"
style={{
backgroundColor: 'dodgerblue',
borderRadius: '10px',
color: ' white',
}}
ref={dragRef}
>
Drag me
</div>
<div
onDragStart={onDragStart}
draggable={true}
className="my-dragger"
style={{
backgroundColor: 'orange',
borderRadius: '10px',
color: ' white',
}}
>
Drag me too
</div>
</div>
<ReactGrid
// autoSizeToFitContainer={true}
onReady={onReady}
components={components}
tabComponents={tabComponents}
debug={true}
// tabHeight={30}
enableExternalDragEvents={true}
// serializedLayout={data}
onTabContextMenu={onTabContextMenu}
/>
</div> </div>
<div )
onDragStart={onDragStart} }
draggable={true}
className="my-dragger"
style={{
backgroundColor: "orange",
borderRadius: "10px",
color: " white",
}}
>
Drag me too
</div>
</div>
<ReactGrid
// autoSizeToFitContainer={true}
onReady={onReady}
components={components}
tabComponents={tabComponents}
debug={true}
// tabHeight={30}
enableExternalDragEvents={true}
// serializedLayout={data}
onTabContextMenu={onTabContextMenu}
/>
</div>
);
};

View File

@ -1,88 +1,88 @@
import * as React from "react"; import * as React from 'react'
import { import {
CompositeDisposable, CompositeDisposable,
IPanelProps, IPanelProps,
ISplitviewPanelProps, ISplitviewPanelProps,
Orientation, Orientation,
SplitviewFacade, SplitviewFacade,
SplitviewReadyEvent, SplitviewReadyEvent,
} from "splitview"; } from 'splitview'
import { SplitViewComponent } from "splitview"; import { SplitViewComponent } from 'splitview'
const components = { const components = {
default1: (props: ISplitviewPanelProps) => { default1: (props: ISplitviewPanelProps) => {
const [focused, setFocused] = React.useState<boolean>(false); const [focused, setFocused] = React.useState<boolean>(false)
React.useEffect(() => { React.useEffect(() => {
const disposable = new CompositeDisposable( const disposable = new CompositeDisposable(
props.api.onDidFocusChange((event) => { props.api.onDidFocusChange((event) => {
setFocused(event.isFocused); setFocused(event.isFocused)
}) })
); )
return () => { return () => {
disposable.dispose(); disposable.dispose()
}; }
}, []); }, [])
return ( return (
<div <div
style={{ height: "100%", width: "100%" }} style={{ height: '100%', width: '100%' }}
>{`component [isFocused: ${focused}]`}</div> >{`component [isFocused: ${focused}]`}</div>
); )
}, },
}; }
export const SplitPanel = (props: IPanelProps) => { export const SplitPanel = (props: IPanelProps) => {
const api = React.useRef<SplitviewFacade>(); const api = React.useRef<SplitviewFacade>()
React.useEffect(() => { React.useEffect(() => {
const disposable = new CompositeDisposable( const disposable = new CompositeDisposable(
props.api.onDidDimensionsChange((event) => { props.api.onDidDimensionsChange((event) => {
api.current?.layout(event.width, event.height - 20); api.current?.layout(event.width, event.height - 20)
}), }),
api.current.onChange((event) => { api.current.onChange((event) => {
props.api.setState("sview_layout", api.current.toJSON()); props.api.setState('sview_layout', api.current.toJSON())
}) })
); )
return () => { return () => {
disposable.dispose(); disposable.dispose()
}; }
}, []); }, [])
const onReady = (event: SplitviewReadyEvent) => { const onReady = (event: SplitviewReadyEvent) => {
const existingLayout = props.api.getStateKey("sview_layout"); const existingLayout = props.api.getStateKey('sview_layout')
if (existingLayout) { if (existingLayout) {
event.api.deserialize(existingLayout); event.api.deserialize(existingLayout)
} else { } else {
event.api.addFromComponent({ id: "1", component: "default1" }); event.api.addFromComponent({ id: '1', component: 'default1' })
event.api.addFromComponent({ id: "2", component: "default1" }); event.api.addFromComponent({ id: '2', component: 'default1' })
}
api.current = event.api
} }
api.current = event.api;
};
const onSave = () => { const onSave = () => {
props.api.setState("sview_layout", api.current.toJSON()); props.api.setState('sview_layout', api.current.toJSON())
}; }
return ( return (
<div <div
style={{ style={{
display: "flex", display: 'flex',
flexDirection: "column", flexDirection: 'column',
height: "100%", height: '100%',
color: "white", color: 'white',
}} }}
> >
<div style={{ height: "20px", flexShrink: 0 }}> <div style={{ height: '20px', flexShrink: 0 }}>
<button onClick={onSave}>save</button> <button onClick={onSave}>save</button>
</div> </div>
<SplitViewComponent <SplitViewComponent
components={components} components={components}
onReady={onReady} onReady={onReady}
orientation={Orientation.VERTICAL} orientation={Orientation.VERTICAL}
/> />
</div> </div>
); )
}; }

View File

@ -1,11 +1,11 @@
{ {
"extends": "../../tsconfig.build.json", "extends": "../../tsconfig.build.json",
"compilerOptions": { "compilerOptions": {
"composite": true, "composite": true,
"declaration": true, "declaration": true,
"outDir": "./dist", "outDir": "./dist",
"rootDir": "./src" "rootDir": "./src"
}, },
"include": ["src/**/*"] "include": ["src/**/*"]
} }

View File

@ -1,4 +1,4 @@
{ {
"extends": "../../tsconfig.json", "extends": "../../tsconfig.json",
"exclude": ["**/node_modules", "src/__tests__"] "exclude": ["**/node_modules", "src/__tests__"]
} }

View File

@ -1,53 +1,53 @@
var path = require("path"); var path = require('path')
module.exports = { module.exports = {
entry: path.resolve(__dirname, "src/index.tsx"), entry: path.resolve(__dirname, 'src/index.tsx'),
devtool: "source-map", devtool: 'source-map',
output: { output: {
filename: "bundle.js", filename: 'bundle.js',
path: path.resolve(__dirname, "dist"), path: path.resolve(__dirname, 'dist'),
},
mode: "development",
resolve: {
extensions: [".ts", ".js", ".tsx", "jsx"],
alias: {
react: path.resolve(__dirname, "node_modules/react"),
"react-dom": path.resolve(__dirname, "node_modules/react-dom"),
}, },
}, mode: 'development',
module: { resolve: {
rules: [ extensions: ['.ts', '.js', '.tsx', 'jsx'],
{ alias: {
test: /\.tsx?$/, react: path.resolve(__dirname, 'node_modules/react'),
loader: "ts-loader", 'react-dom': path.resolve(__dirname, 'node_modules/react-dom'),
options: { configFile: "tsconfig.build.json" }, },
}, },
{ module: {
test: /\.s[ac]ss$/i, rules: [
use: [ {
// Creates `style` nodes from JS strings test: /\.tsx?$/,
"style-loader", loader: 'ts-loader',
// Translates CSS into CommonJS options: { configFile: 'tsconfig.build.json' },
"css-loader", },
// Compiles Sass to CSS {
"sass-loader", test: /\.s[ac]ss$/i,
use: [
// Creates `style` nodes from JS strings
'style-loader',
// Translates CSS into CommonJS
'css-loader',
// Compiles Sass to CSS
'sass-loader',
],
},
{
test: /\.css$/i,
use: [
// Creates `style` nodes from JS strings
'style-loader',
// Translates CSS into CommonJS
'css-loader',
],
},
], ],
}, },
{ devServer: {
test: /\.css$/i, port: 9000,
use: [ compress: true,
// Creates `style` nodes from JS strings contentBase: path.resolve(__dirname, 'public'),
"style-loader", publicPath: '/dist',
// Translates CSS into CommonJS },
"css-loader", }
],
},
],
},
devServer: {
port: 9000,
compress: true,
contentBase: path.resolve(__dirname, "public"),
publicPath: "/dist",
},
};

View File

@ -1,7 +1,7 @@
const gulp = require("gulp"); const gulp = require('gulp')
const buildfile = require("../../scripts/build"); const buildfile = require('../../scripts/build')
const package = require("./package"); const package = require('./package')
buildfile.build({ tsconfig: "./tsconfig.build.json", package }); buildfile.build({ tsconfig: './tsconfig.build.json', package })
gulp.task("run", gulp.series(["clean", "esm", "sass"])); gulp.task('run', gulp.series(['clean', 'esm', 'sass']))

View File

@ -1,433 +1,433 @@
{ {
"name": "splitview-react", "name": "splitview-react",
"version": "1.0.0", "version": "1.0.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
"@babel/code-frame": { "@babel/code-frame": {
"version": "7.10.4", "version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz",
"integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/highlight": "^7.10.4" "@babel/highlight": "^7.10.4"
} }
}, },
"@babel/helper-validator-identifier": { "@babel/helper-validator-identifier": {
"version": "7.10.4", "version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz",
"integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==",
"dev": true "dev": true
}, },
"@babel/highlight": { "@babel/highlight": {
"version": "7.10.4", "version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz",
"integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/helper-validator-identifier": "^7.10.4", "@babel/helper-validator-identifier": "^7.10.4",
"chalk": "^2.0.0", "chalk": "^2.0.0",
"js-tokens": "^4.0.0" "js-tokens": "^4.0.0"
}, },
"dependencies": { "dependencies": {
"chalk": { "chalk": {
"version": "2.4.2", "version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"ansi-styles": "^3.2.1", "ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5", "escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0" "supports-color": "^5.3.0"
} }
} }
} }
}, },
"@babel/runtime": { "@babel/runtime": {
"version": "7.11.2", "version": "7.11.2",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz",
"integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==",
"dev": true, "dev": true,
"requires": { "requires": {
"regenerator-runtime": "^0.13.4" "regenerator-runtime": "^0.13.4"
} }
}, },
"@babel/runtime-corejs3": { "@babel/runtime-corejs3": {
"version": "7.11.2", "version": "7.11.2",
"resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.11.2.tgz", "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.11.2.tgz",
"integrity": "sha512-qh5IR+8VgFz83VBa6OkaET6uN/mJOhHONuy3m1sgF0CV6mXdPSEBdA7e1eUbVvyNtANjMbg22JUv71BaDXLY6A==", "integrity": "sha512-qh5IR+8VgFz83VBa6OkaET6uN/mJOhHONuy3m1sgF0CV6mXdPSEBdA7e1eUbVvyNtANjMbg22JUv71BaDXLY6A==",
"dev": true, "dev": true,
"requires": { "requires": {
"core-js-pure": "^3.0.0", "core-js-pure": "^3.0.0",
"regenerator-runtime": "^0.13.4" "regenerator-runtime": "^0.13.4"
} }
}, },
"@jest/types": { "@jest/types": {
"version": "26.3.0", "version": "26.3.0",
"resolved": "https://registry.npmjs.org/@jest/types/-/types-26.3.0.tgz", "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.3.0.tgz",
"integrity": "sha512-BDPG23U0qDeAvU4f99haztXwdAg3hz4El95LkAM+tHAqqhiVzRpEGHHU8EDxT/AnxOrA65YjLBwDahdJ9pTLJQ==", "integrity": "sha512-BDPG23U0qDeAvU4f99haztXwdAg3hz4El95LkAM+tHAqqhiVzRpEGHHU8EDxT/AnxOrA65YjLBwDahdJ9pTLJQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-lib-coverage": "^2.0.0",
"@types/istanbul-reports": "^3.0.0", "@types/istanbul-reports": "^3.0.0",
"@types/node": "*", "@types/node": "*",
"@types/yargs": "^15.0.0", "@types/yargs": "^15.0.0",
"chalk": "^4.0.0" "chalk": "^4.0.0"
} }
}, },
"@testing-library/dom": { "@testing-library/dom": {
"version": "7.24.2", "version": "7.24.2",
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-7.24.2.tgz", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-7.24.2.tgz",
"integrity": "sha512-ERxcZSoHx0EcN4HfshySEWmEf5Kkmgi+J7O79yCJ3xggzVlBJ2w/QjJUC+EBkJJ2OeSw48i3IoePN4w8JlVUIA==", "integrity": "sha512-ERxcZSoHx0EcN4HfshySEWmEf5Kkmgi+J7O79yCJ3xggzVlBJ2w/QjJUC+EBkJJ2OeSw48i3IoePN4w8JlVUIA==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/code-frame": "^7.10.4", "@babel/code-frame": "^7.10.4",
"@babel/runtime": "^7.10.3", "@babel/runtime": "^7.10.3",
"@types/aria-query": "^4.2.0", "@types/aria-query": "^4.2.0",
"aria-query": "^4.2.2", "aria-query": "^4.2.2",
"chalk": "^4.1.0", "chalk": "^4.1.0",
"dom-accessibility-api": "^0.5.1", "dom-accessibility-api": "^0.5.1",
"pretty-format": "^26.4.2" "pretty-format": "^26.4.2"
} }
}, },
"@testing-library/react": { "@testing-library/react": {
"version": "10.4.9", "version": "10.4.9",
"resolved": "https://registry.npmjs.org/@testing-library/react/-/react-10.4.9.tgz", "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-10.4.9.tgz",
"integrity": "sha512-pHZKkqUy0tmiD81afs8xfiuseXfU/N7rAX3iKjeZYje86t9VaB0LrxYVa+OOsvkrveX5jCK3IjajVn2MbePvqA==", "integrity": "sha512-pHZKkqUy0tmiD81afs8xfiuseXfU/N7rAX3iKjeZYje86t9VaB0LrxYVa+OOsvkrveX5jCK3IjajVn2MbePvqA==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/runtime": "^7.10.3", "@babel/runtime": "^7.10.3",
"@testing-library/dom": "^7.22.3" "@testing-library/dom": "^7.22.3"
} }
}, },
"@types/aria-query": { "@types/aria-query": {
"version": "4.2.0", "version": "4.2.0",
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.0.tgz", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.0.tgz",
"integrity": "sha512-iIgQNzCm0v7QMhhe4Jjn9uRh+I6GoPmt03CbEtwx3ao8/EfoQcmgtqH4vQ5Db/lxiIGaWDv6nwvunuh0RyX0+A==", "integrity": "sha512-iIgQNzCm0v7QMhhe4Jjn9uRh+I6GoPmt03CbEtwx3ao8/EfoQcmgtqH4vQ5Db/lxiIGaWDv6nwvunuh0RyX0+A==",
"dev": true "dev": true
}, },
"@types/color-name": { "@types/color-name": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
"integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==",
"dev": true "dev": true
}, },
"@types/istanbul-lib-coverage": { "@types/istanbul-lib-coverage": {
"version": "2.0.3", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz",
"integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==", "integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==",
"dev": true "dev": true
}, },
"@types/istanbul-lib-report": { "@types/istanbul-lib-report": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz",
"integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==",
"dev": true, "dev": true,
"requires": { "requires": {
"@types/istanbul-lib-coverage": "*" "@types/istanbul-lib-coverage": "*"
} }
}, },
"@types/istanbul-reports": { "@types/istanbul-reports": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz",
"integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==",
"dev": true, "dev": true,
"requires": { "requires": {
"@types/istanbul-lib-report": "*" "@types/istanbul-lib-report": "*"
} }
}, },
"@types/node": { "@types/node": {
"version": "14.10.2", "version": "14.10.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.10.2.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.10.2.tgz",
"integrity": "sha512-IzMhbDYCpv26pC2wboJ4MMOa9GKtjplXfcAqrMeNJpUUwpM/2ATt2w1JPUXwS6spu856TvKZL2AOmeU2rAxskw==", "integrity": "sha512-IzMhbDYCpv26pC2wboJ4MMOa9GKtjplXfcAqrMeNJpUUwpM/2ATt2w1JPUXwS6spu856TvKZL2AOmeU2rAxskw==",
"dev": true "dev": true
}, },
"@types/prop-types": { "@types/prop-types": {
"version": "15.7.3", "version": "15.7.3",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz",
"integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==", "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==",
"dev": true "dev": true
}, },
"@types/react": { "@types/react": {
"version": "16.9.49", "version": "16.9.49",
"resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.49.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.49.tgz",
"integrity": "sha512-DtLFjSj0OYAdVLBbyjhuV9CdGVHCkHn2R+xr3XkBvK2rS1Y1tkc14XSGjYgm5Fjjr90AxH9tiSzc1pCFMGO06g==", "integrity": "sha512-DtLFjSj0OYAdVLBbyjhuV9CdGVHCkHn2R+xr3XkBvK2rS1Y1tkc14XSGjYgm5Fjjr90AxH9tiSzc1pCFMGO06g==",
"dev": true, "dev": true,
"requires": { "requires": {
"@types/prop-types": "*", "@types/prop-types": "*",
"csstype": "^3.0.2" "csstype": "^3.0.2"
} }
}, },
"@types/react-dom": { "@types/react-dom": {
"version": "16.9.8", "version": "16.9.8",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.8.tgz", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.8.tgz",
"integrity": "sha512-ykkPQ+5nFknnlU6lDd947WbQ6TE3NNzbQAkInC2EKY1qeYdTKp7onFusmYZb+ityzx2YviqT6BXSu+LyWWJwcA==", "integrity": "sha512-ykkPQ+5nFknnlU6lDd947WbQ6TE3NNzbQAkInC2EKY1qeYdTKp7onFusmYZb+ityzx2YviqT6BXSu+LyWWJwcA==",
"dev": true, "dev": true,
"requires": { "requires": {
"@types/react": "*" "@types/react": "*"
} }
}, },
"@types/yargs": { "@types/yargs": {
"version": "15.0.5", "version": "15.0.5",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.5.tgz", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.5.tgz",
"integrity": "sha512-Dk/IDOPtOgubt/IaevIUbTgV7doaKkoorvOyYM2CMwuDyP89bekI7H4xLIwunNYiK9jhCkmc6pUrJk3cj2AB9w==", "integrity": "sha512-Dk/IDOPtOgubt/IaevIUbTgV7doaKkoorvOyYM2CMwuDyP89bekI7H4xLIwunNYiK9jhCkmc6pUrJk3cj2AB9w==",
"dev": true, "dev": true,
"requires": { "requires": {
"@types/yargs-parser": "*" "@types/yargs-parser": "*"
} }
}, },
"@types/yargs-parser": { "@types/yargs-parser": {
"version": "15.0.0", "version": "15.0.0",
"resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-15.0.0.tgz", "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-15.0.0.tgz",
"integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==", "integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==",
"dev": true "dev": true
}, },
"ansi-regex": { "ansi-regex": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
"dev": true "dev": true
}, },
"ansi-styles": { "ansi-styles": {
"version": "3.2.1", "version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true, "dev": true,
"requires": { "requires": {
"color-convert": "^1.9.0" "color-convert": "^1.9.0"
} }
}, },
"aria-query": { "aria-query": {
"version": "4.2.2", "version": "4.2.2",
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz",
"integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/runtime": "^7.10.2", "@babel/runtime": "^7.10.2",
"@babel/runtime-corejs3": "^7.10.2" "@babel/runtime-corejs3": "^7.10.2"
} }
}, },
"chalk": { "chalk": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
"dev": true, "dev": true,
"requires": { "requires": {
"ansi-styles": "^4.1.0", "ansi-styles": "^4.1.0",
"supports-color": "^7.1.0" "supports-color": "^7.1.0"
}, },
"dependencies": { "dependencies": {
"ansi-styles": { "ansi-styles": {
"version": "4.2.1", "version": "4.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz",
"integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==",
"dev": true, "dev": true,
"requires": { "requires": {
"@types/color-name": "^1.1.1", "@types/color-name": "^1.1.1",
"color-convert": "^2.0.1" "color-convert": "^2.0.1"
} }
}, },
"color-convert": { "color-convert": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"color-name": "~1.1.4" "color-name": "~1.1.4"
} }
}, },
"color-name": { "color-name": {
"version": "1.1.4", "version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true "dev": true
}, },
"has-flag": { "has-flag": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true "dev": true
}, },
"supports-color": { "supports-color": {
"version": "7.2.0", "version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true, "dev": true,
"requires": { "requires": {
"has-flag": "^4.0.0" "has-flag": "^4.0.0"
} }
} }
} }
}, },
"color-convert": { "color-convert": {
"version": "1.9.3", "version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"dev": true, "dev": true,
"requires": { "requires": {
"color-name": "1.1.3" "color-name": "1.1.3"
} }
}, },
"color-name": { "color-name": {
"version": "1.1.3", "version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
"dev": true "dev": true
}, },
"core-js-pure": { "core-js-pure": {
"version": "3.6.5", "version": "3.6.5",
"resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.6.5.tgz", "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.6.5.tgz",
"integrity": "sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA==", "integrity": "sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA==",
"dev": true "dev": true
}, },
"csstype": { "csstype": {
"version": "3.0.3", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.3.tgz", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.3.tgz",
"integrity": "sha512-jPl+wbWPOWJ7SXsWyqGRk3lGecbar0Cb0OvZF/r/ZU011R4YqiRehgkQ9p4eQfo9DSDLqLL3wHwfxeJiuIsNag==", "integrity": "sha512-jPl+wbWPOWJ7SXsWyqGRk3lGecbar0Cb0OvZF/r/ZU011R4YqiRehgkQ9p4eQfo9DSDLqLL3wHwfxeJiuIsNag==",
"dev": true "dev": true
}, },
"dom-accessibility-api": { "dom-accessibility-api": {
"version": "0.5.2", "version": "0.5.2",
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.2.tgz", "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.2.tgz",
"integrity": "sha512-k7hRNKAiPJXD2aBqfahSo4/01cTsKWXf+LqJgglnkN2Nz8TsxXKQBXHhKe0Ye9fEfHEZY49uSA5Sr3AqP/sWKA==", "integrity": "sha512-k7hRNKAiPJXD2aBqfahSo4/01cTsKWXf+LqJgglnkN2Nz8TsxXKQBXHhKe0Ye9fEfHEZY49uSA5Sr3AqP/sWKA==",
"dev": true "dev": true
}, },
"escape-string-regexp": { "escape-string-regexp": {
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
"dev": true "dev": true
}, },
"has-flag": { "has-flag": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
"dev": true "dev": true
}, },
"js-tokens": { "js-tokens": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"dev": true "dev": true
}, },
"loose-envify": { "loose-envify": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"dev": true, "dev": true,
"requires": { "requires": {
"js-tokens": "^3.0.0 || ^4.0.0" "js-tokens": "^3.0.0 || ^4.0.0"
} }
}, },
"object-assign": { "object-assign": {
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
"dev": true "dev": true
}, },
"pretty-format": { "pretty-format": {
"version": "26.4.2", "version": "26.4.2",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.4.2.tgz", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.4.2.tgz",
"integrity": "sha512-zK6Gd8zDsEiVydOCGLkoBoZuqv8VTiHyAbKznXe/gaph/DAeZOmit9yMfgIz5adIgAMMs5XfoYSwAX3jcCO1tA==", "integrity": "sha512-zK6Gd8zDsEiVydOCGLkoBoZuqv8VTiHyAbKznXe/gaph/DAeZOmit9yMfgIz5adIgAMMs5XfoYSwAX3jcCO1tA==",
"dev": true, "dev": true,
"requires": { "requires": {
"@jest/types": "^26.3.0", "@jest/types": "^26.3.0",
"ansi-regex": "^5.0.0", "ansi-regex": "^5.0.0",
"ansi-styles": "^4.0.0", "ansi-styles": "^4.0.0",
"react-is": "^16.12.0" "react-is": "^16.12.0"
}, },
"dependencies": { "dependencies": {
"ansi-styles": { "ansi-styles": {
"version": "4.2.1", "version": "4.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz",
"integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==",
"dev": true, "dev": true,
"requires": { "requires": {
"@types/color-name": "^1.1.1", "@types/color-name": "^1.1.1",
"color-convert": "^2.0.1" "color-convert": "^2.0.1"
} }
}, },
"color-convert": { "color-convert": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"color-name": "~1.1.4" "color-name": "~1.1.4"
} }
}, },
"color-name": { "color-name": {
"version": "1.1.4", "version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true "dev": true
} }
} }
}, },
"prop-types": { "prop-types": {
"version": "15.7.2", "version": "15.7.2",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
"integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"loose-envify": "^1.4.0", "loose-envify": "^1.4.0",
"object-assign": "^4.1.1", "object-assign": "^4.1.1",
"react-is": "^16.8.1" "react-is": "^16.8.1"
} }
}, },
"react": { "react": {
"version": "16.13.1", "version": "16.13.1",
"resolved": "https://registry.npmjs.org/react/-/react-16.13.1.tgz", "resolved": "https://registry.npmjs.org/react/-/react-16.13.1.tgz",
"integrity": "sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w==", "integrity": "sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w==",
"dev": true, "dev": true,
"requires": { "requires": {
"loose-envify": "^1.1.0", "loose-envify": "^1.1.0",
"object-assign": "^4.1.1", "object-assign": "^4.1.1",
"prop-types": "^15.6.2" "prop-types": "^15.6.2"
} }
}, },
"react-dom": { "react-dom": {
"version": "16.13.1", "version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.13.1.tgz", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.13.1.tgz",
"integrity": "sha512-81PIMmVLnCNLO/fFOQxdQkvEq/+Hfpv24XNJfpyZhTRfO0QcmQIF/PgCa1zCOj2w1hrn12MFLyaJ/G0+Mxtfag==", "integrity": "sha512-81PIMmVLnCNLO/fFOQxdQkvEq/+Hfpv24XNJfpyZhTRfO0QcmQIF/PgCa1zCOj2w1hrn12MFLyaJ/G0+Mxtfag==",
"dev": true, "dev": true,
"requires": { "requires": {
"loose-envify": "^1.1.0", "loose-envify": "^1.1.0",
"object-assign": "^4.1.1", "object-assign": "^4.1.1",
"prop-types": "^15.6.2", "prop-types": "^15.6.2",
"scheduler": "^0.19.1" "scheduler": "^0.19.1"
} }
}, },
"react-is": { "react-is": {
"version": "16.13.1", "version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"dev": true "dev": true
}, },
"regenerator-runtime": { "regenerator-runtime": {
"version": "0.13.7", "version": "0.13.7",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz",
"integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==",
"dev": true "dev": true
}, },
"scheduler": { "scheduler": {
"version": "0.19.1", "version": "0.19.1",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz",
"integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==", "integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==",
"dev": true, "dev": true,
"requires": { "requires": {
"loose-envify": "^1.1.0", "loose-envify": "^1.1.0",
"object-assign": "^4.1.1" "object-assign": "^4.1.1"
} }
}, },
"supports-color": { "supports-color": {
"version": "5.5.0", "version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true, "dev": true,
"requires": { "requires": {
"has-flag": "^3.0.0" "has-flag": "^3.0.0"
} }
} }
} }
} }

View File

@ -1,26 +1,26 @@
{ {
"name": "splitview-react", "name": "splitview-react",
"version": "1.0.0", "version": "1.0.0",
"description": "", "description": "",
"main": "dist/esm/index.js", "main": "dist/esm/index.js",
"types": "dist/esm/index.d.ts", "types": "dist/esm/index.d.ts",
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1" "test": "echo \"Error: no test specified\" && exit 1"
}, },
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"splitview": "*" "splitview": "*"
}, },
"devDependencies": { "devDependencies": {
"@testing-library/react": "^10.4.2", "@testing-library/react": "^10.4.2",
"@types/react": "^16.9.41", "@types/react": "^16.9.41",
"@types/react-dom": "^16.9.8", "@types/react-dom": "^16.9.8",
"react": "^16.13.1", "react": "^16.13.1",
"react-dom": "^16.13.1" "react-dom": "^16.13.1"
}, },
"peerDependencies": { "peerDependencies": {
"react": "^16.x", "react": "^16.x",
"react-dom": "^16.x" "react-dom": "^16.x"
} }
} }

View File

@ -1,117 +1,121 @@
import * as React from "react"; import * as React from 'react'
import { Orientation } from "splitview"; import { Orientation } from 'splitview'
import { IViewWithReactComponent } from "../splitview"; import { IViewWithReactComponent } from '../splitview'
// component view // component view
export interface IPaneComponentProps extends IViewWithReactComponent { export interface IPaneComponentProps extends IViewWithReactComponent {
setExpanded(expanded: boolean): void; setExpanded(expanded: boolean): void
orientation: Orientation; orientation: Orientation
size: number; size: number
orthogonalSize: number; orthogonalSize: number
userprops?: { [index: string]: any }; userprops?: { [index: string]: any }
} }
export interface IPaneComponentRef { export interface IPaneComponentRef {
layout: (size: number, orthogonalSize: number) => void; layout: (size: number, orthogonalSize: number) => void
} }
export type PaneComponent = React.ForwardRefRenderFunction< export type PaneComponent = React.ForwardRefRenderFunction<
IPaneComponentRef, IPaneComponentRef,
IPaneComponentProps IPaneComponentProps
>; >
export interface IPaneHeaderComponentProps extends IViewWithReactComponent { export interface IPaneHeaderComponentProps extends IViewWithReactComponent {
setExpanded(expanded: boolean): void; setExpanded(expanded: boolean): void
isExpanded: boolean; isExpanded: boolean
userprops?: { [index: string]: any }; userprops?: { [index: string]: any }
} }
export type PaneHeaderComponent = React.ForwardRefRenderFunction< export type PaneHeaderComponent = React.ForwardRefRenderFunction<
{}, {},
IPaneHeaderComponentProps IPaneHeaderComponentProps
>; >
// component view facade // component view facade
export interface IPaneRootProps { export interface IPaneRootProps {
component: PaneComponent; component: PaneComponent
props: {}; props: {}
} }
export interface IPaneHeaderRootProps { export interface IPaneHeaderRootProps {
component: PaneHeaderComponent; component: PaneHeaderComponent
props: {}; props: {}
} }
export interface IPaneRootRef extends IPaneComponentRef { export interface IPaneRootRef extends IPaneComponentRef {
updateProps: (props: Partial<IPaneComponentProps>) => void; updateProps: (props: Partial<IPaneComponentProps>) => void
} }
export interface IPaneHeaderRootRef { export interface IPaneHeaderRootRef {
updateProps: (props: Partial<IPaneHeaderComponentProps>) => void; updateProps: (props: Partial<IPaneHeaderComponentProps>) => void
} }
export const PaneRoot = React.forwardRef( export const PaneRoot = React.forwardRef(
(props: IPaneRootProps, facadeRef: React.Ref<IPaneRootRef>) => { (props: IPaneRootProps, facadeRef: React.Ref<IPaneRootRef>) => {
const ref = React.useRef<IPaneComponentRef>(); const ref = React.useRef<IPaneComponentRef>()
const [facadeProps, setFacadeProps] = React.useState<IPaneComponentProps>(); const [facadeProps, setFacadeProps] = React.useState<
IPaneComponentProps
>()
React.useImperativeHandle( React.useImperativeHandle(
facadeRef, facadeRef,
() => { () => {
return { return {
updateProps: (props) => { updateProps: (props) => {
setFacadeProps((_props) => ({ ..._props, ...props })); setFacadeProps((_props) => ({ ..._props, ...props }))
}, },
layout: (size, orthogonalSize) => { layout: (size, orthogonalSize) => {
ref.current?.layout(size, orthogonalSize); ref.current?.layout(size, orthogonalSize)
}, },
}; }
}, },
[ref] [ref]
); )
const Component = React.useMemo(() => React.forwardRef(props.component), [ const Component = React.useMemo(
props.component, () => React.forwardRef(props.component),
]); [props.component]
)
const _props = React.useMemo( const _props = React.useMemo(
() => ({ ...props.props, ...facadeProps, ref }), () => ({ ...props.props, ...facadeProps, ref }),
[props.props, facadeProps] [props.props, facadeProps]
); )
return React.createElement(Component, _props); return React.createElement(Component, _props)
} }
); )
export const PaneHeaderRoot = React.forwardRef( export const PaneHeaderRoot = React.forwardRef(
(props: IPaneHeaderRootProps, facadeRef: React.Ref<IPaneHeaderRootRef>) => { (props: IPaneHeaderRootProps, facadeRef: React.Ref<IPaneHeaderRootRef>) => {
const [facadeProps, setFacadeProps] = React.useState< const [facadeProps, setFacadeProps] = React.useState<
IPaneHeaderComponentProps IPaneHeaderComponentProps
>(); >()
React.useImperativeHandle( React.useImperativeHandle(
facadeRef, facadeRef,
() => { () => {
return { return {
updateProps: (props) => { updateProps: (props) => {
setFacadeProps((_props) => ({ ..._props, ...props })); setFacadeProps((_props) => ({ ..._props, ...props }))
}, },
}; }
}, },
[] []
); )
const Component = React.useMemo(() => React.forwardRef(props.component), [ const Component = React.useMemo(
props.component, () => React.forwardRef(props.component),
]); [props.component]
)
const _props = React.useMemo(() => ({ ...props.props, ...facadeProps }), [ const _props = React.useMemo(
props.props, () => ({ ...props.props, ...facadeProps }),
facadeProps, [props.props, facadeProps]
]); )
return React.createElement(Component, _props); return React.createElement(Component, _props)
} }
); )

View File

@ -1,64 +1,67 @@
import * as React from "react"; import * as React from 'react'
import { IViewWithReactComponent } from "../splitview"; import { IViewWithReactComponent } from '../splitview'
// component view // component view
export interface IViewComponentProps export interface IViewComponentProps
extends Omit<IViewWithReactComponent, "component"> { extends Omit<IViewWithReactComponent, 'component'> {
userprops?: { [index: string]: any }; userprops?: { [index: string]: any }
} }
export interface IViewComponentRef { export interface IViewComponentRef {
layout: (size: number, orthogonalSize: number) => void; layout: (size: number, orthogonalSize: number) => void
} }
export type ViewComponent = React.ForwardRefRenderFunction< export type ViewComponent = React.ForwardRefRenderFunction<
IViewComponentRef, IViewComponentRef,
IViewComponentProps IViewComponentProps
>; >
// component view facade // component view facade
export interface IViewRootProps { export interface IViewRootProps {
component: ViewComponent; component: ViewComponent
props: {}; props: {}
} }
export interface IViewRootRef extends IViewComponentRef { export interface IViewRootRef extends IViewComponentRef {
updateProps: (props: Partial<IViewComponentProps>) => void; updateProps: (props: Partial<IViewComponentProps>) => void
} }
export const ViewRoot = React.forwardRef( export const ViewRoot = React.forwardRef(
(props: IViewRootProps, facadeRef: React.Ref<IViewRootRef>) => { (props: IViewRootProps, facadeRef: React.Ref<IViewRootRef>) => {
const ref = React.useRef<IViewComponentRef>(); const ref = React.useRef<IViewComponentRef>()
const [facadeProps, setFacadeProps] = React.useState<IViewComponentProps>(); const [facadeProps, setFacadeProps] = React.useState<
IViewComponentProps
>()
React.useImperativeHandle( React.useImperativeHandle(
facadeRef, facadeRef,
() => { () => {
return { return {
updateProps: (props) => { updateProps: (props) => {
setFacadeProps((_props) => ({ ..._props, ...props })); setFacadeProps((_props) => ({ ..._props, ...props }))
}, },
layout: (size, orthogonalSize) => { layout: (size, orthogonalSize) => {
ref.current?.layout(size, orthogonalSize); ref.current?.layout(size, orthogonalSize)
}, },
}; }
}, },
[ref] [ref]
); )
const Component = React.useMemo(() => React.forwardRef(props.component), [ const Component = React.useMemo(
props.component, () => React.forwardRef(props.component),
]); [props.component]
)
const _props = React.useMemo( const _props = React.useMemo(
() => ({ ...props.props, ...facadeProps, ref }), () => ({ ...props.props, ...facadeProps, ref }),
[props.props, facadeProps] [props.props, facadeProps]
); )
return React.createElement(Component, _props); return React.createElement(Component, _props)
// return <Component ref={ref} {...props.props} {...facadeProps} />; // return <Component ref={ref} {...props.props} {...facadeProps} />;
} }
); )

View File

@ -1,6 +1,6 @@
export * from "./splitview"; export * from './splitview'
export * from "./paneview"; export * from './paneview'
export * from "./bridge/view"; export * from './bridge/view'
export * from "./panel/view"; export * from './panel/view'
export * from "./bridge/pane"; export * from './bridge/pane'
export * from "./panel/pane"; export * from './panel/pane'

View File

@ -1,143 +1,143 @@
import * as React from "react"; import * as React from 'react'
import * as ReactDOM from "react-dom"; import * as ReactDOM from 'react-dom'
import { Pane, IDisposable } from "splitview"; import { Pane, IDisposable } from 'splitview'
import { import {
PaneComponent, PaneComponent,
PaneRoot as PaneBodyRoot, PaneRoot as PaneBodyRoot,
IPaneRootRef, IPaneRootRef,
PaneHeaderComponent, PaneHeaderComponent,
PaneHeaderRoot, PaneHeaderRoot,
IPaneHeaderRootRef, IPaneHeaderRootRef,
} from "../bridge/pane"; } from '../bridge/pane'
import { IViewWithReactComponent } from "../splitview"; import { IViewWithReactComponent } from '../splitview'
import { IPaneWithReactComponent } from "../paneview"; import { IPaneWithReactComponent } from '../paneview'
export class PaneReact extends Pane { export class PaneReact extends Pane {
public readonly id: string; public readonly id: string
private bodyDisposable: IDisposable; private bodyDisposable: IDisposable
private headerDisposable: IDisposable; private headerDisposable: IDisposable
private bodyRef: IPaneRootRef; private bodyRef: IPaneRootRef
private headerRef: IPaneHeaderRootRef; private headerRef: IPaneHeaderRootRef
private disposable: IDisposable; private disposable: IDisposable
constructor( constructor(
private readonly view: IPaneWithReactComponent, private readonly view: IPaneWithReactComponent,
private readonly bodyComponent: PaneComponent, private readonly bodyComponent: PaneComponent,
private readonly options: { private readonly options: {
headerName: string; headerName: string
addPortal: (portal: React.ReactPortal) => IDisposable; addPortal: (portal: React.ReactPortal) => IDisposable
headerComponent?: PaneHeaderComponent; headerComponent?: PaneHeaderComponent
} }
) { ) {
super({ isExpanded: view.isExpanded }); super({ isExpanded: view.isExpanded })
this.layout = this.layout.bind(this); this.layout = this.layout.bind(this)
this.onDidChange = this.onDidChange.bind(this); this.onDidChange = this.onDidChange.bind(this)
this.setRef = this.setRef.bind(this); this.setRef = this.setRef.bind(this)
this.setHeaderRef = this.setHeaderRef.bind(this); this.setHeaderRef = this.setHeaderRef.bind(this)
this.setExpanded = this.setExpanded.bind(this); this.setExpanded = this.setExpanded.bind(this)
this.id = view.id; this.id = view.id
this.minimumSize = view.minimumSize; this.minimumSize = view.minimumSize
this.maximumSize = view.maximumSize; this.maximumSize = view.maximumSize
this.render(); this.render()
}
public renderBody(element: HTMLElement) {
if (this.bodyDisposable) {
this.bodyDisposable.dispose();
this.bodyDisposable = undefined;
} }
const bodyPortal = ReactDOM.createPortal( public renderBody(element: HTMLElement) {
<PaneBodyRoot if (this.bodyDisposable) {
ref={this.setRef} this.bodyDisposable.dispose()
component={this.bodyComponent} this.bodyDisposable = undefined
props={{ }
minimumSize: this.minimumSize,
maximumSize: this.maximumSize,
snapSize: this.view.snapSize,
userprops: this.view.componentProps,
id: this.id,
}}
/>,
element
);
this.bodyDisposable = this.options.addPortal(bodyPortal);
}
public renderHeader(element: HTMLElement) { const bodyPortal = ReactDOM.createPortal(
if (this.headerDisposable) { <PaneBodyRoot
this.headerDisposable.dispose(); ref={this.setRef}
this.disposable?.dispose(); component={this.bodyComponent}
this.headerDisposable = undefined; props={{
minimumSize: this.minimumSize,
maximumSize: this.maximumSize,
snapSize: this.view.snapSize,
userprops: this.view.componentProps,
id: this.id,
}}
/>,
element
)
this.bodyDisposable = this.options.addPortal(bodyPortal)
} }
if (this.options.headerComponent) { public renderHeader(element: HTMLElement) {
this.disposable = this.onDidChangeExpansionState((isExpanded) => { if (this.headerDisposable) {
this.headerRef?.updateProps({ isExpanded }); this.headerDisposable.dispose()
}); this.disposable?.dispose()
this.headerDisposable = undefined
}
const headerPortal = ReactDOM.createPortal( if (this.options.headerComponent) {
<PaneHeaderRoot this.disposable = this.onDidChangeExpansionState((isExpanded) => {
ref={this.setHeaderRef} this.headerRef?.updateProps({ isExpanded })
component={this.options.headerComponent} })
props={{
const headerPortal = ReactDOM.createPortal(
<PaneHeaderRoot
ref={this.setHeaderRef}
component={this.options.headerComponent}
props={{
minimumSize: this.minimumSize,
maximumSize: this.maximumSize,
snapSize: this.view.snapSize,
userprops: this.view.headerProps,
id: this.id,
}}
/>,
element
)
this.headerDisposable = this.options.addPortal(headerPortal)
} else {
element.textContent = this.options.headerName
element.onclick = () => {
this.setExpanded(!this.isExpanded())
}
}
}
public update(view: IViewWithReactComponent) {
this.minimumSize = view.minimumSize
this.maximumSize = view.maximumSize
this.render()
this.bodyRef?.updateProps({
minimumSize: this.minimumSize, minimumSize: this.minimumSize,
maximumSize: this.maximumSize, maximumSize: this.maximumSize,
snapSize: this.view.snapSize, })
userprops: this.view.headerProps,
id: this.id,
}}
/>,
element
);
this.headerDisposable = this.options.addPortal(headerPortal);
} else {
element.textContent = this.options.headerName;
element.onclick = () => {
this.setExpanded(!this.isExpanded());
};
} }
}
public update(view: IViewWithReactComponent) { public layout(size: number, orthogonalSize: number) {
this.minimumSize = view.minimumSize; super.layout(size, orthogonalSize)
this.maximumSize = view.maximumSize; this.orthogonalSize = orthogonalSize
this.bodyRef?.layout(size, orthogonalSize)
this.bodyRef?.updateProps({ size, orthogonalSize })
}
this.render(); private setRef(ref: IPaneRootRef) {
this.bodyRef = ref
}
this.bodyRef?.updateProps({ private setHeaderRef(ref: IPaneRootRef) {
minimumSize: this.minimumSize, this.headerRef = ref
maximumSize: this.maximumSize, this.headerRef?.updateProps({
}); isExpanded: this.isExpanded(),
} setExpanded: this.setExpanded,
})
}
public layout(size: number, orthogonalSize: number) { public dispose() {
super.layout(size, orthogonalSize); this.bodyDisposable?.dispose()
this.orthogonalSize = orthogonalSize; this.headerDisposable?.dispose()
this.bodyRef?.layout(size, orthogonalSize); this.disposable?.dispose()
this.bodyRef?.updateProps({ size, orthogonalSize }); }
}
private setRef(ref: IPaneRootRef) {
this.bodyRef = ref;
}
private setHeaderRef(ref: IPaneRootRef) {
this.headerRef = ref;
this.headerRef?.updateProps({
isExpanded: this.isExpanded(),
setExpanded: this.setExpanded,
});
}
public dispose() {
this.bodyDisposable?.dispose();
this.headerDisposable?.dispose();
this.disposable?.dispose();
}
} }

View File

@ -1,108 +1,108 @@
import * as React from "react"; import * as React from 'react'
import * as ReactDOM from "react-dom"; import * as ReactDOM from 'react-dom'
import { IView, Emitter } from "splitview"; import { IView, Emitter } from 'splitview'
import { IViewRootRef, ViewComponent, ViewRoot } from "../bridge/view"; import { IViewRootRef, ViewComponent, ViewRoot } from '../bridge/view'
import { IViewWithReactComponent } from "../splitview"; import { IViewWithReactComponent } from '../splitview'
export class ReactRenderView implements IView { export class ReactRenderView implements IView {
private ref: IViewRootRef; private ref: IViewRootRef
private disposable: { dispose: () => void }; private disposable: { dispose: () => void }
public readonly id: string; public readonly id: string
private readonly component: ViewComponent; private readonly component: ViewComponent
public readonly props: {}; public readonly props: {}
public element: HTMLElement; public element: HTMLElement
public minimumSize: number; public minimumSize: number
public maximumSize: number; public maximumSize: number
public snapSize: number; public snapSize: number
public size: number; public size: number
private readonly _onDidChange = new Emitter<number | undefined>(); private readonly _onDidChange = new Emitter<number | undefined>()
public readonly onDidChange = this._onDidChange.event; public readonly onDidChange = this._onDidChange.event
private _rendered = false; private _rendered = false
private _size: number; private _size: number
private _orthogonalSize: number; private _orthogonalSize: number
constructor( constructor(
view: IViewWithReactComponent, view: IViewWithReactComponent,
private readonly addPortal: ( private readonly addPortal: (
portal: React.ReactPortal portal: React.ReactPortal
) => { dispose: () => void } ) => { dispose: () => void }
) { ) {
this.layout = this.layout.bind(this); this.layout = this.layout.bind(this)
this.onDidChange = this.onDidChange.bind(this); this.onDidChange = this.onDidChange.bind(this)
this.setRef = this.setRef.bind(this); this.setRef = this.setRef.bind(this)
this.id = view.id; this.id = view.id
this.component = view.component; this.component = view.component
this.props = view.props; this.props = view.props
this.minimumSize = view.minimumSize; this.minimumSize = view.minimumSize
this.maximumSize = view.maximumSize; this.maximumSize = view.maximumSize
this.snapSize = view.snapSize; this.snapSize = view.snapSize
this.element = document.createElement("div"); this.element = document.createElement('div')
this.element.id = "react-attachable-view"; this.element.id = 'react-attachable-view'
}
public update(view: IView) {
this.minimumSize = view.minimumSize;
this.maximumSize = view.maximumSize;
this.snapSize = view.snapSize;
this.ref?.updateProps({
minimumSize: this.minimumSize,
maximumSize: this.maximumSize,
snapSize: this.snapSize,
});
}
public layout(size: number, orthogonalSize: number) {
if (!this._rendered) {
this.attachReactComponent();
this._rendered = true;
} }
this._size = size; public update(view: IView) {
this._orthogonalSize = orthogonalSize; this.minimumSize = view.minimumSize
this.ref?.layout(size, orthogonalSize); this.maximumSize = view.maximumSize
} this.snapSize = view.snapSize
private attachReactComponent() { this.ref?.updateProps({
const portal = this.createReactElement(); minimumSize: this.minimumSize,
if (this.disposable) { maximumSize: this.maximumSize,
this.disposable.dispose(); snapSize: this.snapSize,
this.disposable = undefined; })
} }
this.disposable = this.addPortal(portal);
}
private createReactElement() { public layout(size: number, orthogonalSize: number) {
return ReactDOM.createPortal( if (!this._rendered) {
<ViewRoot this.attachReactComponent()
ref={this.setRef} this._rendered = true
component={this.component} }
props={{
minimumSize: this.minimumSize,
maximumSize: this.maximumSize,
snapSize: this.snapSize,
userprops: this.props,
id: this.id,
}}
/>,
this.element
);
}
private setRef(ref: IViewRootRef) { this._size = size
this.ref = ref; this._orthogonalSize = orthogonalSize
this.ref?.layout(this._size, this._orthogonalSize); this.ref?.layout(size, orthogonalSize)
} }
public dispose() { private attachReactComponent() {
this.disposable?.dispose(); const portal = this.createReactElement()
} if (this.disposable) {
this.disposable.dispose()
this.disposable = undefined
}
this.disposable = this.addPortal(portal)
}
private createReactElement() {
return ReactDOM.createPortal(
<ViewRoot
ref={this.setRef}
component={this.component}
props={{
minimumSize: this.minimumSize,
maximumSize: this.maximumSize,
snapSize: this.snapSize,
userprops: this.props,
id: this.id,
}}
/>,
this.element
)
}
private setRef(ref: IViewRootRef) {
this.ref = ref
this.ref?.layout(this._size, this._orthogonalSize)
}
public dispose() {
this.disposable?.dispose()
}
} }

View File

@ -1,196 +1,215 @@
import * as React from "react"; import * as React from 'react'
import { Orientation, IBaseView, PaneView } from "splitview"; import { Orientation, IBaseView, PaneView } from 'splitview'
import { PaneReact } from "./panel/pane"; import { PaneReact } from './panel/pane'
import { PaneComponent, PaneHeaderComponent } from "./bridge/pane"; import { PaneComponent, PaneHeaderComponent } from './bridge/pane'
export interface IPaneWithReactComponent extends IBaseView { export interface IPaneWithReactComponent extends IBaseView {
id: string; id: string
headerId: string; headerId: string
component: PaneComponent; component: PaneComponent
headerComponent: PaneHeaderComponent; headerComponent: PaneHeaderComponent
isExpanded: boolean; isExpanded: boolean
componentProps: {}; componentProps: {}
headerProps: {}; headerProps: {}
} }
export interface IPaneViewReactProps { export interface IPaneViewReactProps {
orientation: Orientation; orientation: Orientation
onReady?: (event: PaneViewReadyEvent) => void; onReady?: (event: PaneViewReadyEvent) => void
components?: { [index: string]: PaneComponent }; components?: { [index: string]: PaneComponent }
headerComponents?: { [index: string]: PaneHeaderComponent }; headerComponents?: { [index: string]: PaneHeaderComponent }
size: number; size: number
orthogonalSize: number; orthogonalSize: number
initialLayout?: PaneViewSerializedConfig; initialLayout?: PaneViewSerializedConfig
} }
export interface PaneViewReadyEvent { export interface PaneViewReadyEvent {
api: PaneviewApi; api: PaneviewApi
} }
export interface PaneViewSerializedConfig { export interface PaneViewSerializedConfig {
views: Array< views: Array<
Omit<IPaneWithReactComponent, "component" | "headerComponent"> & { Omit<IPaneWithReactComponent, 'component' | 'headerComponent'> & {
size?: number; size?: number
} }
>; >
} }
export interface PaneviewApi { export interface PaneviewApi {
add: ( add: (
options: Omit<IPaneWithReactComponent, "component" | "headerComponent"> & { options: Omit<
size?: number; IPaneWithReactComponent,
index?: number; 'component' | 'headerComponent'
} > & {
) => void; size?: number
moveView: (from: number, to: number) => void; index?: number
toJSON: () => {}; }
) => void
moveView: (from: number, to: number) => void
toJSON: () => {}
} }
export interface IPaneViewComponentRef { export interface IPaneViewComponentRef {
layout: (size: number, orthogonalSize: number) => void; layout: (size: number, orthogonalSize: number) => void
} }
export const PaneViewComponent = React.forwardRef( export const PaneViewComponent = React.forwardRef(
(props: IPaneViewReactProps, _ref: React.Ref<IPaneViewComponentRef>) => { (props: IPaneViewReactProps, _ref: React.Ref<IPaneViewComponentRef>) => {
const ref = React.useRef<HTMLDivElement>(); const ref = React.useRef<HTMLDivElement>()
const dimension = React.useRef<{ size: number; orthogonalSize: number }>(); const dimension = React.useRef<{
const paneview = React.useRef<PaneView>(); size: number
const [portals, setPortals] = React.useState<React.ReactPortal[]>([]); orthogonalSize: number
}>()
const paneview = React.useRef<PaneView>()
const [portals, setPortals] = React.useState<React.ReactPortal[]>([])
const createView = React.useCallback((_view: IPaneWithReactComponent) => { const createView = React.useCallback(
return new PaneReact(_view, _view.component, { (_view: IPaneWithReactComponent) => {
headerName: "header", return new PaneReact(_view, _view.component, {
headerComponent: _view.headerComponent, headerName: 'header',
addPortal: (portal) => { headerComponent: _view.headerComponent,
setPortals((portals) => [...portals, portal]); addPortal: (portal) => {
return { setPortals((portals) => [...portals, portal])
dispose: () => { return {
setPortals((portals) => portals.filter((_) => _ !== portal)); dispose: () => {
setPortals((portals) =>
portals.filter((_) => _ !== portal)
)
},
}
},
})
}, },
}; []
}, )
});
}, []);
const hydrate = React.useCallback(() => { const hydrate = React.useCallback(() => {
if (!props.initialLayout || !paneview.current) { if (!props.initialLayout || !paneview.current) {
return; return
} }
const serializedConfig = props.initialLayout; const serializedConfig = props.initialLayout
serializedConfig.views.forEach((view) => { serializedConfig.views.forEach((view) => {
const component = props.components[view.id]; const component = props.components[view.id]
const headerComponent = props.headerComponents[view.headerId]; const headerComponent = props.headerComponents[view.headerId]
paneview.current.addPane( paneview.current.addPane(
createView({ ...view, component, headerComponent }), createView({ ...view, component, headerComponent }),
view.size view.size
); )
}); })
paneview.current.layout(props.size, props.orthogonalSize); paneview.current.layout(props.size, props.orthogonalSize)
}, [props.initialLayout]); }, [props.initialLayout])
React.useEffect(() => { React.useEffect(() => {
if (paneview.current && dimension?.current) { if (paneview.current && dimension?.current) {
paneview.current?.layout( paneview.current?.layout(
dimension.current.size, dimension.current.size,
dimension.current.orthogonalSize dimension.current.orthogonalSize
); )
dimension.current = undefined; dimension.current = undefined
} }
}, [paneview.current]); }, [paneview.current])
// if you put this in a hook it's laggy // if you put this in a hook it's laggy
// paneview.current?.layout(props.size, props.orthogonalSize); // paneview.current?.layout(props.size, props.orthogonalSize);
React.useImperativeHandle( React.useImperativeHandle(
_ref, _ref,
() => ({ () => ({
layout: (size, orthogonalSize) => { layout: (size, orthogonalSize) => {
if (!paneview.current) { if (!paneview.current) {
// handle the case when layout is called and paneview doesn't exist yet // handle the case when layout is called and paneview doesn't exist yet
// we cache the values and use them at the first opportunity // we cache the values and use them at the first opportunity
dimension.current = { size, orthogonalSize }; dimension.current = { size, orthogonalSize }
} }
paneview.current?.layout(size, orthogonalSize); paneview.current?.layout(size, orthogonalSize)
}, },
}), }),
[paneview] [paneview]
); )
React.useEffect(() => { React.useEffect(() => {
paneview.current = new PaneView(ref.current, { paneview.current = new PaneView(ref.current, {
orientation: props.orientation, orientation: props.orientation,
}); })
hydrate(); hydrate()
if (props.onReady) { if (props.onReady) {
props.onReady({ props.onReady({
api: { api: {
add: ( add: (
options: Omit< options: Omit<
IPaneWithReactComponent, IPaneWithReactComponent,
"component" | "headerComponent" 'component' | 'headerComponent'
> & { > & {
props?: {}; props?: {}
size?: number; size?: number
index?: number; index?: number
} }
) => { ) => {
const component = props.components[options.id]; const component = props.components[options.id]
const headerComponent = props.headerComponents[options.headerId]; const headerComponent =
paneview.current.addPane( props.headerComponents[options.headerId]
createView({ ...options, component, headerComponent }), paneview.current.addPane(
options.size, createView({
options.index ...options,
); component,
paneview.current.layout(props.size, props.orthogonalSize); headerComponent,
}, }),
moveView: (from: number, to: number) => { options.size,
paneview.current.moveView(from, to); options.index
}, )
toJSON: () => { paneview.current.layout(
return { props.size,
// views: (paneview.current.getViews() as PaneReact[]).map((v) => props.orthogonalSize
// Object.entries({ )
// size: v.size, },
// id: v.id, moveView: (from: number, to: number) => {
// snapSize: v.snapSize, paneview.current.moveView(from, to)
// minimumSize: v.minimumSize, },
// maximumSize: v.maximumSize, toJSON: () => {
// props: v.props, return {
// }).reduce( // views: (paneview.current.getViews() as PaneReact[]).map((v) =>
// (x, y) => // Object.entries({
// y[1] !== undefined || x !== null // size: v.size,
// ? { ...x, [y[0]]: y[1] } // id: v.id,
// : x, // snapSize: v.snapSize,
// {} // minimumSize: v.minimumSize,
// ) // maximumSize: v.maximumSize,
// ), // props: v.props,
}; // }).reduce(
}, // (x, y) =>
}, // y[1] !== undefined || x !== null
}); // ? { ...x, [y[0]]: y[1] }
} // : x,
// {}
// )
// ),
}
},
},
})
}
paneview.current.layout(props.size, props.orthogonalSize); paneview.current.layout(props.size, props.orthogonalSize)
return () => { return () => {
paneview.current?.dispose(); paneview.current?.dispose()
paneview.current = undefined; paneview.current = undefined
}; }
}, []); }, [])
React.useEffect(() => { React.useEffect(() => {
paneview.current?.setOrientation(props.orientation); paneview.current?.setOrientation(props.orientation)
}, [props.orientation]); }, [props.orientation])
return ( return (
<div ref={ref} className="split-view-react-wrapper"> <div ref={ref} className="split-view-react-wrapper">
{portals} {portals}
</div> </div>
); )
} }
); )

View File

@ -1,160 +1,168 @@
import * as React from "react"; import * as React from 'react'
import { SplitView, Orientation, IBaseView } from "splitview"; import { SplitView, Orientation, IBaseView } from 'splitview'
import { ReactRenderView } from "./panel/view"; import { ReactRenderView } from './panel/view'
import { ViewComponent } from "./bridge/view"; import { ViewComponent } from './bridge/view'
export interface IViewWithReactComponent extends IBaseView { export interface IViewWithReactComponent extends IBaseView {
id: string; id: string
props?: {}; props?: {}
component: ViewComponent; component: ViewComponent
} }
export interface OnReadyEvent { export interface OnReadyEvent {
api: SplitviewApi; api: SplitviewApi
} }
export interface SerializedConfig { export interface SerializedConfig {
views: Array<Omit<IViewWithReactComponent, "component"> & { size?: number }>; views: Array<Omit<IViewWithReactComponent, 'component'> & { size?: number }>
} }
export interface SplitviewApi { export interface SplitviewApi {
add: ( add: (
options: Omit<IViewWithReactComponent, "component"> & { options: Omit<IViewWithReactComponent, 'component'> & {
size?: number; size?: number
index?: number; index?: number
} }
) => void; ) => void
moveView: (from: number, to: number) => void; moveView: (from: number, to: number) => void
toJSON: () => {}; toJSON: () => {}
} }
export interface ISplitViewReactProps { export interface ISplitViewReactProps {
orientation: Orientation; orientation: Orientation
size: number; size: number
orthogonalSize: number; orthogonalSize: number
onReady?: (event: OnReadyEvent) => void; onReady?: (event: OnReadyEvent) => void
components?: { [index: string]: ViewComponent }; components?: { [index: string]: ViewComponent }
initialLayout?: SerializedConfig; initialLayout?: SerializedConfig
} }
export interface ISplitViewComponentRef { export interface ISplitViewComponentRef {
layout: (size: number, orthogonalSize: number) => void; layout: (size: number, orthogonalSize: number) => void
} }
export const SplitViewComponent = React.forwardRef( export const SplitViewComponent = React.forwardRef(
(props: ISplitViewReactProps, ref: React.Ref<ISplitViewComponentRef>) => { (props: ISplitViewReactProps, ref: React.Ref<ISplitViewComponentRef>) => {
const containerRef = React.useRef<HTMLDivElement>(); const containerRef = React.useRef<HTMLDivElement>()
const splitview = React.useRef<SplitView>(); const splitview = React.useRef<SplitView>()
const [portals, setPortals] = React.useState<React.ReactPortal[]>([]); const [portals, setPortals] = React.useState<React.ReactPortal[]>([])
const hydrate = React.useCallback(() => { const hydrate = React.useCallback(() => {
if (!props.initialLayout || !splitview.current) { if (!props.initialLayout || !splitview.current) {
return; return
} }
const serializedConfig = props.initialLayout; const serializedConfig = props.initialLayout
serializedConfig.views.forEach((view) => { serializedConfig.views.forEach((view) => {
const component = props.components[view.id]; const component = props.components[view.id]
splitview.current.addView( splitview.current.addView(
createView({ ...view, component }), createView({ ...view, component }),
view.size view.size
); )
}); })
splitview.current.layout(props.size, props.orthogonalSize); splitview.current.layout(props.size, props.orthogonalSize)
}, [props.initialLayout]); }, [props.initialLayout])
React.useEffect(() => { React.useEffect(() => {
splitview.current?.setOrientation(props.orientation); splitview.current?.setOrientation(props.orientation)
splitview.current?.layout(props.size, props.orthogonalSize); splitview.current?.layout(props.size, props.orthogonalSize)
}, [props.orientation]); }, [props.orientation])
React.useImperativeHandle( React.useImperativeHandle(
ref, ref,
() => ({ () => ({
layout: (size, orthogonalSize) => { layout: (size, orthogonalSize) => {
splitview.current?.layout(size, orthogonalSize); splitview.current?.layout(size, orthogonalSize)
}, },
}), }),
[splitview] [splitview]
); )
React.useEffect(() => { React.useEffect(() => {
splitview.current = new SplitView(containerRef.current, { splitview.current = new SplitView(containerRef.current, {
orientation: props.orientation, orientation: props.orientation,
}); })
hydrate(); hydrate()
if (props.onReady) { if (props.onReady) {
props.onReady({ props.onReady({
api: { api: {
add: ( add: (
options: Omit<IViewWithReactComponent, "component"> & { options: Omit<
props?: {}; IViewWithReactComponent,
size?: number; 'component'
index?: number; > & {
} props?: {}
) => { size?: number
const component = props.components[options.id]; index?: number
splitview.current.addView( }
createView({ ...options, component }), ) => {
options.size, const component = props.components[options.id]
options.index splitview.current.addView(
); createView({ ...options, component }),
splitview.current.layout(props.size, props.orthogonalSize); options.size,
}, options.index
moveView: (from: number, to: number) => { )
splitview.current.moveView(from, to); splitview.current.layout(
}, props.size,
toJSON: () => { props.orthogonalSize
return { )
views: (splitview.current.getViews() as ReactRenderView[]).map( },
(v) => moveView: (from: number, to: number) => {
Object.entries({ splitview.current.moveView(from, to)
size: v.size, },
id: v.id, toJSON: () => {
snapSize: v.snapSize, return {
minimumSize: v.minimumSize, views: (splitview.current.getViews() as ReactRenderView[]).map(
maximumSize: v.maximumSize, (v) =>
props: v.props, Object.entries({
}).reduce( size: v.size,
(x, y) => id: v.id,
y[1] !== undefined || x !== null snapSize: v.snapSize,
? { ...x, [y[0]]: y[1] } minimumSize: v.minimumSize,
: x, maximumSize: v.maximumSize,
{} props: v.props,
) }).reduce(
), (x, y) =>
}; y[1] !== undefined || x !== null
}, ? { ...x, [y[0]]: y[1] }
}, : x,
}); {}
} )
),
}
},
},
})
}
splitview.current.layout(props.size, props.orthogonalSize); splitview.current.layout(props.size, props.orthogonalSize)
return () => { return () => {
splitview.current.dispose(); splitview.current.dispose()
}; }
}, []); }, [])
const createView = React.useCallback( const createView = React.useCallback(
(view: IViewWithReactComponent) => (view: IViewWithReactComponent) =>
new ReactRenderView(view, (portal) => { new ReactRenderView(view, (portal) => {
setPortals((portals) => [...portals, portal]); setPortals((portals) => [...portals, portal])
return { return {
dispose: () => dispose: () =>
void setPortals((portals) => portals.filter((_) => _ !== portal)), void setPortals((portals) =>
}; portals.filter((_) => _ !== portal)
}), ),
[] }
); }),
[]
)
return ( return (
<div ref={containerRef} className="split-view-container-react"> <div ref={containerRef} className="split-view-container-react">
{portals} {portals}
</div> </div>
); )
} }
); )

View File

@ -1,12 +1,12 @@
{ {
"extends": "../../tsconfig.build.json", "extends": "../../tsconfig.build.json",
"compilerOptions": { "compilerOptions": {
"composite": true, "composite": true,
"declaration": true, "declaration": true,
"outDir": "./dist", "outDir": "./dist",
"rootDir": "./src" "rootDir": "./src"
}, },
"include": ["src/**/*"], "include": ["src/**/*"],
"exclude": ["**/node_modules"] "exclude": ["**/node_modules"]
} }

View File

@ -1,3 +1,3 @@
{ {
"extends": "../../tsconfig.json" "extends": "../../tsconfig.json"
} }

View File

@ -1,7 +1,7 @@
const gulp = require("gulp"); const gulp = require('gulp')
const buildfile = require("../../scripts/build"); const buildfile = require('../../scripts/build')
const package = require("./package"); const package = require('./package')
buildfile.build({ tsconfig: "./tsconfig.build.json", package }); buildfile.build({ tsconfig: './tsconfig.build.json', package })
gulp.task("run", gulp.series(["esm", "sass"])); gulp.task('run', gulp.series(['esm', 'sass']))

View File

@ -1,368 +1,368 @@
{ {
"name": "splitview", "name": "splitview",
"version": "1.0.0", "version": "1.0.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
"@types/prop-types": { "@types/prop-types": {
"version": "15.7.3", "version": "15.7.3",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz",
"integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==" "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw=="
}, },
"@types/react": { "@types/react": {
"version": "16.9.43", "version": "16.9.43",
"resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.43.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.43.tgz",
"integrity": "sha512-PxshAFcnJqIWYpJbLPriClH53Z2WlJcVZE+NP2etUtWQs2s7yIMj3/LDKZT/5CHJ/F62iyjVCDu2H3jHEXIxSg==", "integrity": "sha512-PxshAFcnJqIWYpJbLPriClH53Z2WlJcVZE+NP2etUtWQs2s7yIMj3/LDKZT/5CHJ/F62iyjVCDu2H3jHEXIxSg==",
"requires": { "requires": {
"@types/prop-types": "*", "@types/prop-types": "*",
"csstype": "^2.2.0" "csstype": "^2.2.0"
} }
}, },
"@types/react-dom": { "@types/react-dom": {
"version": "16.9.8", "version": "16.9.8",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.8.tgz", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.8.tgz",
"integrity": "sha512-ykkPQ+5nFknnlU6lDd947WbQ6TE3NNzbQAkInC2EKY1qeYdTKp7onFusmYZb+ityzx2YviqT6BXSu+LyWWJwcA==", "integrity": "sha512-ykkPQ+5nFknnlU6lDd947WbQ6TE3NNzbQAkInC2EKY1qeYdTKp7onFusmYZb+ityzx2YviqT6BXSu+LyWWJwcA==",
"requires": { "requires": {
"@types/react": "*" "@types/react": "*"
} }
}, },
"at-least-node": { "at-least-node": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
"integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==",
"dev": true "dev": true
}, },
"balanced-match": { "balanced-match": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
"dev": true "dev": true
}, },
"brace-expansion": { "brace-expansion": {
"version": "1.1.11", "version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true, "dev": true,
"requires": { "requires": {
"balanced-match": "^1.0.0", "balanced-match": "^1.0.0",
"concat-map": "0.0.1" "concat-map": "0.0.1"
} }
}, },
"concat-map": { "concat-map": {
"version": "0.0.1", "version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"dev": true "dev": true
}, },
"csstype": { "csstype": {
"version": "2.6.11", "version": "2.6.11",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.11.tgz", "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.11.tgz",
"integrity": "sha512-l8YyEC9NBkSm783PFTvh0FmJy7s5pFKrDp49ZL7zBGX3fWkO+N4EEyan1qqp8cwPLDcD0OSdyY6hAMoxp34JFw==" "integrity": "sha512-l8YyEC9NBkSm783PFTvh0FmJy7s5pFKrDp49ZL7zBGX3fWkO+N4EEyan1qqp8cwPLDcD0OSdyY6hAMoxp34JFw=="
}, },
"fs-extra": { "fs-extra": {
"version": "9.0.1", "version": "9.0.1",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz",
"integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==", "integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"at-least-node": "^1.0.0", "at-least-node": "^1.0.0",
"graceful-fs": "^4.2.0", "graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1", "jsonfile": "^6.0.1",
"universalify": "^1.0.0" "universalify": "^1.0.0"
} }
}, },
"fs.realpath": { "fs.realpath": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
"dev": true "dev": true
}, },
"glob": { "glob": {
"version": "7.1.6", "version": "7.1.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
"dev": true, "dev": true,
"requires": { "requires": {
"fs.realpath": "^1.0.0", "fs.realpath": "^1.0.0",
"inflight": "^1.0.4", "inflight": "^1.0.4",
"inherits": "2", "inherits": "2",
"minimatch": "^3.0.4", "minimatch": "^3.0.4",
"once": "^1.3.0", "once": "^1.3.0",
"path-is-absolute": "^1.0.0" "path-is-absolute": "^1.0.0"
} }
}, },
"graceful-fs": { "graceful-fs": {
"version": "4.2.4", "version": "4.2.4",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
"integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==",
"dev": true "dev": true
}, },
"handlebars": { "handlebars": {
"version": "4.7.6", "version": "4.7.6",
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.6.tgz", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.6.tgz",
"integrity": "sha512-1f2BACcBfiwAfStCKZNrUCgqNZkGsAT7UM3kkYtXuLo0KnaVfjKOyf7PRzB6++aK9STyT1Pd2ZCPe3EGOXleXA==", "integrity": "sha512-1f2BACcBfiwAfStCKZNrUCgqNZkGsAT7UM3kkYtXuLo0KnaVfjKOyf7PRzB6++aK9STyT1Pd2ZCPe3EGOXleXA==",
"dev": true, "dev": true,
"requires": { "requires": {
"minimist": "^1.2.5", "minimist": "^1.2.5",
"neo-async": "^2.6.0", "neo-async": "^2.6.0",
"source-map": "^0.6.1", "source-map": "^0.6.1",
"uglify-js": "^3.1.4", "uglify-js": "^3.1.4",
"wordwrap": "^1.0.0" "wordwrap": "^1.0.0"
} }
}, },
"highlight.js": { "highlight.js": {
"version": "10.1.2", "version": "10.1.2",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.1.2.tgz", "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.1.2.tgz",
"integrity": "sha512-Q39v/Mn5mfBlMff9r+zzA+gWxRsCRKwEMvYTiisLr/XUiFI/4puWt0Ojdko3R3JCNWGdOWaA5g/Yxqa23kC5AA==", "integrity": "sha512-Q39v/Mn5mfBlMff9r+zzA+gWxRsCRKwEMvYTiisLr/XUiFI/4puWt0Ojdko3R3JCNWGdOWaA5g/Yxqa23kC5AA==",
"dev": true "dev": true
}, },
"inflight": { "inflight": {
"version": "1.0.6", "version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"dev": true, "dev": true,
"requires": { "requires": {
"once": "^1.3.0", "once": "^1.3.0",
"wrappy": "1" "wrappy": "1"
} }
}, },
"inherits": { "inherits": {
"version": "2.0.4", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true "dev": true
}, },
"interpret": { "interpret": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz",
"integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==",
"dev": true "dev": true
}, },
"js-tokens": { "js-tokens": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
}, },
"jsonfile": { "jsonfile": {
"version": "6.0.1", "version": "6.0.1",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.0.1.tgz", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.0.1.tgz",
"integrity": "sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg==", "integrity": "sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg==",
"dev": true, "dev": true,
"requires": { "requires": {
"graceful-fs": "^4.1.6", "graceful-fs": "^4.1.6",
"universalify": "^1.0.0" "universalify": "^1.0.0"
} }
}, },
"lodash": { "lodash": {
"version": "4.17.20", "version": "4.17.20",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==",
"dev": true "dev": true
}, },
"loose-envify": { "loose-envify": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"requires": { "requires": {
"js-tokens": "^3.0.0 || ^4.0.0" "js-tokens": "^3.0.0 || ^4.0.0"
} }
}, },
"lunr": { "lunr": {
"version": "2.3.9", "version": "2.3.9",
"resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz",
"integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==",
"dev": true "dev": true
}, },
"marked": { "marked": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/marked/-/marked-1.1.1.tgz", "resolved": "https://registry.npmjs.org/marked/-/marked-1.1.1.tgz",
"integrity": "sha512-mJzT8D2yPxoPh7h0UXkB+dBj4FykPJ2OIfxAWeIHrvoHDkFxukV/29QxoFQoPM6RLEwhIFdJpmKBlqVM3s2ZIw==", "integrity": "sha512-mJzT8D2yPxoPh7h0UXkB+dBj4FykPJ2OIfxAWeIHrvoHDkFxukV/29QxoFQoPM6RLEwhIFdJpmKBlqVM3s2ZIw==",
"dev": true "dev": true
}, },
"minimatch": { "minimatch": {
"version": "3.0.4", "version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"dev": true, "dev": true,
"requires": { "requires": {
"brace-expansion": "^1.1.7" "brace-expansion": "^1.1.7"
} }
}, },
"minimist": { "minimist": {
"version": "1.2.5", "version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
"dev": true "dev": true
}, },
"neo-async": { "neo-async": {
"version": "2.6.2", "version": "2.6.2",
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
"dev": true "dev": true
}, },
"object-assign": { "object-assign": {
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
}, },
"once": { "once": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dev": true, "dev": true,
"requires": { "requires": {
"wrappy": "1" "wrappy": "1"
} }
}, },
"path-is-absolute": { "path-is-absolute": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
"dev": true "dev": true
}, },
"path-parse": { "path-parse": {
"version": "1.0.6", "version": "1.0.6",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
"dev": true "dev": true
}, },
"progress": { "progress": {
"version": "2.0.3", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
"integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
"dev": true "dev": true
}, },
"prop-types": { "prop-types": {
"version": "15.7.2", "version": "15.7.2",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
"integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
"requires": { "requires": {
"loose-envify": "^1.4.0", "loose-envify": "^1.4.0",
"object-assign": "^4.1.1", "object-assign": "^4.1.1",
"react-is": "^16.8.1" "react-is": "^16.8.1"
} }
}, },
"react": { "react": {
"version": "16.13.1", "version": "16.13.1",
"resolved": "https://registry.npmjs.org/react/-/react-16.13.1.tgz", "resolved": "https://registry.npmjs.org/react/-/react-16.13.1.tgz",
"integrity": "sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w==", "integrity": "sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w==",
"requires": { "requires": {
"loose-envify": "^1.1.0", "loose-envify": "^1.1.0",
"object-assign": "^4.1.1", "object-assign": "^4.1.1",
"prop-types": "^15.6.2" "prop-types": "^15.6.2"
} }
}, },
"react-dom": { "react-dom": {
"version": "16.13.1", "version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.13.1.tgz", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.13.1.tgz",
"integrity": "sha512-81PIMmVLnCNLO/fFOQxdQkvEq/+Hfpv24XNJfpyZhTRfO0QcmQIF/PgCa1zCOj2w1hrn12MFLyaJ/G0+Mxtfag==", "integrity": "sha512-81PIMmVLnCNLO/fFOQxdQkvEq/+Hfpv24XNJfpyZhTRfO0QcmQIF/PgCa1zCOj2w1hrn12MFLyaJ/G0+Mxtfag==",
"requires": { "requires": {
"loose-envify": "^1.1.0", "loose-envify": "^1.1.0",
"object-assign": "^4.1.1", "object-assign": "^4.1.1",
"prop-types": "^15.6.2", "prop-types": "^15.6.2",
"scheduler": "^0.19.1" "scheduler": "^0.19.1"
} }
}, },
"react-is": { "react-is": {
"version": "16.13.1", "version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
}, },
"rechoir": { "rechoir": {
"version": "0.6.2", "version": "0.6.2",
"resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz",
"integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=",
"dev": true, "dev": true,
"requires": { "requires": {
"resolve": "^1.1.6" "resolve": "^1.1.6"
} }
}, },
"resolve": { "resolve": {
"version": "1.17.0", "version": "1.17.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz",
"integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==",
"dev": true, "dev": true,
"requires": { "requires": {
"path-parse": "^1.0.6" "path-parse": "^1.0.6"
} }
}, },
"scheduler": { "scheduler": {
"version": "0.19.1", "version": "0.19.1",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz",
"integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==", "integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==",
"requires": { "requires": {
"loose-envify": "^1.1.0", "loose-envify": "^1.1.0",
"object-assign": "^4.1.1" "object-assign": "^4.1.1"
} }
}, },
"shelljs": { "shelljs": {
"version": "0.8.4", "version": "0.8.4",
"resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.4.tgz", "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.4.tgz",
"integrity": "sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ==", "integrity": "sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"glob": "^7.0.0", "glob": "^7.0.0",
"interpret": "^1.0.0", "interpret": "^1.0.0",
"rechoir": "^0.6.2" "rechoir": "^0.6.2"
} }
}, },
"source-map": { "source-map": {
"version": "0.6.1", "version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true "dev": true
}, },
"typedoc": { "typedoc": {
"version": "0.18.0", "version": "0.18.0",
"resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.18.0.tgz", "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.18.0.tgz",
"integrity": "sha512-UgDQwapCGQCCdYhEQzQ+kGutmcedklilgUGf62Vw6RdI29u6FcfAXFQfRTiJEbf16aK3YnkB20ctQK1JusCRbA==", "integrity": "sha512-UgDQwapCGQCCdYhEQzQ+kGutmcedklilgUGf62Vw6RdI29u6FcfAXFQfRTiJEbf16aK3YnkB20ctQK1JusCRbA==",
"dev": true, "dev": true,
"requires": { "requires": {
"fs-extra": "^9.0.1", "fs-extra": "^9.0.1",
"handlebars": "^4.7.6", "handlebars": "^4.7.6",
"highlight.js": "^10.0.0", "highlight.js": "^10.0.0",
"lodash": "^4.17.15", "lodash": "^4.17.15",
"lunr": "^2.3.8", "lunr": "^2.3.8",
"marked": "^1.1.1", "marked": "^1.1.1",
"minimatch": "^3.0.0", "minimatch": "^3.0.0",
"progress": "^2.0.3", "progress": "^2.0.3",
"shelljs": "^0.8.4", "shelljs": "^0.8.4",
"typedoc-default-themes": "^0.10.2" "typedoc-default-themes": "^0.10.2"
} }
}, },
"typedoc-default-themes": { "typedoc-default-themes": {
"version": "0.10.2", "version": "0.10.2",
"resolved": "https://registry.npmjs.org/typedoc-default-themes/-/typedoc-default-themes-0.10.2.tgz", "resolved": "https://registry.npmjs.org/typedoc-default-themes/-/typedoc-default-themes-0.10.2.tgz",
"integrity": "sha512-zo09yRj+xwLFE3hyhJeVHWRSPuKEIAsFK5r2u47KL/HBKqpwdUSanoaz5L34IKiSATFrjG5ywmIu98hPVMfxZg==", "integrity": "sha512-zo09yRj+xwLFE3hyhJeVHWRSPuKEIAsFK5r2u47KL/HBKqpwdUSanoaz5L34IKiSATFrjG5ywmIu98hPVMfxZg==",
"dev": true, "dev": true,
"requires": { "requires": {
"lunr": "^2.3.8" "lunr": "^2.3.8"
} }
}, },
"uglify-js": { "uglify-js": {
"version": "3.10.1", "version": "3.10.1",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.10.1.tgz", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.10.1.tgz",
"integrity": "sha512-RjxApKkrPJB6kjJxQS3iZlf///REXWYxYJxO/MpmlQzVkDWVI3PSnCBWezMecmTU/TRkNxrl8bmsfFQCp+LO+Q==", "integrity": "sha512-RjxApKkrPJB6kjJxQS3iZlf///REXWYxYJxO/MpmlQzVkDWVI3PSnCBWezMecmTU/TRkNxrl8bmsfFQCp+LO+Q==",
"dev": true, "dev": true,
"optional": true "optional": true
}, },
"universalify": { "universalify": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz", "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz",
"integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==", "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==",
"dev": true "dev": true
}, },
"wordwrap": { "wordwrap": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
"integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=",
"dev": true "dev": true
}, },
"wrappy": { "wrappy": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"dev": true "dev": true
}
} }
}
} }

View File

@ -1,23 +1,23 @@
{ {
"name": "splitview", "name": "splitview",
"version": "1.0.0", "version": "1.0.0",
"description": "", "description": "",
"main": "dist/esm/index.js", "main": "dist/esm/index.js",
"types": "dist/esm/index.d.ts", "types": "dist/esm/index.d.ts",
"module": "dist/esm/index.js", "module": "dist/esm/index.js",
"scripts": { "scripts": {
"build": "gulp run", "build": "gulp run",
"docs": "typedoc" "docs": "typedoc"
}, },
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@types/react": "^16.9.43", "@types/react": "^16.9.43",
"@types/react-dom": "^16.9.8", "@types/react-dom": "^16.9.8",
"react": "^16.13.1", "react": "^16.13.1",
"react-dom": "^16.13.1" "react-dom": "^16.13.1"
}, },
"devDependencies": { "devDependencies": {
"typedoc": "^0.18.0" "typedoc": "^0.18.0"
} }
} }

View File

@ -1,69 +1,69 @@
export function tail<T>(arr: T[]): [T[], T] { export function tail<T>(arr: T[]): [T[], T] {
if (arr.length === 0) { if (arr.length === 0) {
throw new Error("Invalid tail call"); throw new Error('Invalid tail call')
} }
return [arr.slice(0, arr.length - 1), arr[arr.length - 1]]; return [arr.slice(0, arr.length - 1), arr[arr.length - 1]]
} }
export function last<T>(arr: T[]): T { export function last<T>(arr: T[]): T {
return arr.length > 0 ? arr[arr.length - 1] : undefined; return arr.length > 0 ? arr[arr.length - 1] : undefined
} }
export function sequenceEquals<T>(arr1: T[], arr2: T[]) { export function sequenceEquals<T>(arr1: T[], arr2: T[]) {
if (arr1.length !== arr2.length) { if (arr1.length !== arr2.length) {
return false; return false
}
for (let i = 0; i < arr1.length; i++) {
if (arr1[i] !== arr2[i]) {
return false;
} }
}
return true; for (let i = 0; i < arr1.length; i++) {
if (arr1[i] !== arr2[i]) {
return false
}
}
return true
} }
/** /**
* Pushes an element to the start of the array, if found. * Pushes an element to the start of the array, if found.
*/ */
export function pushToStart<T>(arr: T[], value: T): void { export function pushToStart<T>(arr: T[], value: T): void {
const index = arr.indexOf(value); const index = arr.indexOf(value)
if (index > -1) { if (index > -1) {
arr.splice(index, 1); arr.splice(index, 1)
arr.unshift(value); arr.unshift(value)
} }
} }
/** /**
* Pushes an element to the end of the array, if found. * Pushes an element to the end of the array, if found.
*/ */
export function pushToEnd<T>(arr: T[], value: T): void { export function pushToEnd<T>(arr: T[], value: T): void {
const index = arr.indexOf(value); const index = arr.indexOf(value)
if (index > -1) { if (index > -1) {
arr.splice(index, 1); arr.splice(index, 1)
arr.push(value); arr.push(value)
} }
} }
export const range = (from: number, to: number = undefined) => { export const range = (from: number, to: number = undefined) => {
const result: number[] = []; const result: number[] = []
if (to === undefined) { if (to === undefined) {
to = from; to = from
from = 0; from = 0
}
if (from <= to) {
for (let i = from; i < to; i++) {
result.push(i);
} }
} else {
for (let i = from; i > to; i--) {
result.push(i);
}
}
return result; if (from <= to) {
}; for (let i = from; i < to; i++) {
result.push(i)
}
} else {
for (let i = from; i > to; i--) {
result.push(i)
}
}
return result
}

View File

@ -1,7 +1,7 @@
export function timeoutPromise(timeout: number): Promise<void> { export function timeoutPromise(timeout: number): Promise<void> {
return new Promise((resolve) => { return new Promise((resolve) => {
setTimeout(() => { setTimeout(() => {
resolve(); resolve()
}, timeout); }, timeout)
}); })
} }

View File

@ -1,14 +1,14 @@
import { Event, Emitter, addDisposableListener } from "./events"; import { Event, Emitter, addDisposableListener } from './events'
import { IDisposable, CompositeDisposable } from "./lifecycle"; import { IDisposable, CompositeDisposable } from './lifecycle'
export function getDomNodePagePosition(domNode: HTMLElement) { export function getDomNodePagePosition(domNode: HTMLElement) {
const bb = domNode.getBoundingClientRect(); const bb = domNode.getBoundingClientRect()
return { return {
left: bb.left + window.scrollX, left: bb.left + window.scrollX,
top: bb.top + window.scrollY, top: bb.top + window.scrollY,
width: bb.width, width: bb.width,
height: bb.height, height: bb.height,
}; }
} }
/** /**
@ -16,213 +16,217 @@ export function getDomNodePagePosition(domNode: HTMLElement) {
* the element is above or below the containers view portal. * the element is above or below the containers view portal.
*/ */
export const scrollIntoView = ( export const scrollIntoView = (
element: HTMLElement, element: HTMLElement,
container: HTMLElement container: HTMLElement
) => { ) => {
const { inView, breachPoint } = isElementInView(element, container, true); const { inView, breachPoint } = isElementInView(element, container, true)
if (!inView) { if (!inView) {
const adder = -container.offsetTop; const adder = -container.offsetTop
const isUp = breachPoint === "top"; const isUp = breachPoint === 'top'
container.scrollTo({ container.scrollTo({
top: isUp top: isUp
? adder + element.offsetTop ? adder + element.offsetTop
: adder + : adder +
element.offsetTop - element.offsetTop -
container.clientHeight + container.clientHeight +
element.clientHeight, element.clientHeight,
}); })
} }
}; }
export const isElementInView = ( export const isElementInView = (
element: HTMLElement, element: HTMLElement,
container: HTMLElement, container: HTMLElement,
fullyInView: boolean fullyInView: boolean
): { inView: boolean; breachPoint?: "top" | "bottom" } => { ): { inView: boolean; breachPoint?: 'top' | 'bottom' } => {
const containerOfftsetTop = container.offsetTop; const containerOfftsetTop = container.offsetTop
const containerTop = containerOfftsetTop + container.scrollTop; const containerTop = containerOfftsetTop + container.scrollTop
const containerBottom = const containerBottom =
containerTop + container.getBoundingClientRect().height; containerTop + container.getBoundingClientRect().height
const elementTop = element.offsetTop; const elementTop = element.offsetTop
const elementBottom = elementTop + element.getBoundingClientRect().height; const elementBottom = elementTop + element.getBoundingClientRect().height
const isAbove = fullyInView const isAbove = fullyInView
? containerTop >= elementTop ? containerTop >= elementTop
: elementTop > containerBottom; : elementTop > containerBottom
const isBelow = fullyInView const isBelow = fullyInView
? containerBottom <= elementBottom ? containerBottom <= elementBottom
: elementBottom < containerTop; : elementBottom < containerTop
if (isAbove) { if (isAbove) {
return { inView: false, breachPoint: "top" }; return { inView: false, breachPoint: 'top' }
} }
if (isBelow) { if (isBelow) {
return { inView: false, breachPoint: "bottom" }; return { inView: false, breachPoint: 'bottom' }
} }
return { inView: true }; return { inView: true }
}; }
export function isHTMLElement(o: any): o is HTMLElement { export function isHTMLElement(o: any): o is HTMLElement {
if (typeof HTMLElement === "object") { if (typeof HTMLElement === 'object') {
return o instanceof HTMLElement; return o instanceof HTMLElement
} }
return ( return (
o && o &&
typeof o === "object" && typeof o === 'object' &&
o.nodeType === 1 && o.nodeType === 1 &&
typeof o.nodeName === "string" typeof o.nodeName === 'string'
); )
} }
export const isInTree = (element: HTMLElement, className: string) => { export const isInTree = (element: HTMLElement, className: string) => {
let _element = element; let _element = element
while (_element) { while (_element) {
if (_element.classList.contains(className)) { if (_element.classList.contains(className)) {
return true; return true
}
_element = _element.parentElement
} }
_element = _element.parentElement;
}
return false; return false
}; }
export const removeClasses = (element: HTMLElement, ...classes: string[]) => { export const removeClasses = (element: HTMLElement, ...classes: string[]) => {
for (const classname of classes) { for (const classname of classes) {
if (element.classList.contains(classname)) { if (element.classList.contains(classname)) {
element.classList.remove(classname); element.classList.remove(classname)
}
} }
} }
};
export const addClasses = (element: HTMLElement, ...classes: string[]) => { export const addClasses = (element: HTMLElement, ...classes: string[]) => {
for (const classname of classes) { for (const classname of classes) {
if (!element.classList.contains(classname)) { if (!element.classList.contains(classname)) {
element.classList.add(classname); element.classList.add(classname)
}
} }
} }
};
export const toggleClass = ( export const toggleClass = (
element: HTMLElement, element: HTMLElement,
className: string, className: string,
isToggled: boolean isToggled: boolean
) => { ) => {
const hasClass = element.classList.contains(className); const hasClass = element.classList.contains(className)
if (isToggled && !hasClass) { if (isToggled && !hasClass) {
element.classList.add(className); element.classList.add(className)
} }
if (!isToggled && hasClass) { if (!isToggled && hasClass) {
element.classList.remove(className); element.classList.remove(className)
} }
}; }
export function firstIndex<T>( export function firstIndex<T>(
array: T[] | ReadonlyArray<T>, array: T[] | ReadonlyArray<T>,
fn: (item: T) => boolean fn: (item: T) => boolean
): number { ): number {
for (let i = 0; i < array.length; i++) { for (let i = 0; i < array.length; i++) {
const element = array[i]; const element = array[i]
if (fn(element)) { if (fn(element)) {
return i; return i
}
} }
}
return -1; return -1
} }
export function isAncestor( export function isAncestor(
testChild: Node | null, testChild: Node | null,
testAncestor: Node | null testAncestor: Node | null
): boolean { ): boolean {
while (testChild) { while (testChild) {
if (testChild === testAncestor) { if (testChild === testAncestor) {
return true; return true
}
testChild = testChild.parentNode
} }
testChild = testChild.parentNode;
}
return false; return false
} }
export interface IFocusTracker extends IDisposable { export interface IFocusTracker extends IDisposable {
onDidFocus: Event<void>; onDidFocus: Event<void>
onDidBlur: Event<void>; onDidBlur: Event<void>
refreshState?(): void; refreshState?(): void
} }
export function trackFocus(element: HTMLElement | Window): IFocusTracker { export function trackFocus(element: HTMLElement | Window): IFocusTracker {
return new FocusTracker(element); return new FocusTracker(element)
} }
/** /**
* Track focus on an element. Ensure tabIndex is set when an HTMLElement is not focusable by default * Track focus on an element. Ensure tabIndex is set when an HTMLElement is not focusable by default
*/ */
class FocusTracker extends CompositeDisposable implements IFocusTracker { class FocusTracker extends CompositeDisposable implements IFocusTracker {
private readonly _onDidFocus = new Emitter<void>(); private readonly _onDidFocus = new Emitter<void>()
public readonly onDidFocus: Event<void> = this._onDidFocus.event; public readonly onDidFocus: Event<void> = this._onDidFocus.event
private readonly _onDidBlur = new Emitter<void>(); private readonly _onDidBlur = new Emitter<void>()
public readonly onDidBlur: Event<void> = this._onDidBlur.event; public readonly onDidBlur: Event<void> = this._onDidBlur.event
private _refreshStateHandler: () => void; private _refreshStateHandler: () => void
constructor(element: HTMLElement | Window) { constructor(element: HTMLElement | Window) {
super(); super()
let hasFocus = isAncestor(document.activeElement, <HTMLElement>element); let hasFocus = isAncestor(document.activeElement, <HTMLElement>element)
let loosingFocus = false; let loosingFocus = false
const onFocus = () => { const onFocus = () => {
loosingFocus = false; loosingFocus = false
if (!hasFocus) { if (!hasFocus) {
hasFocus = true; hasFocus = true
this._onDidFocus.fire(); this._onDidFocus.fire()
} }
};
const onBlur = () => {
if (hasFocus) {
loosingFocus = true;
window.setTimeout(() => {
if (loosingFocus) {
loosingFocus = false;
hasFocus = false;
this._onDidBlur.fire();
}
}, 0);
}
};
this._refreshStateHandler = () => {
let currentNodeHasFocus = isAncestor(
document.activeElement,
<HTMLElement>element
);
if (currentNodeHasFocus !== hasFocus) {
if (hasFocus) {
onBlur();
} else {
onFocus();
} }
}
};
this.addDisposables(addDisposableListener(element, "focus", onFocus, true)); const onBlur = () => {
this.addDisposables(addDisposableListener(element, "blur", onBlur, true)); if (hasFocus) {
} loosingFocus = true
window.setTimeout(() => {
if (loosingFocus) {
loosingFocus = false
hasFocus = false
this._onDidBlur.fire()
}
}, 0)
}
}
refreshState() { this._refreshStateHandler = () => {
this._refreshStateHandler(); let currentNodeHasFocus = isAncestor(
} document.activeElement,
<HTMLElement>element
)
if (currentNodeHasFocus !== hasFocus) {
if (hasFocus) {
onBlur()
} else {
onFocus()
}
}
}
public dispose() { this.addDisposables(
super.dispose(); addDisposableListener(element, 'focus', onFocus, true)
)
this.addDisposables(
addDisposableListener(element, 'blur', onBlur, true)
)
}
this._onDidBlur.dispose(); refreshState() {
this._onDidFocus.dispose(); this._refreshStateHandler()
} }
public dispose() {
super.dispose()
this._onDidBlur.dispose()
this._onDidFocus.dispose()
}
} }

View File

@ -1,88 +1,88 @@
import { IDisposable } from "./lifecycle"; import { IDisposable } from './lifecycle'
export interface Event<T> { export interface Event<T> {
(listener: (e: T) => any): IDisposable; (listener: (e: T) => any): IDisposable
} }
export interface EmitterOptions { export interface EmitterOptions {
emitLastValue?: boolean; emitLastValue?: boolean
} }
export namespace Event { export namespace Event {
export const any = <T>(...children: Event<T>[]): Event<T> => { export const any = <T>(...children: Event<T>[]): Event<T> => {
return (listener: (e: T) => void) => { return (listener: (e: T) => void) => {
const disposables = children.map((child) => child(listener)); const disposables = children.map((child) => child(listener))
return { return {
dispose: () => { dispose: () => {
disposables.forEach((d) => { disposables.forEach((d) => {
d.dispose(); d.dispose()
}); })
}, },
}; }
}; }
}; }
} }
// dumb event emitter with better typings than nodes event emitter // dumb event emitter with better typings than nodes event emitter
// https://github.com/microsoft/vscode/blob/master/src/vs/base/common/event.ts // https://github.com/microsoft/vscode/blob/master/src/vs/base/common/event.ts
export class Emitter<T> implements IDisposable { export class Emitter<T> implements IDisposable {
private _event: Event<T>; private _event: Event<T>
private _last: T; private _last: T
private _listeners: Array<(e: T) => any> = []; private _listeners: Array<(e: T) => any> = []
private _disposed: boolean = false; private _disposed: boolean = false
constructor(private readonly options?: EmitterOptions) {} constructor(private readonly options?: EmitterOptions) {}
get event() { get event() {
if (!this._event) { if (!this._event) {
this._event = (listener: (e: T) => void): IDisposable => { this._event = (listener: (e: T) => void): IDisposable => {
if (this.options?.emitLastValue && this._last !== undefined) { if (this.options?.emitLastValue && this._last !== undefined) {
listener(this._last); listener(this._last)
} }
this._listeners.push(listener); this._listeners.push(listener)
return { return {
dispose: () => { dispose: () => {
const index = this._listeners.indexOf(listener); const index = this._listeners.indexOf(listener)
if (index > -1) { if (index > -1) {
this._listeners.splice(index, 1); this._listeners.splice(index, 1)
}
},
}
} }
}, }
}; return this._event
};
} }
return this._event;
}
public fire(e: T) { public fire(e: T) {
this._last = e; this._last = e
this._listeners.forEach((listener) => { this._listeners.forEach((listener) => {
listener(e); listener(e)
}); })
} }
public dispose() { public dispose() {
this._listeners = []; this._listeners = []
this._disposed = true; this._disposed = true
} }
} }
export type EventHandler = HTMLElement | HTMLDocument | Window; export type EventHandler = HTMLElement | HTMLDocument | Window
export const addDisposableListener = <K extends keyof HTMLElementEventMap>( export const addDisposableListener = <K extends keyof HTMLElementEventMap>(
element: EventHandler, element: EventHandler,
type: K, type: K,
listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any,
options?: boolean | AddEventListenerOptions options?: boolean | AddEventListenerOptions
): IDisposable => { ): IDisposable => {
element.addEventListener(type, listener, options); element.addEventListener(type, listener, options)
return { return {
dispose: () => { dispose: () => {
element.removeEventListener(type, listener); element.removeEventListener(type, listener)
}, },
}; }
}; }

View File

@ -1,9 +1,9 @@
export function debounce<T extends Function>(cb: T, wait: number) { export function debounce<T extends Function>(cb: T, wait: number) {
let timeout: NodeJS.Timeout; let timeout: NodeJS.Timeout
const callable = (...args: any) => { const callable = (...args: any) => {
clearTimeout(timeout); clearTimeout(timeout)
timeout = setTimeout(() => cb(...args), wait); timeout = setTimeout(() => cb(...args), wait)
}; }
return <T>(<any>callable); return <T>(<any>callable)
} }

View File

@ -1,244 +1,246 @@
import { import {
IView, IView,
SplitView, SplitView,
Orientation, Orientation,
Sizing, Sizing,
LayoutPriority, LayoutPriority,
} from "../splitview/splitview"; } from '../splitview/splitview'
import { Emitter, Event } from "../events"; import { Emitter, Event } from '../events'
import { INodeDescriptor } from "./gridview"; import { INodeDescriptor } from './gridview'
import { Node } from "./types"; import { Node } from './types'
import { CompositeDisposable, IDisposable, Disposable } from "../lifecycle"; import { CompositeDisposable, IDisposable, Disposable } from '../lifecycle'
export class BranchNode extends CompositeDisposable implements IView { export class BranchNode extends CompositeDisposable implements IView {
readonly element: HTMLElement; readonly element: HTMLElement
private splitview: SplitView; private splitview: SplitView
private _orthogonalSize: number; private _orthogonalSize: number
private _size: number; private _size: number
public readonly children: Node[] = []; public readonly children: Node[] = []
private readonly _onDidChange = new Emitter<number | undefined>(); private readonly _onDidChange = new Emitter<number | undefined>()
readonly onDidChange: Event<number | undefined> = this._onDidChange.event; readonly onDidChange: Event<number | undefined> = this._onDidChange.event
get width(): number { get width(): number {
return this.orientation === Orientation.HORIZONTAL return this.orientation === Orientation.HORIZONTAL
? this.size ? this.size
: this.orthogonalSize; : this.orthogonalSize
}
get height(): number {
return this.orientation === Orientation.HORIZONTAL
? this.orthogonalSize
: this.size;
}
get minimumSize(): number {
return this.children.length === 0
? 0
: Math.max(...this.children.map((c) => c.minimumOrthogonalSize));
}
get maximumSize(): number {
return Math.min(...this.children.map((c) => c.maximumOrthogonalSize));
}
get minimumOrthogonalSize(): number {
return this.splitview.minimumSize;
}
get maximumOrthogonalSize(): number {
return this.splitview.maximumSize;
}
get orthogonalSize() {
return this._orthogonalSize;
}
get size() {
return this._size;
}
get minimumWidth(): number {
return this.orientation === Orientation.HORIZONTAL
? this.minimumOrthogonalSize
: this.minimumSize;
}
get snapSize() {
return undefined;
}
get minimumHeight(): number {
return this.orientation === Orientation.HORIZONTAL
? this.minimumSize
: this.minimumOrthogonalSize;
}
get maximumWidth(): number {
return this.orientation === Orientation.HORIZONTAL
? this.maximumOrthogonalSize
: this.maximumSize;
}
get maximumHeight(): number {
return this.orientation === Orientation.HORIZONTAL
? this.maximumSize
: this.maximumOrthogonalSize;
}
get priority(): LayoutPriority {
if (this.children.length === 0) {
return LayoutPriority.Normal;
} }
const priorities = this.children.map((c) => get height(): number {
typeof c.priority === "undefined" ? LayoutPriority.Normal : c.priority return this.orientation === Orientation.HORIZONTAL
); ? this.orthogonalSize
: this.size
if (priorities.some((p) => p === LayoutPriority.High)) {
return LayoutPriority.High;
} else if (priorities.some((p) => p === LayoutPriority.Low)) {
return LayoutPriority.Low;
} }
return LayoutPriority.Normal; get minimumSize(): number {
} return this.children.length === 0
? 0
constructor( : Math.max(...this.children.map((c) => c.minimumOrthogonalSize))
readonly orientation: Orientation,
readonly proportionalLayout: boolean,
size: number = 0,
orthogonalSize: number,
childDescriptors?: INodeDescriptor[]
) {
super();
this._orthogonalSize = orthogonalSize;
this._size = size;
this.element = document.createElement("div");
this.element.className = "branch-node";
if (!childDescriptors) {
this.splitview = new SplitView(this.element, {
orientation: this.orientation,
proportionalLayout,
});
this.splitview.layout(this.size, this.orthogonalSize);
} else {
const descriptor = {
views: childDescriptors.map((childDescriptor) => {
return {
view: childDescriptor.node,
size: childDescriptor.node.size,
};
}),
size: this.orthogonalSize,
};
this.children = childDescriptors.map((c) => c.node);
this.splitview = new SplitView(this.element, {
orientation: this.orientation,
descriptor,
});
} }
this.addDisposables( get maximumSize(): number {
this.splitview.onDidSashEnd(() => { return Math.min(...this.children.map((c) => c.maximumOrthogonalSize))
this._onDidChange.fire(undefined);
})
);
}
moveChild(from: number, to: number): void {
if (from === to) {
return;
} }
if (from < 0 || from >= this.children.length) { get minimumOrthogonalSize(): number {
throw new Error("Invalid from index"); return this.splitview.minimumSize
} }
if (from < to) { get maximumOrthogonalSize(): number {
to--; return this.splitview.maximumSize
} }
this.splitview.moveView(from, to); get orthogonalSize() {
return this._orthogonalSize
const child = this._removeChild(from);
this._addChild(child, to);
}
getChildSize(index: number): number {
if (index < 0 || index >= this.children.length) {
throw new Error("Invalid index");
} }
return this.splitview.getViewSize(index); get size() {
} return this._size
resizeChild(index: number, size: number): void {
if (index < 0 || index >= this.children.length) {
throw new Error("Invalid index");
} }
this.splitview.resizeView(index, size); get minimumWidth(): number {
} return this.orientation === Orientation.HORIZONTAL
? this.minimumOrthogonalSize
public layout(size: number, orthogonalSize: number) { : this.minimumSize
this._size = orthogonalSize;
this._orthogonalSize = size;
this.splitview.layout(this.size, this.orthogonalSize);
}
public addChild(node: Node, size: number | Sizing, index: number): void {
if (index < 0 || index > this.children.length) {
throw new Error("Invalid index");
} }
this.splitview.addView(node, size, index); get snapSize() {
this._addChild(node, index); return undefined
}
public removeChild(index: number, sizing?: Sizing) {
if (index < 0 || index >= this.children.length) {
throw new Error("Invalid index");
} }
this.splitview.removeView(index, sizing); get minimumHeight(): number {
this._removeChild(index); return this.orientation === Orientation.HORIZONTAL
} ? this.minimumSize
: this.minimumOrthogonalSize
}
private _addChild(node: Node, index: number): void { get maximumWidth(): number {
this.children.splice(index, 0, node); return this.orientation === Orientation.HORIZONTAL
this.setupChildrenEvents(); ? this.maximumOrthogonalSize
} : this.maximumSize
}
private _removeChild(index: number): Node { get maximumHeight(): number {
const first = index === 0; return this.orientation === Orientation.HORIZONTAL
const last = index === this.children.length - 1; ? this.maximumSize
const [child] = this.children.splice(index, 1); : this.maximumOrthogonalSize
this.setupChildrenEvents(); }
return child; get priority(): LayoutPriority {
} if (this.children.length === 0) {
return LayoutPriority.Normal
}
private _childrenDisposable: IDisposable = Disposable.NONE; const priorities = this.children.map((c) =>
typeof c.priority === 'undefined'
? LayoutPriority.Normal
: c.priority
)
private setupChildrenEvents() { if (priorities.some((p) => p === LayoutPriority.High)) {
this._childrenDisposable.dispose(); return LayoutPriority.High
} else if (priorities.some((p) => p === LayoutPriority.Low)) {
return LayoutPriority.Low
}
this._childrenDisposable = Event.any( return LayoutPriority.Normal
...this.children.map((c) => c.onDidChange) }
)((e) => {
this._onDidChange.fire(e);
});
}
public dispose() { constructor(
super.dispose(); readonly orientation: Orientation,
this._childrenDisposable.dispose(); readonly proportionalLayout: boolean,
this.splitview.dispose(); size: number = 0,
this.children.forEach((child) => child.dispose()); orthogonalSize: number,
}
childDescriptors?: INodeDescriptor[]
) {
super()
this._orthogonalSize = orthogonalSize
this._size = size
this.element = document.createElement('div')
this.element.className = 'branch-node'
if (!childDescriptors) {
this.splitview = new SplitView(this.element, {
orientation: this.orientation,
proportionalLayout,
})
this.splitview.layout(this.size, this.orthogonalSize)
} else {
const descriptor = {
views: childDescriptors.map((childDescriptor) => {
return {
view: childDescriptor.node,
size: childDescriptor.node.size,
}
}),
size: this.orthogonalSize,
}
this.children = childDescriptors.map((c) => c.node)
this.splitview = new SplitView(this.element, {
orientation: this.orientation,
descriptor,
})
}
this.addDisposables(
this.splitview.onDidSashEnd(() => {
this._onDidChange.fire(undefined)
})
)
}
moveChild(from: number, to: number): void {
if (from === to) {
return
}
if (from < 0 || from >= this.children.length) {
throw new Error('Invalid from index')
}
if (from < to) {
to--
}
this.splitview.moveView(from, to)
const child = this._removeChild(from)
this._addChild(child, to)
}
getChildSize(index: number): number {
if (index < 0 || index >= this.children.length) {
throw new Error('Invalid index')
}
return this.splitview.getViewSize(index)
}
resizeChild(index: number, size: number): void {
if (index < 0 || index >= this.children.length) {
throw new Error('Invalid index')
}
this.splitview.resizeView(index, size)
}
public layout(size: number, orthogonalSize: number) {
this._size = orthogonalSize
this._orthogonalSize = size
this.splitview.layout(this.size, this.orthogonalSize)
}
public addChild(node: Node, size: number | Sizing, index: number): void {
if (index < 0 || index > this.children.length) {
throw new Error('Invalid index')
}
this.splitview.addView(node, size, index)
this._addChild(node, index)
}
public removeChild(index: number, sizing?: Sizing) {
if (index < 0 || index >= this.children.length) {
throw new Error('Invalid index')
}
this.splitview.removeView(index, sizing)
this._removeChild(index)
}
private _addChild(node: Node, index: number): void {
this.children.splice(index, 0, node)
this.setupChildrenEvents()
}
private _removeChild(index: number): Node {
const first = index === 0
const last = index === this.children.length - 1
const [child] = this.children.splice(index, 1)
this.setupChildrenEvents()
return child
}
private _childrenDisposable: IDisposable = Disposable.NONE
private setupChildrenEvents() {
this._childrenDisposable.dispose()
this._childrenDisposable = Event.any(
...this.children.map((c) => c.onDidChange)
)((e) => {
this._onDidChange.fire(e)
})
}
public dispose() {
super.dispose()
this._childrenDisposable.dispose()
this.splitview.dispose()
this.children.forEach((child) => child.dispose())
}
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,94 +1,94 @@
import { IView, LayoutPriority, Orientation } from "../splitview/splitview"; import { IView, LayoutPriority, Orientation } from '../splitview/splitview'
import { Emitter, Event } from "../events"; import { Emitter, Event } from '../events'
import { IGridView } from "./gridview"; import { IGridView } from './gridview'
export class LeafNode implements IView { export class LeafNode implements IView {
private readonly _onDidChange = new Emitter<number | undefined>(); private readonly _onDidChange = new Emitter<number | undefined>()
readonly onDidChange: Event<number | undefined> = this._onDidChange.event; readonly onDidChange: Event<number | undefined> = this._onDidChange.event
private _size: number; private _size: number
private _orthogonalSize: number; private _orthogonalSize: number
public dispose() {} public dispose() {}
private get minimumWidth(): number { private get minimumWidth(): number {
return this.view.minimumWidth; return this.view.minimumWidth
} }
private get maximumWidth(): number { private get maximumWidth(): number {
return this.view.maximumWidth; return this.view.maximumWidth
} }
private get minimumHeight(): number { private get minimumHeight(): number {
return this.view.minimumHeight; return this.view.minimumHeight
} }
private get maximumHeight(): number { private get maximumHeight(): number {
return this.view.maximumHeight; return this.view.maximumHeight
} }
get priority(): LayoutPriority | undefined { get priority(): LayoutPriority | undefined {
return this.view.priority; return this.view.priority
} }
get snapSize() { get snapSize() {
return this.view.snap ? this.minimumSize / 2 : undefined; return this.view.snap ? this.minimumSize / 2 : undefined
} }
get minimumSize(): number { get minimumSize(): number {
return this.orientation === Orientation.HORIZONTAL return this.orientation === Orientation.HORIZONTAL
? this.minimumHeight ? this.minimumHeight
: this.minimumWidth; : this.minimumWidth
} }
get maximumSize(): number { get maximumSize(): number {
return this.orientation === Orientation.HORIZONTAL return this.orientation === Orientation.HORIZONTAL
? this.maximumHeight ? this.maximumHeight
: this.maximumWidth; : this.maximumWidth
} }
get minimumOrthogonalSize(): number { get minimumOrthogonalSize(): number {
return this.orientation === Orientation.HORIZONTAL return this.orientation === Orientation.HORIZONTAL
? this.minimumWidth ? this.minimumWidth
: this.minimumHeight; : this.minimumHeight
} }
get maximumOrthogonalSize(): number { get maximumOrthogonalSize(): number {
return this.orientation === Orientation.HORIZONTAL return this.orientation === Orientation.HORIZONTAL
? this.maximumWidth ? this.maximumWidth
: this.maximumHeight; : this.maximumHeight
} }
get orthogonalSize() { get orthogonalSize() {
return this._orthogonalSize; return this._orthogonalSize
} }
get size() { get size() {
return this._size; return this._size
} }
get element() { get element() {
return this.view.element; return this.view.element
} }
constructor( constructor(
public readonly view: IGridView, public readonly view: IGridView,
readonly orientation: Orientation, readonly orientation: Orientation,
orthogonalSize: number, orthogonalSize: number,
size: number = 0 size: number = 0
) { ) {
this._orthogonalSize = orthogonalSize; this._orthogonalSize = orthogonalSize
this._size = size; this._size = size
} }
public layout(size: number, orthogonalSize: number) { public layout(size: number, orthogonalSize: number) {
this._size = size; this._size = size
this._orthogonalSize = orthogonalSize; this._orthogonalSize = orthogonalSize
const [width, height] = const [width, height] =
this.orientation === Orientation.HORIZONTAL this.orientation === Orientation.HORIZONTAL
? [orthogonalSize, size] ? [orthogonalSize, size]
: [size, orthogonalSize]; : [size, orthogonalSize]
this.view.layout(width, height, 0, 0); this.view.layout(width, height, 0, 0)
} }
} }

View File

@ -1,4 +1,4 @@
import { BranchNode } from "./branchNode"; import { BranchNode } from './branchNode'
import { LeafNode } from "./leafNode"; import { LeafNode } from './leafNode'
export type Node = BranchNode | LeafNode; export type Node = BranchNode | LeafNode

View File

@ -1,29 +1,29 @@
.actions-bar { .actions-bar {
text-align: right; text-align: right;
width: 28px; width: 28px;
display: flex;
align-items: center;
.actions-container {
display: flex; display: flex;
padding: 0px; align-items: center;
margin: 0px;
justify-content: flex-end;
a:active { .actions-container {
-webkit-mask-size: 100% 100% !important; display: flex;
mask-size: 100% 100% !important; padding: 0px;
} margin: 0px;
justify-content: flex-end;
.close-action { a:active {
background-color: white; -webkit-mask-size: 100% 100% !important;
height: 16px; mask-size: 100% 100% !important;
width: 16px; }
display: block;
-webkit-mask: var(--tab-close-icon) 50% 50% / 90% 90% no-repeat; .close-action {
mask: var(--tab-close-icon) 50% 50% / 90% 90% no-repeat; background-color: white;
margin-right: "0.5em"; height: 16px;
cursor: pointer; width: 16px;
display: block;
-webkit-mask: var(--tab-close-icon) 50% 50% / 90% 90% no-repeat;
mask: var(--tab-close-icon) 50% 50% / 90% 90% no-repeat;
margin-right: '0.5em';
cursor: pointer;
}
} }
}
} }

View File

@ -1,22 +1,22 @@
export class ActionContainer { export class ActionContainer {
private _element: HTMLElement; private _element: HTMLElement
private _list: HTMLElement; private _list: HTMLElement
get element() { get element() {
return this._element; return this._element
} }
constructor() { constructor() {
this._element = document.createElement("div"); this._element = document.createElement('div')
this._element.className = "actions-bar"; this._element.className = 'actions-bar'
this._list = document.createElement("ul"); this._list = document.createElement('ul')
this._list.className = "actions-container"; this._list.className = 'actions-container'
this._element.appendChild(this._list); this._element.appendChild(this._list)
} }
public add(element: HTMLElement) { public add(element: HTMLElement) {
this._list.appendChild(element); this._list.appendChild(element)
} }
} }

View File

@ -1,124 +1,124 @@
import { PanelOptions } from "../../layout/options"; import { PanelOptions } from '../../layout/options'
export const DATA_KEY = "splitview/transfer"; export const DATA_KEY = 'splitview/transfer'
export const isPanelTransferEvent = (event: DragEvent) => { export const isPanelTransferEvent = (event: DragEvent) => {
return event.dataTransfer.types.includes(DATA_KEY); return event.dataTransfer.types.includes(DATA_KEY)
}; }
export enum DragType { export enum DragType {
ITEM = "group_drag", ITEM = 'group_drag',
EXTERNAL = "external_group_drag", EXTERNAL = 'external_group_drag',
} }
export interface DragItem { export interface DragItem {
itemId: string; itemId: string
groupId: string; groupId: string
} }
export interface ExternalDragItem extends PanelOptions {} export interface ExternalDragItem extends PanelOptions {}
export type DataObject = DragItem | ExternalDragItem; export type DataObject = DragItem | ExternalDragItem
/** /**
* Determine whether this data belong to that of an event that was started by * Determine whether this data belong to that of an event that was started by
* dragging a tab component * dragging a tab component
*/ */
export const isTabDragEvent = (data: any): data is DragItem => { export const isTabDragEvent = (data: any): data is DragItem => {
return data.type === DragType.ITEM; return data.type === DragType.ITEM
}; }
/** /**
* Determine whether this data belong to that of an event that was started by * Determine whether this data belong to that of an event that was started by
* a custom drag-enable component * a custom drag-enable component
*/ */
export const isCustomDragEvent = (data: any): data is ExternalDragItem => { export const isCustomDragEvent = (data: any): data is ExternalDragItem => {
return data.type === DragType.EXTERNAL; return data.type === DragType.EXTERNAL
};
export const extractData = (event: DragEvent): DataObject => {
const data = JSON.parse(event.dataTransfer.getData(DATA_KEY));
if (!data) {
console.warn(`[dragEvent] ${DATA_KEY} data is missing`);
}
if (typeof data.type !== "string") {
console.warn(`[dragEvent] invalid type ${data.type}`);
}
return data;
};
class DataTransfer {
private map = new Map<string, string>();
public setData(format: string, data: string) {
this.map.set(format, data);
}
public getData(format: string) {
const data = this.map.get(format);
return data;
}
public has(format: string) {
return this.map.has(format);
}
public removeData(format: string) {
const data = this.getData(format);
this.map.delete(format);
return data;
}
get size() {
return this.map.size;
}
} }
export const DataTransferSingleton = new DataTransfer(); export const extractData = (event: DragEvent): DataObject => {
const data = JSON.parse(event.dataTransfer.getData(DATA_KEY))
if (!data) {
console.warn(`[dragEvent] ${DATA_KEY} data is missing`)
}
if (typeof data.type !== 'string') {
console.warn(`[dragEvent] invalid type ${data.type}`)
}
return data
}
class DataTransfer {
private map = new Map<string, string>()
public setData(format: string, data: string) {
this.map.set(format, data)
}
public getData(format: string) {
const data = this.map.get(format)
return data
}
public has(format: string) {
return this.map.has(format)
}
public removeData(format: string) {
const data = this.getData(format)
this.map.delete(format)
return data
}
get size() {
return this.map.size
}
}
export const DataTransferSingleton = new DataTransfer()
/** /**
* A singleton to store transfer data during drag & drop operations that are only valid within the application. * A singleton to store transfer data during drag & drop operations that are only valid within the application.
*/ */
export class LocalSelectionTransfer<T> { export class LocalSelectionTransfer<T> {
private static readonly INSTANCE = new LocalSelectionTransfer(); private static readonly INSTANCE = new LocalSelectionTransfer()
private data?: T[]; private data?: T[]
private proto?: T; private proto?: T
private constructor() { private constructor() {
// protect against external instantiation // protect against external instantiation
}
static getInstance<T>(): LocalSelectionTransfer<T> {
return LocalSelectionTransfer.INSTANCE as LocalSelectionTransfer<T>;
}
hasData(proto: T): boolean {
return proto && proto === this.proto;
}
clearData(proto: T): void {
if (this.hasData(proto)) {
this.proto = undefined;
this.data = undefined;
}
}
getData(proto: T): T[] | undefined {
if (this.hasData(proto)) {
return this.data;
} }
return undefined; static getInstance<T>(): LocalSelectionTransfer<T> {
} return LocalSelectionTransfer.INSTANCE as LocalSelectionTransfer<T>
}
setData(data: T[], proto: T): void {
if (proto) { hasData(proto: T): boolean {
this.data = data; return proto && proto === this.proto
this.proto = proto; }
clearData(proto: T): void {
if (this.hasData(proto)) {
this.proto = undefined
this.data = undefined
}
}
getData(proto: T): T[] | undefined {
if (this.hasData(proto)) {
return this.data
}
return undefined
}
setData(data: T[], proto: T): void {
if (proto) {
this.data = data
this.proto = proto
}
} }
}
} }

View File

@ -1,38 +1,38 @@
.drop-target { .drop-target {
position: relative;
.drop-target-dropzone {
position: absolute;
left: 0px;
top: 0px;
height: 100%;
width: 100%;
z-index: 10000;
}
.drop-target-selection {
position: relative; position: relative;
pointer-events: none;
height: 100%;
width: 100%;
background-color: var(--drag-over-background-color);
transition-duration: 0.15s; .drop-target-dropzone {
transition-timing-function: ease-out; position: absolute;
left: 0px;
top: 0px;
height: 100%;
width: 100%;
z-index: 10000;
}
&.left, .drop-target-selection {
&.right { position: relative;
width: 50%; pointer-events: none;
height: 100%;
width: 100%;
background-color: var(--drag-over-background-color);
transition-duration: 0.15s;
transition-timing-function: ease-out;
&.left,
&.right {
width: 50%;
}
&.right {
transform: translate(100%, 0%);
}
&.bottom {
transform: translate(0%, 100%);
}
&.top,
&.bottom {
height: 50%;
}
} }
&.right {
transform: translate(100%, 0%);
}
&.bottom {
transform: translate(0%, 100%);
}
&.top,
&.bottom {
height: 50%;
}
}
} }

View File

@ -1,170 +1,170 @@
import { Emitter, Event } from "../../events"; import { Emitter, Event } from '../../events'
import { DataTransferSingleton } from "./dataTransfer"; import { DataTransferSingleton } from './dataTransfer'
export enum Position { export enum Position {
Top = "Top", Top = 'Top',
Left = "Left", Left = 'Left',
Bottom = "Bottom", Bottom = 'Bottom',
Right = "Right", Right = 'Right',
Center = "Center", Center = 'Center',
} }
export interface DroptargetEvent { export interface DroptargetEvent {
position: Position; position: Position
event: DragEvent; event: DragEvent
} }
const HAS_PROCESSED_KEY = "__drop_target_processed__"; const HAS_PROCESSED_KEY = '__drop_target_processed__'
export const hasProcessed = (event: DragEvent) => export const hasProcessed = (event: DragEvent) =>
!!(event as any)[HAS_PROCESSED_KEY]; !!(event as any)[HAS_PROCESSED_KEY]
// tagging events as processed is better than calling .stopPropagation() which is the root of all evil // tagging events as processed is better than calling .stopPropagation() which is the root of all evil
const setEventAsProcessed = (event: DragEvent) => { const setEventAsProcessed = (event: DragEvent) => {
event[HAS_PROCESSED_KEY] = true; event[HAS_PROCESSED_KEY] = true
}; }
const toggleClassName = ( const toggleClassName = (
element: HTMLElement, element: HTMLElement,
className: string, className: string,
addOrRemove: boolean addOrRemove: boolean
) => { ) => {
if (addOrRemove && !element.classList.contains(className)) { if (addOrRemove && !element.classList.contains(className)) {
element.classList.add(className); element.classList.add(className)
} else if (!addOrRemove && element.classList.contains(className)) { } else if (!addOrRemove && element.classList.contains(className)) {
element.classList.remove(className); element.classList.remove(className)
} }
}; }
export class Droptarget { export class Droptarget {
private target: HTMLElement; private target: HTMLElement
private overlay: HTMLElement; private overlay: HTMLElement
private state: Position | undefined; private state: Position | undefined
private readonly _onDidChange = new Emitter<DroptargetEvent>(); private readonly _onDidChange = new Emitter<DroptargetEvent>()
readonly onDidChange: Event<DroptargetEvent> = this._onDidChange.event; readonly onDidChange: Event<DroptargetEvent> = this._onDidChange.event
constructor( constructor(
private element: HTMLElement, private element: HTMLElement,
private options: { private options: {
isDisabled: () => boolean; isDisabled: () => boolean
isDirectional: boolean; isDirectional: boolean
id: string; id: string
enableExternalDragEvents?: boolean; enableExternalDragEvents?: boolean
} }
) {
this.element.addEventListener("dragenter", this.onDragEnter);
}
public dispose() {
this._onDidChange.dispose();
this.removeDropTarget();
this.element.removeEventListener("dragenter", this.onDragEnter);
}
private onDragEnter = (event: DragEvent) => {
if (
!this.options.enableExternalDragEvents &&
!DataTransferSingleton.has(this.options.id)
) { ) {
console.debug("[droptarget] invalid event"); this.element.addEventListener('dragenter', this.onDragEnter)
return;
} }
if (this.options.isDisabled()) { public dispose() {
return; this._onDidChange.dispose()
this.removeDropTarget()
this.element.removeEventListener('dragenter', this.onDragEnter)
} }
event.preventDefault(); private onDragEnter = (event: DragEvent) => {
if (!this.target) { if (
console.debug("[droptarget] created"); !this.options.enableExternalDragEvents &&
this.target = document.createElement("div"); !DataTransferSingleton.has(this.options.id)
this.target.className = "drop-target-dropzone"; ) {
this.overlay = document.createElement("div"); console.debug('[droptarget] invalid event')
this.overlay.className = "drop-target-selection"; return
// }
this.target.addEventListener("dragover", this.onDragOver);
this.target.addEventListener("dragleave", this.onDragLeave);
this.target.addEventListener("drop", this.onDrop);
this.target.appendChild(this.overlay);
this.element.classList.add("drop-target"); if (this.options.isDisabled()) {
this.element.append(this.target); return
} }
};
private onDrop = (event: DragEvent) => { event.preventDefault()
if ( if (!this.target) {
!this.options.enableExternalDragEvents && console.debug('[droptarget] created')
!DataTransferSingleton.has(this.options.id) this.target = document.createElement('div')
) { this.target.className = 'drop-target-dropzone'
console.debug("[dragtarget] invalid"); this.overlay = document.createElement('div')
return; this.overlay.className = 'drop-target-selection'
//
this.target.addEventListener('dragover', this.onDragOver)
this.target.addEventListener('dragleave', this.onDragLeave)
this.target.addEventListener('drop', this.onDrop)
this.target.appendChild(this.overlay)
this.element.classList.add('drop-target')
this.element.append(this.target)
}
} }
console.debug("[dragtarget] drop"); private onDrop = (event: DragEvent) => {
this.removeDropTarget(); if (
!this.options.enableExternalDragEvents &&
!DataTransferSingleton.has(this.options.id)
) {
console.debug('[dragtarget] invalid')
return
}
if (!hasProcessed(event)) { console.debug('[dragtarget] drop')
this._onDidChange.fire({ position: this.state, event }); this.removeDropTarget()
} else {
console.debug("[dragtarget] already processed");
}
this.state = undefined;
setEventAsProcessed(event); if (!hasProcessed(event)) {
}; this._onDidChange.fire({ position: this.state, event })
} else {
console.debug('[dragtarget] already processed')
}
this.state = undefined
private onDragOver = (event: DragEvent) => { setEventAsProcessed(event)
event.preventDefault();
if (!this.options.isDirectional) {
return;
} }
const width = this.target.clientWidth; private onDragOver = (event: DragEvent) => {
const height = this.target.clientHeight; event.preventDefault()
const x = event.offsetX;
const y = event.offsetY;
const xp = (100 * x) / width;
const yp = (100 * y) / height;
const isRight = xp > 80; if (!this.options.isDirectional) {
const isLeft = xp < 20; return
const isTop = !isRight && !isLeft && yp < 20; }
const isBottom = !isRight && !isLeft && yp > 80;
toggleClassName(this.overlay, "right", isRight); const width = this.target.clientWidth
toggleClassName(this.overlay, "left", isLeft); const height = this.target.clientHeight
toggleClassName(this.overlay, "top", isTop); const x = event.offsetX
toggleClassName(this.overlay, "bottom", isBottom); const y = event.offsetY
const xp = (100 * x) / width
const yp = (100 * y) / height
if (isRight) { const isRight = xp > 80
this.state = Position.Right; const isLeft = xp < 20
} else if (isLeft) { const isTop = !isRight && !isLeft && yp < 20
this.state = Position.Left; const isBottom = !isRight && !isLeft && yp > 80
} else if (isTop) {
this.state = Position.Top; toggleClassName(this.overlay, 'right', isRight)
} else if (isBottom) { toggleClassName(this.overlay, 'left', isLeft)
this.state = Position.Bottom; toggleClassName(this.overlay, 'top', isTop)
} else { toggleClassName(this.overlay, 'bottom', isBottom)
this.state = Position.Center;
if (isRight) {
this.state = Position.Right
} else if (isLeft) {
this.state = Position.Left
} else if (isTop) {
this.state = Position.Top
} else if (isBottom) {
this.state = Position.Bottom
} else {
this.state = Position.Center
}
} }
};
private onDragLeave = (event: DragEvent) => { private onDragLeave = (event: DragEvent) => {
console.debug("[droptarget] leave"); console.debug('[droptarget] leave')
this.removeDropTarget(); this.removeDropTarget()
}; }
private removeDropTarget() { private removeDropTarget() {
if (this.target) { if (this.target) {
this.target.removeEventListener("dragover", this.onDragOver); this.target.removeEventListener('dragover', this.onDragOver)
this.target.removeEventListener("dragleave", this.onDragLeave); this.target.removeEventListener('dragleave', this.onDragLeave)
this.target.removeEventListener("drop", this.onDrop); this.target.removeEventListener('drop', this.onDrop)
this.element.removeChild(this.target); this.element.removeChild(this.target)
this.target = undefined; this.target = undefined
this.element.classList.remove("drop-target"); this.element.classList.remove('drop-target')
}
} }
}
} }

View File

@ -1,19 +1,19 @@
import { DroptargetEvent } from "./droptarget/droptarget"; import { DroptargetEvent } from './droptarget/droptarget'
import { IGroupPanel } from "./panel/types"; import { IGroupPanel } from './panel/types'
export interface TabDropEvent { export interface TabDropEvent {
event: DroptargetEvent; event: DroptargetEvent
index?: number; index?: number
} }
export enum MouseEventKind { export enum MouseEventKind {
CLICK = "CLICK", CLICK = 'CLICK',
CONTEXT_MENU = "CONTEXT_MENU", CONTEXT_MENU = 'CONTEXT_MENU',
} }
export interface LayoutMouseEvent { export interface LayoutMouseEvent {
kind: MouseEventKind; kind: MouseEventKind
event: MouseEvent; event: MouseEvent
panel?: IGroupPanel; panel?: IGroupPanel
tab?: boolean; tab?: boolean
} }

View File

@ -1,22 +1,22 @@
.groupview { .groupview {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 100%; height: 100%;
background-color: var(--group-view-background-color); background-color: var(--group-view-background-color);
&:focus { &:focus {
outline: none; outline: none;
}
&.empty {
.title-container {
display: none;
} }
}
.content-container { &.empty {
flex-grow: 1; .title-container {
overflow: hidden; display: none;
outline: none; }
} }
.content-container {
flex-grow: 1;
overflow: hidden;
outline: none;
}
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,76 +1,76 @@
import { IGroupview } from "../groupview"; import { IGroupview } from '../groupview'
import { Emitter, Event } from "../../events"; import { Emitter, Event } from '../../events'
import { ClosePanelResult } from "./parts"; import { ClosePanelResult } from './parts'
import { IGroupPanel } from "./types"; import { IGroupPanel } from './types'
import { IBaseViewApi, BaseViewApi } from "../../panel/api"; import { IBaseViewApi, BaseViewApi } from '../../panel/api'
interface ChangeVisibilityEvent { interface ChangeVisibilityEvent {
isVisible: boolean; isVisible: boolean
} }
export interface IGroupPanelApi extends IBaseViewApi { export interface IGroupPanelApi extends IBaseViewApi {
// events // events
onDidDirtyChange: Event<boolean>; onDidDirtyChange: Event<boolean>
onDidChangeVisibility: Event<ChangeVisibilityEvent>; onDidChangeVisibility: Event<ChangeVisibilityEvent>
// misc // misc
readonly isVisible: boolean; readonly isVisible: boolean
group: IGroupview; group: IGroupview
close: () => Promise<boolean>; close: () => Promise<boolean>
canClose: () => Promise<ClosePanelResult>; canClose: () => Promise<ClosePanelResult>
setClosePanelHook(callback: () => Promise<ClosePanelResult>): void; setClosePanelHook(callback: () => Promise<ClosePanelResult>): void
} }
export class GroupPanelApi extends BaseViewApi implements IGroupPanelApi { export class GroupPanelApi extends BaseViewApi implements IGroupPanelApi {
private _isVisible: boolean; private _isVisible: boolean
private _group: IGroupview; private _group: IGroupview
private _closePanelCallback: () => Promise<ClosePanelResult>; private _closePanelCallback: () => Promise<ClosePanelResult>
readonly _onDidDirtyChange = new Emitter<boolean>(); readonly _onDidDirtyChange = new Emitter<boolean>()
readonly onDidDirtyChange = this._onDidDirtyChange.event; readonly onDidDirtyChange = this._onDidDirtyChange.event
readonly _onDidChangeVisibility = new Emitter<ChangeVisibilityEvent>({ readonly _onDidChangeVisibility = new Emitter<ChangeVisibilityEvent>({
emitLastValue: true, emitLastValue: true,
}); })
readonly onDidChangeVisibility: Event<ChangeVisibilityEvent> = this readonly onDidChangeVisibility: Event<ChangeVisibilityEvent> = this
._onDidChangeVisibility.event; ._onDidChangeVisibility.event
get isVisible() { get isVisible() {
return this._isVisible; return this._isVisible
} }
get canClose() { get canClose() {
return this._closePanelCallback; return this._closePanelCallback
} }
set group(value: IGroupview) { set group(value: IGroupview) {
this._group = value; this._group = value
} }
get group() { get group() {
return this._group; return this._group
} }
constructor(private panel: IGroupPanel, group: IGroupview) { constructor(private panel: IGroupPanel, group: IGroupview) {
super(); super()
this._group = group; this._group = group
this.addDisposables( this.addDisposables(
this._onDidChangeVisibility, this._onDidChangeVisibility,
this._onDidDirtyChange, this._onDidDirtyChange,
this.onDidChangeVisibility((event) => { this.onDidChangeVisibility((event) => {
this._isVisible = event.isVisible; this._isVisible = event.isVisible
}) })
); )
} }
public close() { public close() {
return this.group.closePanel(this.panel); return this.group.closePanel(this.panel)
} }
public setClosePanelHook(callback: () => Promise<ClosePanelResult>) { public setClosePanelHook(callback: () => Promise<ClosePanelResult>) {
this._closePanelCallback = callback; this._closePanelCallback = callback
} }
public dispose() { public dispose() {
super.dispose(); super.dispose()
} }
} }

View File

@ -1,54 +1,55 @@
import { CompositeDisposable, IDisposable } from "../../../lifecycle"; import { CompositeDisposable, IDisposable } from '../../../lifecycle'
import { Emitter, Event } from "../../../events"; import { Emitter, Event } from '../../../events'
import { trackFocus } from "../../../dom"; import { trackFocus } from '../../../dom'
export interface IContentContainer extends IDisposable { export interface IContentContainer extends IDisposable {
onDidFocus: Event<void>; onDidFocus: Event<void>
element: HTMLElement; element: HTMLElement
openPanel: (panel: HTMLElement) => void; openPanel: (panel: HTMLElement) => void
closePanel: () => void; closePanel: () => void
} }
export class ContentContainer extends CompositeDisposable export class ContentContainer
implements IContentContainer { extends CompositeDisposable
private _element: HTMLElement; implements IContentContainer {
private content: HTMLElement; private _element: HTMLElement
private content: HTMLElement
private readonly _onDidFocus = new Emitter<void>(); private readonly _onDidFocus = new Emitter<void>()
readonly onDidFocus: Event<void> = this._onDidFocus.event; readonly onDidFocus: Event<void> = this._onDidFocus.event
get element() { get element() {
return this._element; return this._element
}
constructor() {
super();
this._element = document.createElement("div");
this._element.className = "content-container";
this._element.tabIndex = -1;
const { onDidBlur, onDidFocus } = trackFocus(this._element);
this.addDisposables(onDidFocus(() => this._onDidFocus.fire()));
}
public openPanel(panel: HTMLElement) {
if (this.content) {
this._element.removeChild(this.content);
this.content = undefined;
} }
this.content = panel;
this._element.appendChild(this.content);
}
public closePanel() { constructor() {
if (this.content) { super()
this._element.removeChild(this.content); this._element = document.createElement('div')
this.content = undefined; this._element.className = 'content-container'
this._element.tabIndex = -1
const { onDidBlur, onDidFocus } = trackFocus(this._element)
this.addDisposables(onDidFocus(() => this._onDidFocus.fire()))
} }
}
public dispose() { public openPanel(panel: HTMLElement) {
super.dispose(); if (this.content) {
} this._element.removeChild(this.content)
this.content = undefined
}
this.content = panel
this._element.appendChild(this.content)
}
public closePanel() {
if (this.content) {
this._element.removeChild(this.content)
this.content = undefined
}
}
public dispose() {
super.dispose()
}
} }

View File

@ -1,141 +1,141 @@
import { IGroupPanel, PanelInitParameters } from "./types"; import { IGroupPanel, PanelInitParameters } from './types'
import { GroupPanelApi } from "./api"; import { GroupPanelApi } from './api'
import { Event } from "../../events"; import { Event } from '../../events'
import { IGroupview, GroupChangeKind } from "../groupview"; import { IGroupview, GroupChangeKind } from '../groupview'
import { MutableDisposable, CompositeDisposable } from "../../lifecycle"; import { MutableDisposable, CompositeDisposable } from '../../lifecycle'
import { PanelContentPart, PanelHeaderPart, ClosePanelResult } from "./parts"; import { PanelContentPart, PanelHeaderPart, ClosePanelResult } from './parts'
import { PanelUpdateEvent } from "../../panel/types"; import { PanelUpdateEvent } from '../../panel/types'
export class DefaultPanel extends CompositeDisposable implements IGroupPanel { export class DefaultPanel extends CompositeDisposable implements IGroupPanel {
private readonly mutableDisposable = new MutableDisposable(); private readonly mutableDisposable = new MutableDisposable()
private readonly api: GroupPanelApi; private readonly api: GroupPanelApi
private _group: IGroupview; private _group: IGroupview
private params: PanelInitParameters; private params: PanelInitParameters
readonly onDidStateChange: Event<any>; readonly onDidStateChange: Event<any>
get group() { get group() {
return this._group; return this._group
}
get header() {
return this.headerPart;
}
get content() {
return this.contentPart;
}
constructor(
public readonly id: string,
private readonly headerPart: PanelHeaderPart,
private readonly contentPart: PanelContentPart
) {
super();
this.api = new GroupPanelApi(this, this._group);
this.onDidStateChange = this.api.onDidStateChange;
}
public setDirty(isDirty: boolean) {
this.api._onDidDirtyChange.fire(isDirty);
}
public close(): Promise<ClosePanelResult> {
if (this.api.canClose) {
return this.api.canClose();
} }
return Promise.resolve(ClosePanelResult.CLOSE); get header() {
} return this.headerPart
public toJSON(): object {
return {
id: this.id,
content: this.contentPart.toJSON(),
tab: this.headerPart.toJSON(),
props: this.params.params,
title: this.params.title,
suppressClosable: this.params.suppressClosable,
state: this.api.getState(),
};
}
public fromJSON(data: object) {
//
}
public update(params: PanelUpdateEvent): void {
this.params.params = { ...this.params.params, ...params };
this.contentPart.update(params.params);
this.api._onDidStateChange.fire();
}
public init(params: PanelInitParameters): void {
this.params = params;
this.api.setState(this.params.state);
if (this.content.init) {
this.content.init({ ...params, api: this.api });
} }
if (this.header.init) {
this.header.init({ ...params, api: this.api }); get content() {
return this.contentPart
} }
}
public onHide() { constructor(
// public readonly id: string,
} private readonly headerPart: PanelHeaderPart,
private readonly contentPart: PanelContentPart
) {
super()
public focus() { this.api = new GroupPanelApi(this, this._group)
// this.onDidStateChange = this.api.onDidStateChange
} }
public setVisible(isGroupActive: boolean, group: IGroupview) { public setDirty(isDirty: boolean) {
this._group = group; this.api._onDidDirtyChange.fire(isDirty)
this.api.group = group; }
this.mutableDisposable.value = this._group.onDidGroupChange((ev) => { public close(): Promise<ClosePanelResult> {
if (ev.kind === GroupChangeKind.GROUP_ACTIVE) { if (this.api.canClose) {
return this.api.canClose()
}
return Promise.resolve(ClosePanelResult.CLOSE)
}
public toJSON(): object {
return {
id: this.id,
content: this.contentPart.toJSON(),
tab: this.headerPart.toJSON(),
props: this.params.params,
title: this.params.title,
suppressClosable: this.params.suppressClosable,
state: this.api.getState(),
}
}
public fromJSON(data: object) {
//
}
public update(params: PanelUpdateEvent): void {
this.params.params = { ...this.params.params, ...params }
this.contentPart.update(params.params)
this.api._onDidStateChange.fire()
}
public init(params: PanelInitParameters): void {
this.params = params
this.api.setState(this.params.state)
if (this.content.init) {
this.content.init({ ...params, api: this.api })
}
if (this.header.init) {
this.header.init({ ...params, api: this.api })
}
}
public onHide() {
//
}
public focus() {
//
}
public setVisible(isGroupActive: boolean, group: IGroupview) {
this._group = group
this.api.group = group
this.mutableDisposable.value = this._group.onDidGroupChange((ev) => {
if (ev.kind === GroupChangeKind.GROUP_ACTIVE) {
this.api._onDidChangeVisibility.fire({
isVisible: this._group.isPanelActive(this),
})
}
})
this.api._onDidChangeFocus.fire({ isFocused: isGroupActive })
this.api._onDidChangeVisibility.fire({ this.api._onDidChangeVisibility.fire({
isVisible: this._group.isPanelActive(this), isVisible: this._group.isPanelActive(this),
}); })
}
});
this.api._onDidChangeFocus.fire({ isFocused: isGroupActive }); if (this.headerPart.setVisible) {
this.api._onDidChangeVisibility.fire({ this.headerPart.setVisible(
isVisible: this._group.isPanelActive(this), this._group.isPanelActive(this),
}); isGroupActive
)
if (this.headerPart.setVisible) { }
this.headerPart.setVisible( if (this.contentPart.setVisible) {
this._group.isPanelActive(this), this.contentPart.setVisible(
isGroupActive this._group.isPanelActive(this),
); isGroupActive
)
}
} }
if (this.contentPart.setVisible) {
this.contentPart.setVisible( public layout(width: number, height: number) {
this._group.isPanelActive(this), // thw height of the panel excluded the height of the title/tab
isGroupActive this.api._onDidPanelDimensionChange.fire({
); width,
height: height - (this.group?.tabHeight || 0),
})
} }
}
public layout(width: number, height: number) { public dispose() {
// thw height of the panel excluded the height of the title/tab this.api.dispose()
this.api._onDidPanelDimensionChange.fire({ this.mutableDisposable.dispose()
width,
height: height - (this.group?.tabHeight || 0),
});
}
public dispose() { this.headerPart.dispose()
this.api.dispose(); this.contentPart.dispose()
this.mutableDisposable.dispose(); }
this.headerPart.dispose();
this.contentPart.dispose();
}
} }

View File

@ -1,57 +1,57 @@
import { IDisposable } from "../../lifecycle"; import { IDisposable } from '../../lifecycle'
import { IGroupview } from "../groupview"; import { IGroupview } from '../groupview'
import { IGroupAccessor } from "../../layout"; import { IGroupAccessor } from '../../layout'
import { IGroupPanelApi } from "./api"; import { IGroupPanelApi } from './api'
import { PanelInitParameters } from "./types"; import { PanelInitParameters } from './types'
import { Constructor } from "../../types"; import { Constructor } from '../../types'
export enum ClosePanelResult { export enum ClosePanelResult {
CLOSE = "CLOSE", CLOSE = 'CLOSE',
DONT_CLOSE = "DONT_CLOSE", DONT_CLOSE = 'DONT_CLOSE',
} }
interface BasePart extends IDisposable { interface BasePart extends IDisposable {
init?(params: PartInitParameters): void; init?(params: PartInitParameters): void
setVisible(isPanelVisible: boolean, isGroupVisible: boolean): void; setVisible(isPanelVisible: boolean, isGroupVisible: boolean): void
} }
export interface WatermarkPartInitParameters { export interface WatermarkPartInitParameters {
accessor: IGroupAccessor; accessor: IGroupAccessor
} }
export interface PartInitParameters extends PanelInitParameters { export interface PartInitParameters extends PanelInitParameters {
api: IGroupPanelApi; api: IGroupPanelApi
} }
export interface PanelHeaderPart extends BasePart { export interface PanelHeaderPart extends BasePart {
id: string; id: string
element: HTMLElement; element: HTMLElement
layout?(height: string): void; layout?(height: string): void
toJSON(): {}; toJSON(): {}
} }
export interface PanelContentPart extends BasePart { export interface PanelContentPart extends BasePart {
id: string; id: string
element: HTMLElement; element: HTMLElement
layout?(width: number, height: number): void; layout?(width: number, height: number): void
close?(): Promise<ClosePanelResult>; close?(): Promise<ClosePanelResult>
focus(): void; focus(): void
onHide(): void; onHide(): void
update(params: {}): void; update(params: {}): void
toJSON(): {}; toJSON(): {}
} }
export interface WatermarkPart extends IDisposable { export interface WatermarkPart extends IDisposable {
init?: (params: WatermarkPartInitParameters) => void; init?: (params: WatermarkPartInitParameters) => void
setVisible?(visible: boolean, group: IGroupview): void; setVisible?(visible: boolean, group: IGroupview): void
element: HTMLElement; element: HTMLElement
} }
// constructors // constructors
export interface PanelHeaderPartConstructor export interface PanelHeaderPartConstructor
extends Constructor<PanelHeaderPart> {} extends Constructor<PanelHeaderPart> {}
export interface PanelContentPartConstructor export interface PanelContentPartConstructor
extends Constructor<PanelContentPart> {} extends Constructor<PanelContentPart> {}
export interface WatermarkConstructor extends Constructor<WatermarkPart> {} export interface WatermarkConstructor extends Constructor<WatermarkPart> {}

View File

@ -1,155 +1,161 @@
import { addDisposableListener, Emitter, Event } from "../../../events"; import { addDisposableListener, Emitter, Event } from '../../../events'
import { Droptarget, DroptargetEvent } from "../../droptarget/droptarget"; import { Droptarget, DroptargetEvent } from '../../droptarget/droptarget'
import { CompositeDisposable } from "../../../lifecycle"; import { CompositeDisposable } from '../../../lifecycle'
import { IGroupview } from "../../groupview"; import { IGroupview } from '../../groupview'
import { import {
DataTransferSingleton, DataTransferSingleton,
DATA_KEY, DATA_KEY,
DragType, DragType,
} from "../../droptarget/dataTransfer"; } from '../../droptarget/dataTransfer'
import { toggleClass } from "../../../dom"; import { toggleClass } from '../../../dom'
import { IGroupAccessor } from "../../../layout"; import { IGroupAccessor } from '../../../layout'
import {LayoutMouseEvent, MouseEventKind} from "../../events" import { LayoutMouseEvent, MouseEventKind } from '../../events'
export interface ITab { export interface ITab {
id: string; id: string
element: HTMLElement; element: HTMLElement
hasActiveDragEvent: boolean; hasActiveDragEvent: boolean
setContent: (element: HTMLElement) => void; setContent: (element: HTMLElement) => void
onChanged: Event<LayoutMouseEvent>; onChanged: Event<LayoutMouseEvent>
onDropped: Event<DroptargetEvent>; onDropped: Event<DroptargetEvent>
setActive(isActive: boolean): void; setActive(isActive: boolean): void
startDragEvent(): void; startDragEvent(): void
stopDragEvent(): void; stopDragEvent(): void
} }
export class Tab extends CompositeDisposable implements ITab { export class Tab extends CompositeDisposable implements ITab {
private _element: HTMLElement; private _element: HTMLElement
private dragInPlayDetails: { id?: string; isDragging: boolean } = { private dragInPlayDetails: { id?: string; isDragging: boolean } = {
isDragging: false, isDragging: false,
};
private droptarget: Droptarget;
private content: HTMLElement;
private readonly _onChanged = new Emitter<LayoutMouseEvent>();
readonly onChanged: Event<LayoutMouseEvent> = this._onChanged.event;
private readonly _onDropped = new Emitter<DroptargetEvent>();
readonly onDropped: Event<DroptargetEvent> = this._onDropped.event;
public get element() {
return this._element;
}
public get hasActiveDragEvent() {
return this.dragInPlayDetails?.isDragging;
}
public startDragEvent() {
this.dragInPlayDetails = { isDragging: true, id: this.accessor.id };
}
public stopDragEvent() {
this.dragInPlayDetails = { isDragging: false, id: undefined };
}
constructor(
public id: string,
private readonly accessor: IGroupAccessor,
private group: IGroupview
) {
super();
this.addDisposables(this._onChanged, this._onDropped);
this._element = document.createElement("div");
this._element.className = "tab";
this._element.draggable = true;
this.addDisposables(
addDisposableListener(this._element, "mousedown", (event) => {
if (event.defaultPrevented) {
return;
}
this._onChanged.fire({ kind: MouseEventKind.CLICK,event });
}),
addDisposableListener(this._element, "contextmenu", (event) => {
this._onChanged.fire({ kind: MouseEventKind.CONTEXT_MENU, event });
}),
addDisposableListener(this._element, "dragstart", (event) => {
this.dragInPlayDetails = { isDragging: true, id: this.accessor.id };
// set up a custom ghost image
const dragImage = this._element.cloneNode(true) as HTMLElement;
const box = this._element.getBoundingClientRect();
// if the style of the tab is determined by CSS by a parent element that style will lost
// therefore we must explicility re-add the style features that we know will be lost
dragImage.style.height = `${box.height}px`;
dragImage.style.width = `${box.width}px`;
dragImage.style.position = "absolute";
dragImage.classList.add("dragging");
document.body.appendChild(dragImage);
event.dataTransfer.setDragImage(
dragImage,
event.offsetX,
event.offsetY
);
setTimeout(() => document.body.removeChild(dragImage), 0);
// configure the data-transfer object
const data = JSON.stringify({
type: DragType.ITEM,
itemId: this.id,
groupId: this.group.id,
});
DataTransferSingleton.setData(this.dragInPlayDetails.id, data);
event.dataTransfer.setData(DATA_KEY, data);
event.dataTransfer.effectAllowed = "move";
}),
addDisposableListener(this._element, "dragend", (ev) => {
// drop events fire before dragend so we can remove this safely
DataTransferSingleton.removeData(this.dragInPlayDetails.id);
this.dragInPlayDetails = {
isDragging: false,
id: undefined,
};
})
);
this.droptarget = new Droptarget(this._element, {
isDirectional: false,
isDisabled: () => this.dragInPlayDetails.isDragging,
id: this.accessor.id,
enableExternalDragEvents: this.accessor.options.enableExternalDragEvents,
});
this.addDisposables(
this.droptarget.onDidChange((event) => {
this._onDropped.fire(event);
})
);
}
public setActive(isActive: boolean) {
toggleClass(this.element, "active-tab", isActive);
toggleClass(this.element, "inactive-tab", !isActive);
}
public setContent(element: HTMLElement) {
if (this.content) {
this._element.removeChild(this.content);
} }
this.content = element; private droptarget: Droptarget
this._element.appendChild(this.content); private content: HTMLElement
}
public dispose() { private readonly _onChanged = new Emitter<LayoutMouseEvent>()
super.dispose(); readonly onChanged: Event<LayoutMouseEvent> = this._onChanged.event
this.droptarget.dispose();
} private readonly _onDropped = new Emitter<DroptargetEvent>()
readonly onDropped: Event<DroptargetEvent> = this._onDropped.event
public get element() {
return this._element
}
public get hasActiveDragEvent() {
return this.dragInPlayDetails?.isDragging
}
public startDragEvent() {
this.dragInPlayDetails = { isDragging: true, id: this.accessor.id }
}
public stopDragEvent() {
this.dragInPlayDetails = { isDragging: false, id: undefined }
}
constructor(
public id: string,
private readonly accessor: IGroupAccessor,
private group: IGroupview
) {
super()
this.addDisposables(this._onChanged, this._onDropped)
this._element = document.createElement('div')
this._element.className = 'tab'
this._element.draggable = true
this.addDisposables(
addDisposableListener(this._element, 'mousedown', (event) => {
if (event.defaultPrevented) {
return
}
this._onChanged.fire({ kind: MouseEventKind.CLICK, event })
}),
addDisposableListener(this._element, 'contextmenu', (event) => {
this._onChanged.fire({
kind: MouseEventKind.CONTEXT_MENU,
event,
})
}),
addDisposableListener(this._element, 'dragstart', (event) => {
this.dragInPlayDetails = {
isDragging: true,
id: this.accessor.id,
}
// set up a custom ghost image
const dragImage = this._element.cloneNode(true) as HTMLElement
const box = this._element.getBoundingClientRect()
// if the style of the tab is determined by CSS by a parent element that style will lost
// therefore we must explicility re-add the style features that we know will be lost
dragImage.style.height = `${box.height}px`
dragImage.style.width = `${box.width}px`
dragImage.style.position = 'absolute'
dragImage.classList.add('dragging')
document.body.appendChild(dragImage)
event.dataTransfer.setDragImage(
dragImage,
event.offsetX,
event.offsetY
)
setTimeout(() => document.body.removeChild(dragImage), 0)
// configure the data-transfer object
const data = JSON.stringify({
type: DragType.ITEM,
itemId: this.id,
groupId: this.group.id,
})
DataTransferSingleton.setData(this.dragInPlayDetails.id, data)
event.dataTransfer.setData(DATA_KEY, data)
event.dataTransfer.effectAllowed = 'move'
}),
addDisposableListener(this._element, 'dragend', (ev) => {
// drop events fire before dragend so we can remove this safely
DataTransferSingleton.removeData(this.dragInPlayDetails.id)
this.dragInPlayDetails = {
isDragging: false,
id: undefined,
}
})
)
this.droptarget = new Droptarget(this._element, {
isDirectional: false,
isDisabled: () => this.dragInPlayDetails.isDragging,
id: this.accessor.id,
enableExternalDragEvents: this.accessor.options
.enableExternalDragEvents,
})
this.addDisposables(
this.droptarget.onDidChange((event) => {
this._onDropped.fire(event)
})
)
}
public setActive(isActive: boolean) {
toggleClass(this.element, 'active-tab', isActive)
toggleClass(this.element, 'inactive-tab', !isActive)
}
public setContent(element: HTMLElement) {
if (this.content) {
this._element.removeChild(this.content)
}
this.content = element
this._element.appendChild(this.content)
}
public dispose() {
super.dispose()
this.droptarget.dispose()
}
} }

View File

@ -1,14 +1,14 @@
import { IGroupview } from "../groupview"; import { IGroupview } from '../groupview'
import { IDisposable, ISerializable } from "../../lifecycle"; import { IDisposable, ISerializable } from '../../lifecycle'
import { Event } from "../../events"; import { Event } from '../../events'
import { PanelHeaderPart, PanelContentPart, ClosePanelResult } from "./parts"; import { PanelHeaderPart, PanelContentPart, ClosePanelResult } from './parts'
import { InitParameters, IPanel } from "../../panel/types"; import { InitParameters, IPanel } from '../../panel/types'
// init parameters // init parameters
export interface PanelInitParameters extends InitParameters { export interface PanelInitParameters extends InitParameters {
title: string; title: string
suppressClosable?: boolean; suppressClosable?: boolean
} }
// constructors // constructors
@ -16,15 +16,15 @@ export interface PanelInitParameters extends InitParameters {
// panel // panel
export interface IGroupPanel extends IDisposable, ISerializable, IPanel { export interface IGroupPanel extends IDisposable, ISerializable, IPanel {
id: string; id: string
header: PanelHeaderPart; header: PanelHeaderPart
content: PanelContentPart; content: PanelContentPart
group: IGroupview; group: IGroupview
focus(): void; focus(): void
onHide(): void; onHide(): void
setVisible(isGroupActive: boolean, group: IGroupview): void; setVisible(isGroupActive: boolean, group: IGroupview): void
setDirty(isDirty: boolean): void; setDirty(isDirty: boolean): void
close?(): Promise<ClosePanelResult>; close?(): Promise<ClosePanelResult>
init?(params: PanelInitParameters & { [index: string]: string }): void; init?(params: PanelInitParameters & { [index: string]: string }): void
onDidStateChange: Event<any>; onDidStateChange: Event<any>
} }

View File

@ -1,57 +1,57 @@
.title-container { .title-container {
display: flex;
background-color: var(--title-bar-background-color);
overflow: overlay;
flex-shrink: 0;
box-sizing: border-box;
&.hidden {
display: none;
}
&::-webkit-scrollbar {
height: 3px;
}
/* Track */
&::-webkit-scrollbar-track {
background: transparent;
}
/* Handle */
&::-webkit-scrollbar-thumb {
background: var(--title-bar-scroll-bar-color);
}
.tab-container {
flex-shrink: 0;
flex-grow: 1;
display: flex; display: flex;
font-size: 13px; background-color: var(--title-bar-background-color);
overflow-x: overlay; overflow: overlay;
overflow-y: hidden; flex-shrink: 0;
box-sizing: border-box;
&.drag-over-target { &.hidden {
background-color: var(--drag-over-background-color); display: none;
} }
.tab { &::-webkit-scrollbar {
min-width: 75px; height: 3px;
cursor: pointer; }
position: relative;
box-sizing: border-box; /* Track */
&::-webkit-scrollbar-track {
&:not(:first-child)::before { background: transparent;
content: " "; }
position: absolute;
top: 0; /* Handle */
left: 0; &::-webkit-scrollbar-thumb {
z-index: 5; background: var(--title-bar-scroll-bar-color);
pointer-events: none; }
background-color: var(--tab-divider-color);
width: 1px; .tab-container {
height: 100%; flex-shrink: 0;
} flex-grow: 1;
display: flex;
font-size: 13px;
overflow-x: overlay;
overflow-y: hidden;
&.drag-over-target {
background-color: var(--drag-over-background-color);
}
.tab {
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(--tab-divider-color);
width: 1px;
height: 100%;
}
}
} }
}
} }

View File

@ -1,243 +1,252 @@
import { import {
IDisposable, IDisposable,
CompositeDisposable, CompositeDisposable,
IValueDisposable, IValueDisposable,
} from "../../lifecycle"; } from '../../lifecycle'
import { addDisposableListener, Emitter, Event } from "../../events"; import { addDisposableListener, Emitter, Event } from '../../events'
import { ITab, Tab } from "../panel/tab/tab"; import { ITab, Tab } from '../panel/tab/tab'
import { removeClasses, addClasses, toggleClass } from "../../dom"; import { removeClasses, addClasses, toggleClass } from '../../dom'
import { hasProcessed, Position } from "../droptarget/droptarget"; import { hasProcessed, Position } from '../droptarget/droptarget'
import { TabDropEvent } from "../events"; import { TabDropEvent } from '../events'
import { IGroupview } from "../groupview"; import { IGroupview } from '../groupview'
import { IGroupAccessor } from "../../layout"; import { IGroupAccessor } from '../../layout'
import { last } from "../../array"; import { last } from '../../array'
import { DataTransferSingleton } from "../droptarget/dataTransfer"; import { DataTransferSingleton } from '../droptarget/dataTransfer'
import { IGroupPanel } from "../panel/types"; import { IGroupPanel } from '../panel/types'
import {MouseEventKind} from "../events" import { MouseEventKind } from '../events'
export interface ITabContainer extends IDisposable { export interface ITabContainer extends IDisposable {
element: HTMLElement; element: HTMLElement
visible: boolean; visible: boolean
height: number; height: number
hasActiveDragEvent: boolean; hasActiveDragEvent: boolean
delete: (id: string) => void; delete: (id: string) => void
indexOf: (tabOrId: ITab | string) => number; indexOf: (tabOrId: ITab | string) => number
at: (index: number) => ITab; at: (index: number) => ITab
onDropEvent: Event<TabDropEvent>; onDropEvent: Event<TabDropEvent>
setActive: (isGroupActive: boolean) => void; setActive: (isGroupActive: boolean) => void
setActivePanel: (panel: IGroupPanel) => void; setActivePanel: (panel: IGroupPanel) => void
isActive: (tab: ITab) => boolean; isActive: (tab: ITab) => boolean
closePanel: (panel: IGroupPanel) => void; closePanel: (panel: IGroupPanel) => void
openPanel: (panel: IGroupPanel, index?: number) => void; openPanel: (panel: IGroupPanel, index?: number) => void
} }
export class TabContainer extends CompositeDisposable implements ITabContainer { export class TabContainer extends CompositeDisposable implements ITabContainer {
private tabContainer: HTMLElement; private tabContainer: HTMLElement
private _element: HTMLElement; private _element: HTMLElement
private actionContainer: HTMLElement; private actionContainer: HTMLElement
private tabs: IValueDisposable<ITab>[] = []; private tabs: IValueDisposable<ITab>[] = []
private selectedIndex: number = -1; private selectedIndex: number = -1
private active: boolean; private active: boolean
private activePanel: IGroupPanel; private activePanel: IGroupPanel
private _visible: boolean = true; private _visible: boolean = true
private _height: number; private _height: number
private readonly _onDropped = new Emitter<TabDropEvent>(); private readonly _onDropped = new Emitter<TabDropEvent>()
readonly onDropEvent: Event<TabDropEvent> = this._onDropped.event; readonly onDropEvent: Event<TabDropEvent> = this._onDropped.event
get visible() { get visible() {
return this._visible; return this._visible
}
set visible(value: boolean) {
this._visible = value;
toggleClass(this.element, "hidden", !this._visible);
}
get height() {
return this._height;
}
set height(value: number) {
this._height = value;
this._element.style.height = `${this.height}px`;
}
public get element() {
return this._element;
}
public isActive(tab: ITab) {
return (
this.selectedIndex > -1 && this.tabs[this.selectedIndex].value === tab
);
}
public get hasActiveDragEvent() {
return !!this.tabs.find((tab) => tab.value.hasActiveDragEvent);
}
public at(index: number) {
return this.tabs[index]?.value;
}
public indexOf(tabOrId: ITab) {
const id = typeof tabOrId === "string" ? tabOrId : tabOrId.id;
return this.tabs.findIndex((tab) => tab.value.id === id);
}
constructor(private accessor: IGroupAccessor, private group: IGroupview) {
super();
this.addDisposables(this._onDropped);
this._element = document.createElement("div");
this._element.className = "title-container";
this.height = 35;
this.actionContainer = document.createElement("div");
this.actionContainer.className = "action-container";
const list = document.createElement("ul");
list.className = "action-list";
this.tabContainer = document.createElement("div");
this.tabContainer.className = "tab-container";
this._element.appendChild(this.tabContainer);
this._element.appendChild(this.actionContainer);
this.addDisposables(
addDisposableListener(this.tabContainer, "dragenter", (event) => {
if (!DataTransferSingleton.has(this.accessor.id)) {
console.debug("[tabs] invalid drop event");
return;
}
if (!last(this.tabs).value.hasActiveDragEvent) {
addClasses(this.tabContainer, "drag-over-target");
}
}),
addDisposableListener(this.tabContainer, "dragover", (event) => {
event.preventDefault();
}),
addDisposableListener(this.tabContainer, "dragleave", (event) => {
removeClasses(this.tabContainer, "drag-over-target");
}),
addDisposableListener(this.tabContainer, "drop", (event) => {
if (!DataTransferSingleton.has(this.accessor.id)) {
console.debug("[tabs] invalid drop event");
return;
}
if (hasProcessed(event)) {
console.debug("[tab] drop event already processed");
return;
}
removeClasses(this.tabContainer, "drag-over-target");
const activetab = this.tabs.find((tab) => tab.value.hasActiveDragEvent);
const ignore = !!(
activetab &&
event.composedPath().find((x) => activetab.value.element === x)
);
if (ignore) {
console.debug("[tabs] ignore event");
return;
}
this._onDropped.fire({
event: { event, position: Position.Center },
index: this.tabs.length - 1,
});
})
);
}
public setActive(isGroupActive: boolean) {
this.active = isGroupActive;
}
private addTab(
tab: IValueDisposable<ITab>,
index: number = this.tabs.length
) {
if (index < 0 || index > this.tabs.length) {
throw new Error("invalid location");
} }
this.tabContainer.insertBefore( set visible(value: boolean) {
tab.value.element, this._visible = value
this.tabContainer.children[index]
);
this.tabs = [...this.tabs.slice(0, index), tab, ...this.tabs.slice(index)]; toggleClass(this.element, 'hidden', !this._visible)
if (this.selectedIndex < 0) {
this.selectedIndex = index;
} }
}
public delete(id: string) { get height() {
const index = this.tabs.findIndex((tab) => tab.value.id === id); return this._height
const tab = this.tabs.splice(index, 1)[0];
const { value, disposable } = tab;
disposable.dispose();
value.element.remove();
}
public setActivePanel(panel: IGroupPanel) {
this.tabs.forEach((tab) => {
const isActivePanel = panel.id === tab.value.id;
tab.value.setActive(isActivePanel);
});
}
public openPanel(panel: IGroupPanel, index: number = this.tabs.length) {
if (this.tabs.find((tab) => tab.value.id === panel.id)) {
return;
} }
const tab = new Tab(panel.id, this.accessor, this.group);
tab.setContent(panel.header.element);
const disposable = CompositeDisposable.from( set height(value: number) {
tab.onChanged((event) => { this._height = value
switch (event.kind) { this._element.style.height = `${this.height}px`
case MouseEventKind.CLICK: }
this.group.openPanel(panel);
break; public get element() {
return this._element
}
public isActive(tab: ITab) {
return (
this.selectedIndex > -1 &&
this.tabs[this.selectedIndex].value === tab
)
}
public get hasActiveDragEvent() {
return !!this.tabs.find((tab) => tab.value.hasActiveDragEvent)
}
public at(index: number) {
return this.tabs[index]?.value
}
public indexOf(tabOrId: ITab) {
const id = typeof tabOrId === 'string' ? tabOrId : tabOrId.id
return this.tabs.findIndex((tab) => tab.value.id === id)
}
constructor(private accessor: IGroupAccessor, private group: IGroupview) {
super()
this.addDisposables(this._onDropped)
this._element = document.createElement('div')
this._element.className = 'title-container'
this.height = 35
this.actionContainer = document.createElement('div')
this.actionContainer.className = 'action-container'
const list = document.createElement('ul')
list.className = 'action-list'
this.tabContainer = document.createElement('div')
this.tabContainer.className = 'tab-container'
this._element.appendChild(this.tabContainer)
this._element.appendChild(this.actionContainer)
this.addDisposables(
addDisposableListener(this.tabContainer, 'dragenter', (event) => {
if (!DataTransferSingleton.has(this.accessor.id)) {
console.debug('[tabs] invalid drop event')
return
}
if (!last(this.tabs).value.hasActiveDragEvent) {
addClasses(this.tabContainer, 'drag-over-target')
}
}),
addDisposableListener(this.tabContainer, 'dragover', (event) => {
event.preventDefault()
}),
addDisposableListener(this.tabContainer, 'dragleave', (event) => {
removeClasses(this.tabContainer, 'drag-over-target')
}),
addDisposableListener(this.tabContainer, 'drop', (event) => {
if (!DataTransferSingleton.has(this.accessor.id)) {
console.debug('[tabs] invalid drop event')
return
}
if (hasProcessed(event)) {
console.debug('[tab] drop event already processed')
return
}
removeClasses(this.tabContainer, 'drag-over-target')
const activetab = this.tabs.find(
(tab) => tab.value.hasActiveDragEvent
)
const ignore = !!(
activetab &&
event
.composedPath()
.find((x) => activetab.value.element === x)
)
if (ignore) {
console.debug('[tabs] ignore event')
return
}
this._onDropped.fire({
event: { event, position: Position.Center },
index: this.tabs.length - 1,
})
})
)
}
public setActive(isGroupActive: boolean) {
this.active = isGroupActive
}
private addTab(
tab: IValueDisposable<ITab>,
index: number = this.tabs.length
) {
if (index < 0 || index > this.tabs.length) {
throw new Error('invalid location')
} }
this.accessor.fireMouseEvent({...event, panel, tab:true});
}),
tab.onDropped((event) => {
this._onDropped.fire({ event, index: this.indexOf(tab) });
})
);
const value: IValueDisposable<ITab> = { value: tab, disposable }; this.tabContainer.insertBefore(
tab.value.element,
this.tabContainer.children[index]
)
this.addTab(value, index); this.tabs = [
this.activePanel = panel; ...this.tabs.slice(0, index),
} tab,
...this.tabs.slice(index),
]
public closePanel(panel: IGroupPanel) { if (this.selectedIndex < 0) {
this.delete(panel.id); this.selectedIndex = index
} }
}
public dispose() { public delete(id: string) {
super.dispose(); const index = this.tabs.findIndex((tab) => tab.value.id === id)
this.tabs.forEach((tab) => { const tab = this.tabs.splice(index, 1)[0]
tab.disposable.dispose();
}); const { value, disposable } = tab
this.tabs = [];
} disposable.dispose()
value.element.remove()
}
public setActivePanel(panel: IGroupPanel) {
this.tabs.forEach((tab) => {
const isActivePanel = panel.id === tab.value.id
tab.value.setActive(isActivePanel)
})
}
public openPanel(panel: IGroupPanel, index: number = this.tabs.length) {
if (this.tabs.find((tab) => tab.value.id === panel.id)) {
return
}
const tab = new Tab(panel.id, this.accessor, this.group)
tab.setContent(panel.header.element)
const disposable = CompositeDisposable.from(
tab.onChanged((event) => {
switch (event.kind) {
case MouseEventKind.CLICK:
this.group.openPanel(panel)
break
}
this.accessor.fireMouseEvent({ ...event, panel, tab: true })
}),
tab.onDropped((event) => {
this._onDropped.fire({ event, index: this.indexOf(tab) })
})
)
const value: IValueDisposable<ITab> = { value: tab, disposable }
this.addTab(value, index)
this.activePanel = panel
}
public closePanel(panel: IGroupPanel) {
this.delete(panel.id)
}
public dispose() {
super.dispose()
this.tabs.forEach((tab) => {
tab.disposable.dispose()
})
this.tabs = []
}
} }

View File

@ -1,21 +1,21 @@
export * from "./splitview/splitview"; export * from './splitview/splitview'
export * from "./paneview/paneview"; export * from './paneview/paneview'
export * from "./gridview/gridview"; export * from './gridview/gridview'
export * from "./groupview/groupview"; export * from './groupview/groupview'
export * from "./groupview/panel/content/content"; export * from './groupview/panel/content/content'
export * from "./groupview/panel/tab/tab"; export * from './groupview/panel/tab/tab'
export * from "./events"; export * from './events'
export * from "./lifecycle"; export * from './lifecycle'
export * from "./groupview/panel/panel"; export * from './groupview/panel/panel'
export * from "./groupview/panel/api"; export * from './groupview/panel/api'
export * from "./react/react"; export * from './react/react'
export * from "./groupview/panel/types"; export * from './groupview/panel/types'
export * from "./groupview/panel/parts"; export * from './groupview/panel/parts'
export * from "./react/layout"; export * from './react/layout'
export * from "./react/splitview"; export * from './react/splitview'
export * from "./react/gridview"; export * from './react/gridview'
export * from "./react/reactContentPart"; export * from './react/reactContentPart'
export * from "./react/reactHeaderPart"; export * from './react/reactHeaderPart'
export * from "./react/reactComponentGridView"; export * from './react/reactComponentGridView'
export * from "./layout"; export * from './layout'

View File

@ -1,89 +1,89 @@
import { import {
PanelContentPart, PanelContentPart,
PanelContentPartConstructor, PanelContentPartConstructor,
PanelHeaderPart, PanelHeaderPart,
PanelHeaderPartConstructor, PanelHeaderPartConstructor,
} from "../groupview/panel/parts"; } from '../groupview/panel/parts'
import { FrameworkFactory } from "../types"; import { FrameworkFactory } from '../types'
import { DefaultTab } from "./components/tab/defaultTab"; import { DefaultTab } from './components/tab/defaultTab'
export function createContentComponent( export function createContentComponent(
componentName: string | PanelContentPartConstructor | any, componentName: string | PanelContentPartConstructor | any,
components: { components: {
[componentName: string]: PanelContentPartConstructor; [componentName: string]: PanelContentPartConstructor
}, },
frameworkComponents: { frameworkComponents: {
[componentName: string]: any; [componentName: string]: any
}, },
createFrameworkComponent: FrameworkFactory<PanelContentPart> createFrameworkComponent: FrameworkFactory<PanelContentPart>
): PanelContentPart { ): PanelContentPart {
const Component = const Component =
typeof componentName === "string" typeof componentName === 'string'
? components[componentName] ? components[componentName]
: componentName; : componentName
const FrameworkComponent = const FrameworkComponent =
typeof componentName === "string" typeof componentName === 'string'
? frameworkComponents[componentName] ? frameworkComponents[componentName]
: componentName; : componentName
if (Component && FrameworkComponent) { if (Component && FrameworkComponent) {
throw new Error( throw new Error(
`cannot register component ${componentName} as both a component and frameworkComponent` `cannot register component ${componentName} as both a component and frameworkComponent`
); )
}
if (FrameworkComponent) {
if (!createFrameworkComponent) {
throw new Error(
"you must register a frameworkPanelWrapper to use framework components"
);
} }
const wrappedComponent = createFrameworkComponent.createComponent( if (FrameworkComponent) {
componentName, if (!createFrameworkComponent) {
FrameworkComponent throw new Error(
); 'you must register a frameworkPanelWrapper to use framework components'
return wrappedComponent; )
} }
return new Component() as PanelContentPart; const wrappedComponent = createFrameworkComponent.createComponent(
componentName,
FrameworkComponent
)
return wrappedComponent
}
return new Component() as PanelContentPart
} }
export function createTabComponent( export function createTabComponent(
componentName: string | PanelHeaderPartConstructor | any, componentName: string | PanelHeaderPartConstructor | any,
components: { components: {
[componentName: string]: PanelHeaderPartConstructor; [componentName: string]: PanelHeaderPartConstructor
}, },
frameworkComponents: { frameworkComponents: {
[componentName: string]: any; [componentName: string]: any
}, },
createFrameworkComponent: FrameworkFactory<PanelHeaderPart> createFrameworkComponent: FrameworkFactory<PanelHeaderPart>
): PanelHeaderPart { ): PanelHeaderPart {
const Component = const Component =
typeof componentName === "string" typeof componentName === 'string'
? components[componentName] ? components[componentName]
: componentName; : componentName
const FrameworkComponent = const FrameworkComponent =
typeof componentName === "string" typeof componentName === 'string'
? frameworkComponents[componentName] ? frameworkComponents[componentName]
: componentName; : componentName
if (Component && FrameworkComponent) { if (Component && FrameworkComponent) {
throw new Error( throw new Error(
`cannot register component ${componentName} as both a component and frameworkComponent` `cannot register component ${componentName} as both a component and frameworkComponent`
); )
} }
if (FrameworkComponent) { if (FrameworkComponent) {
if (!createFrameworkComponent) { if (!createFrameworkComponent) {
throw new Error( throw new Error(
"you must register a frameworkPanelWrapper to use framework components" 'you must register a frameworkPanelWrapper to use framework components'
); )
}
const wrappedComponent = createFrameworkComponent.createComponent(
componentName,
FrameworkComponent
)
return wrappedComponent
} }
const wrappedComponent = createFrameworkComponent.createComponent(
componentName,
FrameworkComponent
);
return wrappedComponent;
}
if (!Component) { if (!Component) {
return new DefaultTab(); return new DefaultTab()
} }
return new Component() as PanelHeaderPart; return new Component() as PanelHeaderPart
} }

View File

@ -1,408 +1,410 @@
import { Gridview, getRelativeLocation, IGridView } from "../gridview/gridview"; import { Gridview, getRelativeLocation, IGridView } from '../gridview/gridview'
import { Position } from "../groupview/droptarget/droptarget"; import { Position } from '../groupview/droptarget/droptarget'
import { getGridLocation } from "../gridview/gridview"; import { getGridLocation } from '../gridview/gridview'
import { tail, sequenceEquals } from "../array"; import { tail, sequenceEquals } from '../array'
import { import {
GroupChangeKind, GroupChangeKind,
GroupChangeEvent, GroupChangeEvent,
GroupDropEvent, GroupDropEvent,
} from "../groupview/groupview"; } from '../groupview/groupview'
import { import { CompositeDisposable, Disposable, IValueDisposable } from '../lifecycle'
CompositeDisposable, import { Event, Emitter } from '../events'
Disposable,
IValueDisposable,
} from "../lifecycle";
import { Event, Emitter } from "../events";
import { DebugWidget } from "./components/debug/debug"; import { DebugWidget } from './components/debug/debug'
import { sequentialNumberGenerator } from "../math"; import { sequentialNumberGenerator } from '../math'
import { DefaultDeserializer, IPanelDeserializer } from "./deserializer"; import { IPanelDeserializer } from './deserializer'
import { MovementOptions } from "./options";
import { createComponent } from "../splitview/options"; import { createComponent } from '../splitview/options'
import { LayoutPriority, Orientation } from "../splitview/splitview"; import { LayoutPriority, Orientation } from '../splitview/splitview'
const nextGroupId = sequentialNumberGenerator(); const nextLayoutId = sequentialNumberGenerator()
const nextLayoutId = sequentialNumberGenerator();
export interface AddComponentOptions { export interface AddComponentOptions {
component: string; component: string
params?: { [key: string]: any }; params?: { [key: string]: any }
id: string; id: string
position?: { position?: {
direction?: "left" | "right" | "above" | "below" | "within"; direction?: 'left' | 'right' | 'above' | 'below' | 'within'
reference: string; reference: string
}; }
size?: number
size?: number; priority?: LayoutPriority
priority?: LayoutPriority; snap?: boolean
snap?: boolean;
} }
export interface GridComponentOptions { export interface GridComponentOptions {
orientation: Orientation; orientation: Orientation
components?: { components?: {
[componentName: string]: IComponentGridview; [componentName: string]: IComponentGridview
}; }
frameworkComponents?: { frameworkComponents?: {
[componentName: string]: any; [componentName: string]: any
}; }
frameworkComponentFactory: any; frameworkComponentFactory: any
tabHeight?: number; tabHeight?: number
} }
export interface IComponentGridview extends IGridView { export interface IComponentGridview extends IGridView {
id: string; id: string
init: (params: { params: any }) => void; init: (params: { params: any }) => void
priority?: LayoutPriority; priority?: LayoutPriority
} }
export interface MovementOptions2 { export interface MovementOptions2 {
group?: IComponentGridview; group?: IComponentGridview
} }
export interface IComponentGridviewLayout { export interface IComponentGridviewLayout {
addComponent(options: AddComponentOptions): void; addComponent(options: AddComponentOptions): void
} }
export class ComponentGridview export class ComponentGridview
extends CompositeDisposable extends CompositeDisposable
implements IComponentGridviewLayout { implements IComponentGridviewLayout {
private readonly _id = nextLayoutId.next(); private readonly _id = nextLayoutId.next()
private readonly groups = new Map< private readonly groups = new Map<
string, string,
IValueDisposable<IComponentGridview> IValueDisposable<IComponentGridview>
>(); >()
private readonly gridview: Gridview = new Gridview(false); private readonly gridview: Gridview = new Gridview(false)
// events // events
private readonly _onDidLayoutChange = new Emitter<GroupChangeEvent>(); private readonly _onDidLayoutChange = new Emitter<GroupChangeEvent>()
readonly onDidLayoutChange: Event<GroupChangeEvent> = this._onDidLayoutChange readonly onDidLayoutChange: Event<GroupChangeEvent> = this
.event; ._onDidLayoutChange.event
// everything else // everything else
private _size: number; private _size: number
private _orthogonalSize: number; private _orthogonalSize: number
private _activeGroup: IComponentGridview; private _activeGroup: IComponentGridview
private _deserializer: IPanelDeserializer; private _deserializer: IPanelDeserializer
private resizeTimer: NodeJS.Timer; private resizeTimer: NodeJS.Timer
private debugContainer: DebugWidget; private debugContainer: DebugWidget
constructor( constructor(
private readonly element: HTMLElement, private readonly element: HTMLElement,
public readonly options: GridComponentOptions public readonly options: GridComponentOptions
) { ) {
super(); super()
this.element.appendChild(this.gridview.element); this.element.appendChild(this.gridview.element)
if (!this.options.components) { if (!this.options.components) {
this.options.components = {}; this.options.components = {}
} }
if (!this.options.frameworkComponents) { if (!this.options.frameworkComponents) {
this.options.frameworkComponents = {}; this.options.frameworkComponents = {}
}
this.addDisposables(
this.gridview.onDidChange((e) => {
this._onDidLayoutChange.fire({ kind: GroupChangeKind.LAYOUT })
})
)
} }
this.addDisposables( get minimumHeight() {
this.gridview.onDidChange((e) => { return this.gridview.minimumHeight
this._onDidLayoutChange.fire({ kind: GroupChangeKind.LAYOUT });
})
);
}
get minimumHeight() {
return this.gridview.minimumHeight;
}
get maximumHeight() {
return this.gridview.maximumHeight;
}
get minimumWidth() {
return this.gridview.maximumWidth;
}
get maximumWidth() {
return this.gridview.maximumWidth;
}
get activeGroup() {
return this._activeGroup;
}
get deserializer() {
return this._deserializer;
}
set deserializer(value: IPanelDeserializer) {
this._deserializer = value;
}
get id() {
return this._id;
}
get size() {
return this.groups.size;
}
public moveToNext(options?: MovementOptions2) {
if (!options) {
options = {};
} }
if (!options.group) { get maximumHeight() {
options.group = this.activeGroup; return this.gridview.maximumHeight
}
get minimumWidth() {
return this.gridview.maximumWidth
}
get maximumWidth() {
return this.gridview.maximumWidth
} }
const location = getGridLocation(options.group.element); get activeGroup() {
const next = this.gridview.next(location)?.view as IComponentGridview; return this._activeGroup
this.doSetGroupActive(next);
}
public moveToPrevious(options?: MovementOptions2) {
if (!options) {
options = {};
}
if (!options.group) {
options.group = this.activeGroup;
} }
const location = getGridLocation(options.group.element); get deserializer() {
const next = this.gridview.preivous(location)?.view as IComponentGridview; return this._deserializer
this.doSetGroupActive(next);
}
/**
* Serialize the current state of the layout
*
* @returns A JSON respresentation of the layout
*/
public toJSON() {
const data = this.gridview.serialize();
return { grid: data };
}
public deserialize(data: any) {
this.gridview.clear();
this.groups.clear();
this.fromJSON(data, this.deserializer);
this.gridview.layout(this._size, this._orthogonalSize);
}
public fromJSON(data: any, deserializer: IPanelDeserializer) {
const { grid, panels } = data;
// this.gridview.deserialize(
// grid,
// new DefaultDeserializer(this, {
// createPanel: (id) => {
// const panelData = panels[id];
// const panel = deserializer.fromJSON(panelData);
// this.registerPanel(panel);
// return panel;
// },
// })
// );
this._onDidLayoutChange.fire({ kind: GroupChangeKind.NEW_LAYOUT });
}
public setAutoResizeToFit(enabled: boolean) {
if (this.resizeTimer) {
clearInterval(this.resizeTimer);
}
if (enabled) {
this.resizeTimer = setInterval(() => {
this.resizeToFit();
}, 500);
}
}
/**
* Resize the layout to fit the parent container
*/
public resizeToFit() {
const {
width,
height,
} = this.element.parentElement.getBoundingClientRect();
this.layout(width, height);
}
public addComponent(options: AddComponentOptions) {
let relativeLocation: number[] = [0];
if (options.position?.reference) {
const referenceGroup = this.groups.get(options.position.reference).value;
const target = this.toTarget(options.position.direction);
if (target === Position.Center) {
throw new Error(`${target} not supported as an option`);
} else {
const location = getGridLocation(referenceGroup.element);
relativeLocation = getRelativeLocation(
this.gridview.orientation,
location,
target
);
}
} }
const view = createComponent( set deserializer(value: IPanelDeserializer) {
options.component, this._deserializer = value
this.options.components,
this.options.frameworkComponents,
this.options.frameworkComponentFactory.createComponent
);
view.init({ params: {} });
view.priority = options.priority;
view.snap = options.snap;
this.groups.set(options.id, { value: view, disposable: Disposable.NONE });
this.doAddGroup(view, relativeLocation, options.size);
}
public getGroup(id: string) {
return this.groups.get(id)?.value;
}
public removeGroup(group: IComponentGridview) {
if (group === this._activeGroup) {
this._activeGroup = undefined;
}
this.doRemoveGroup(group);
}
private doAddGroup(
group: IComponentGridview,
location: number[],
size?: number
) {
this.gridview.addView(group, size ?? { type: "distribute" }, location);
this._onDidLayoutChange.fire({ kind: GroupChangeKind.ADD_GROUP });
this.doSetGroupActive(group);
}
private doRemoveGroup(
group: IComponentGridview,
options?: { skipActive?: boolean; skipDispose?: boolean }
) {
if (!this.groups.has(group.id)) {
throw new Error("invalid operation");
} }
const { disposable } = this.groups.get(group.id); get id() {
return this._id
if (!options?.skipDispose) {
disposable.dispose();
this.groups.delete(group.id);
} }
const view = this.gridview.remove(group, { type: "distribute" }); get size() {
this._onDidLayoutChange.fire({ kind: GroupChangeKind.REMOVE_GROUP }); return this.groups.size
if (!options?.skipActive && this.groups.size > 0) {
this.doSetGroupActive(Array.from(this.groups.values())[0].value);
} }
return view; public moveToNext(options?: MovementOptions2) {
} if (!options) {
options = {}
}
if (!options.group) {
options.group = this.activeGroup
}
public doSetGroupActive(group: IComponentGridview) { const location = getGridLocation(options.group.element)
if (this._activeGroup && this._activeGroup !== group) { const next = this.gridview.next(location)?.view as IComponentGridview
// this._activeGroup.setActive(false); this.doSetGroupActive(next)
}
// group.setActive(true);
this._activeGroup = group;
}
public moveGroup(
referenceGroup: IComponentGridview,
groupId: string,
itemId: string,
target: Position
) {
const sourceGroup = groupId ? this.groups.get(groupId).value : undefined;
const referenceLocation = getGridLocation(referenceGroup.element);
const targetLocation = getRelativeLocation(
this.gridview.orientation,
referenceLocation,
target
);
const [targetParentLocation, to] = tail(targetLocation);
const sourceLocation = getGridLocation(sourceGroup.element);
const [sourceParentLocation, from] = tail(sourceLocation);
if (sequenceEquals(sourceParentLocation, targetParentLocation)) {
// special case when 'swapping' two views within same grid location
// if a group has one tab - we are essentially moving the 'group'
// which is equivalent to swapping two views in this case
this.gridview.moveView(sourceParentLocation, from, to);
return;
} }
// source group will become empty so delete the group public moveToPrevious(options?: MovementOptions2) {
const targetGroup = this.doRemoveGroup(sourceGroup, { if (!options) {
skipActive: true, options = {}
skipDispose: true, }
}) as IComponentGridview; if (!options.group) {
options.group = this.activeGroup
}
// after deleting the group we need to re-evaulate the ref location const location = getGridLocation(options.group.element)
const updatedReferenceLocation = getGridLocation(referenceGroup.element); const next = this.gridview.preivous(location)
const location = getRelativeLocation( ?.view as IComponentGridview
this.gridview.orientation, this.doSetGroupActive(next)
updatedReferenceLocation,
target
);
this.doAddGroup(targetGroup, location);
}
public layout(size: number, orthogonalSize: number, force?: boolean) {
const different =
force || size !== this._size || orthogonalSize !== this._orthogonalSize;
if (!different) {
return;
} }
this.element.style.height = `${orthogonalSize}px`; /**
this.element.style.width = `${size}px`; * Serialize the current state of the layout
*
* @returns A JSON respresentation of the layout
*/
public toJSON() {
const data = this.gridview.serialize()
this._size = size; return { grid: data }
this._orthogonalSize = orthogonalSize;
this.gridview.layout(size, orthogonalSize);
}
private toTarget(direction: "left" | "right" | "above" | "below" | "within") {
switch (direction) {
case "left":
return Position.Left;
case "right":
return Position.Right;
case "above":
return Position.Top;
case "below":
return Position.Bottom;
case "within":
default:
return Position.Center;
}
}
public dispose() {
super.dispose();
this.gridview.dispose();
this.debugContainer?.dispose();
if (this.resizeTimer) {
clearInterval(this.resizeTimer);
this.resizeTimer = undefined;
} }
this._onDidLayoutChange.dispose(); public deserialize(data: any) {
} this.gridview.clear()
this.groups.clear()
this.fromJSON(data, this.deserializer)
this.gridview.layout(this._size, this._orthogonalSize)
}
public fromJSON(data: any, deserializer: IPanelDeserializer) {
const { grid, panels } = data
// this.gridview.deserialize(
// grid,
// new DefaultDeserializer(this, {
// createPanel: (id) => {
// const panelData = panels[id];
// const panel = deserializer.fromJSON(panelData);
// this.registerPanel(panel);
// return panel;
// },
// })
// );
this._onDidLayoutChange.fire({ kind: GroupChangeKind.NEW_LAYOUT })
}
public setAutoResizeToFit(enabled: boolean) {
if (this.resizeTimer) {
clearInterval(this.resizeTimer)
}
if (enabled) {
this.resizeTimer = setInterval(() => {
this.resizeToFit()
}, 500)
}
}
/**
* Resize the layout to fit the parent container
*/
public resizeToFit() {
const {
width,
height,
} = this.element.parentElement.getBoundingClientRect()
this.layout(width, height)
}
public addComponent(options: AddComponentOptions) {
let relativeLocation: number[] = [0]
if (options.position?.reference) {
const referenceGroup = this.groups.get(options.position.reference)
.value
const target = this.toTarget(options.position.direction)
if (target === Position.Center) {
throw new Error(`${target} not supported as an option`)
} else {
const location = getGridLocation(referenceGroup.element)
relativeLocation = getRelativeLocation(
this.gridview.orientation,
location,
target
)
}
}
const view = createComponent(
options.component,
this.options.components,
this.options.frameworkComponents,
this.options.frameworkComponentFactory.createComponent
)
view.init({ params: {} })
view.priority = options.priority
view.snap = options.snap
this.groups.set(options.id, {
value: view,
disposable: Disposable.NONE,
})
this.doAddGroup(view, relativeLocation, options.size)
}
public getGroup(id: string) {
return this.groups.get(id)?.value
}
public removeGroup(group: IComponentGridview) {
if (group === this._activeGroup) {
this._activeGroup = undefined
}
this.doRemoveGroup(group)
}
private doAddGroup(
group: IComponentGridview,
location: number[],
size?: number
) {
this.gridview.addView(group, size ?? { type: 'distribute' }, location)
this._onDidLayoutChange.fire({ kind: GroupChangeKind.ADD_GROUP })
this.doSetGroupActive(group)
}
private doRemoveGroup(
group: IComponentGridview,
options?: { skipActive?: boolean; skipDispose?: boolean }
) {
if (!this.groups.has(group.id)) {
throw new Error('invalid operation')
}
const { disposable } = this.groups.get(group.id)
if (!options?.skipDispose) {
disposable.dispose()
this.groups.delete(group.id)
}
const view = this.gridview.remove(group, { type: 'distribute' })
this._onDidLayoutChange.fire({ kind: GroupChangeKind.REMOVE_GROUP })
if (!options?.skipActive && this.groups.size > 0) {
this.doSetGroupActive(Array.from(this.groups.values())[0].value)
}
return view
}
public doSetGroupActive(group: IComponentGridview) {
if (this._activeGroup && this._activeGroup !== group) {
// this._activeGroup.setActive(false);
}
// group.setActive(true);
this._activeGroup = group
}
public moveGroup(
referenceGroup: IComponentGridview,
groupId: string,
itemId: string,
target: Position
) {
const sourceGroup = groupId ? this.groups.get(groupId).value : undefined
const referenceLocation = getGridLocation(referenceGroup.element)
const targetLocation = getRelativeLocation(
this.gridview.orientation,
referenceLocation,
target
)
const [targetParentLocation, to] = tail(targetLocation)
const sourceLocation = getGridLocation(sourceGroup.element)
const [sourceParentLocation, from] = tail(sourceLocation)
if (sequenceEquals(sourceParentLocation, targetParentLocation)) {
// special case when 'swapping' two views within same grid location
// if a group has one tab - we are essentially moving the 'group'
// which is equivalent to swapping two views in this case
this.gridview.moveView(sourceParentLocation, from, to)
return
}
// source group will become empty so delete the group
const targetGroup = this.doRemoveGroup(sourceGroup, {
skipActive: true,
skipDispose: true,
}) as IComponentGridview
// after deleting the group we need to re-evaulate the ref location
const updatedReferenceLocation = getGridLocation(referenceGroup.element)
const location = getRelativeLocation(
this.gridview.orientation,
updatedReferenceLocation,
target
)
this.doAddGroup(targetGroup, location)
}
public layout(size: number, orthogonalSize: number, force?: boolean) {
const different =
force ||
size !== this._size ||
orthogonalSize !== this._orthogonalSize
if (!different) {
return
}
this.element.style.height = `${orthogonalSize}px`
this.element.style.width = `${size}px`
this._size = size
this._orthogonalSize = orthogonalSize
this.gridview.layout(size, orthogonalSize)
}
private toTarget(
direction: 'left' | 'right' | 'above' | 'below' | 'within'
) {
switch (direction) {
case 'left':
return Position.Left
case 'right':
return Position.Right
case 'above':
return Position.Top
case 'below':
return Position.Bottom
case 'within':
default:
return Position.Center
}
}
public dispose() {
super.dispose()
this.gridview.dispose()
this.debugContainer?.dispose()
if (this.resizeTimer) {
clearInterval(this.resizeTimer)
this.resizeTimer = undefined
}
this._onDidLayoutChange.dispose()
}
} }

View File

@ -1,21 +1,21 @@
.layout-debug-container { .layout-debug-container {
color: white; color: white;
position: fixed; position: fixed;
top: 0px; top: 0px;
right: 0px; right: 0px;
z-index: 9999; z-index: 9999;
font-size: 11px; font-size: 11px;
width: 100px; width: 100px;
.layout-debug-widget { .layout-debug-widget {
background-color: black; background-color: black;
margin: 1px; margin: 1px;
.layout-debug-widget-row { .layout-debug-widget-row {
padding: 0px 5px; padding: 0px 5px;
display: flex; display: flex;
span:first-child { span:first-child {
flex-grow: 1; flex-grow: 1;
} }
}
} }
}
} }

View File

@ -1,59 +1,59 @@
import { CompositeDisposable } from "../../../lifecycle"; import { CompositeDisposable } from '../../../lifecycle'
import { Layout } from "../../layout"; import { Layout } from '../../layout'
import { GroupChangeKind } from "../../../groupview/groupview"; import { GroupChangeKind } from '../../../groupview/groupview'
export class DebugWidget extends CompositeDisposable { export class DebugWidget extends CompositeDisposable {
private _element: HTMLElement; private _element: HTMLElement
constructor(private layout: Layout) { constructor(private layout: Layout) {
super(); super()
let container = document.getElementById("layout-debug-container"); let container = document.getElementById('layout-debug-container')
if (!container) { if (!container) {
container = document.createElement("div"); container = document.createElement('div')
container.id = "layout-debug-container"; container.id = 'layout-debug-container'
container.className = "layout-debug-container"; container.className = 'layout-debug-container'
document.body.appendChild(container); document.body.appendChild(container)
}
this._element = document.createElement("div");
this._element.innerHTML =
`<div class='layout-debug-widget'>` +
`<div class='layout-debug-widget-row'><span>Groups:</span><span id='group-count'>0</span></div>` +
`<div class='layout-debug-widget-row'><span>Panels:</span><span id='panel-count'>0</span></div>` +
`</div>`;
container.appendChild(this._element);
const gc = this._element.querySelector("#group-count");
const pc = this._element.querySelector("#panel-count");
const events = [
GroupChangeKind.PANEL_CREATED,
GroupChangeKind.PANEL_DESTROYED,
GroupChangeKind.ADD_GROUP,
GroupChangeKind.REMOVE_GROUP,
];
this.addDisposables(
this.layout.onDidLayoutChange((event) => {
if (events.includes(event.kind)) {
gc.textContent = this.layout.size.toString();
pc.textContent = this.layout.totalPanels.toString();
} }
})
);
}
public dispose() { this._element = document.createElement('div')
super.dispose(); this._element.innerHTML =
`<div class='layout-debug-widget'>` +
`<div class='layout-debug-widget-row'><span>Groups:</span><span id='group-count'>0</span></div>` +
`<div class='layout-debug-widget-row'><span>Panels:</span><span id='panel-count'>0</span></div>` +
`</div>`
this._element.remove(); container.appendChild(this._element)
const container = document.getElementById("layout-debug-container"); const gc = this._element.querySelector('#group-count')
if (container && container.children.length === 0) { const pc = this._element.querySelector('#panel-count')
container.remove();
const events = [
GroupChangeKind.PANEL_CREATED,
GroupChangeKind.PANEL_DESTROYED,
GroupChangeKind.ADD_GROUP,
GroupChangeKind.REMOVE_GROUP,
]
this.addDisposables(
this.layout.onDidLayoutChange((event) => {
if (events.includes(event.kind)) {
gc.textContent = this.layout.size.toString()
pc.textContent = this.layout.totalPanels.toString()
}
})
)
}
public dispose() {
super.dispose()
this._element.remove()
const container = document.getElementById('layout-debug-container')
if (container && container.children.length === 0) {
container.remove()
}
} }
}
} }

View File

@ -1,77 +1,79 @@
.tab { .tab {
&.dragging { &.dragging {
.tab-action {
background-color: var(--active-group-visible-panel-color);
}
}
&.active-tab > .default-tab {
.tab-action {
visibility: visible;
}
}
&.inactive-tab > .default-tab {
.tab-action:not(.dirty) {
visibility: hidden;
}
&:hover {
.tab-action {
visibility: visible;
}
}
}
.default-tab {
position: relative;
height: 100%;
display: flex;
min-width: 80px;
align-items: center;
padding-left: 10px;
white-space: nowrap;
text-overflow: elipsis;
font-size: 13px;
.tab-content {
flex-grow: 1;
}
.action-container {
text-align: right;
width: 28px;
display: flex;
.tab-list {
display: flex;
padding: 0px;
margin: 0px;
justify-content: flex-end;
a:active:hover {
-webkit-mask-size: 100% 100% !important;
mask-size: 100% 100% !important;
}
.tab-action { .tab-action {
height: 16px; background-color: var(--active-group-visible-panel-color);
width: 16px; }
display: block; }
-webkit-mask: var(--tab-close-icon) 50% 50% / 90% 90% no-repeat;
mask: var(--tab-close-icon) 50% 50% / 90% 90% no-repeat; &.active-tab > .default-tab {
margin-right: "0.5em"; .tab-action {
visibility: visible;
&.disable-close { }
display: none; }
}
&.inactive-tab > .default-tab {
&.dirty:not(:hover) { .tab-action:not(.dirty) {
display: block; visibility: hidden;
-webkit-mask: var(--tab-dirty-icon) 50% 50% / 60% 60% no-repeat; }
mask: var(--tab-dirty-icon) 50% 50% / 60% 60% no-repeat; &:hover {
} .tab-action {
visibility: visible;
}
}
}
.default-tab {
position: relative;
height: 100%;
display: flex;
min-width: 80px;
align-items: center;
padding-left: 10px;
white-space: nowrap;
text-overflow: elipsis;
font-size: 13px;
.tab-content {
flex-grow: 1;
}
.action-container {
text-align: right;
width: 28px;
display: flex;
.tab-list {
display: flex;
padding: 0px;
margin: 0px;
justify-content: flex-end;
a:active:hover {
-webkit-mask-size: 100% 100% !important;
mask-size: 100% 100% !important;
}
.tab-action {
height: 16px;
width: 16px;
display: block;
-webkit-mask: var(--tab-close-icon) 50% 50% / 90% 90%
no-repeat;
mask: var(--tab-close-icon) 50% 50% / 90% 90% no-repeat;
margin-right: '0.5em';
&.disable-close {
display: none;
}
&.dirty:not(:hover) {
display: block;
-webkit-mask: var(--tab-dirty-icon) 50% 50% / 60% 60%
no-repeat;
mask: var(--tab-dirty-icon) 50% 50% / 60% 60% no-repeat;
}
}
}
} }
}
} }
}
} }

View File

@ -1,97 +1,99 @@
import { CompositeDisposable, MutableDisposable } from "../../../lifecycle"; import { CompositeDisposable, MutableDisposable } from '../../../lifecycle'
import { import {
PanelHeaderPart, PanelHeaderPart,
PartInitParameters, PartInitParameters,
} from "../../../groupview/panel/parts"; } from '../../../groupview/panel/parts'
import { addDisposableListener } from "../../../events"; import { addDisposableListener } from '../../../events'
import { toggleClass } from "../../../dom"; import { toggleClass } from '../../../dom'
export class DefaultTab extends CompositeDisposable implements PanelHeaderPart { export class DefaultTab extends CompositeDisposable implements PanelHeaderPart {
private _element: HTMLElement; private _element: HTMLElement
private _isGroupActive: boolean; private _isGroupActive: boolean
private _isPanelVisible: boolean; private _isPanelVisible: boolean
//
private _content: HTMLElement;
private _actionContainer: HTMLElement;
private _list: HTMLElement;
private action: HTMLElement;
//
private params: PartInitParameters;
//
private isDirtyDisposable = new MutableDisposable();
get element() {
return this._element;
}
get id() {
return "__DEFAULT_TAB__";
}
constructor() {
super();
this._element = document.createElement("div");
this._element.className = "default-tab";
// //
this._content = document.createElement("div"); private _content: HTMLElement
this._content.className = "tab-content"; private _actionContainer: HTMLElement
private _list: HTMLElement
private action: HTMLElement
// //
this._actionContainer = document.createElement("div"); private params: PartInitParameters
this._actionContainer.className = "action-container";
// //
this._list = document.createElement("ul"); private isDirtyDisposable = new MutableDisposable()
this._list.className = "tab-list";
//
this.action = document.createElement("a");
this.action.className = "tab-action";
//
this._element.appendChild(this._content);
this._element.appendChild(this._actionContainer);
this._actionContainer.appendChild(this._list);
this._list.appendChild(this.action);
//
this.addDisposables(
addDisposableListener(this._actionContainer, "mousedown", (ev) => {
ev.preventDefault();
})
);
this.render(); get element() {
} return this._element
public toJSON() {
return { id: this.id };
}
public init(params: PartInitParameters) {
this.params = params;
this._content.textContent = params.title;
this.isDirtyDisposable.value = this.params.api.onDidDirtyChange((event) => {
const isDirty = event;
toggleClass(this.action, "dirty", isDirty);
});
if (!this.params.suppressClosable) {
addDisposableListener(this.action, "click", (ev) => {
ev.preventDefault(); //
this.params.api.close();
});
} else {
this.action.classList.add("disable-close");
} }
}
public setVisible(isPanelVisible: boolean, isGroupVisible: boolean) { get id() {
this._isPanelVisible = isPanelVisible; return '__DEFAULT_TAB__'
this._isGroupActive = isGroupVisible; }
this.render(); constructor() {
} super()
private render() { this._element = document.createElement('div')
// this._element.className = 'default-tab'
} //
this._content = document.createElement('div')
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('a')
this.action.className = 'tab-action'
//
this._element.appendChild(this._content)
this._element.appendChild(this._actionContainer)
this._actionContainer.appendChild(this._list)
this._list.appendChild(this.action)
//
this.addDisposables(
addDisposableListener(this._actionContainer, 'mousedown', (ev) => {
ev.preventDefault()
})
)
this.render()
}
public toJSON() {
return { id: this.id }
}
public init(params: PartInitParameters) {
this.params = params
this._content.textContent = params.title
this.isDirtyDisposable.value = this.params.api.onDidDirtyChange(
(event) => {
const isDirty = event
toggleClass(this.action, 'dirty', isDirty)
}
)
if (!this.params.suppressClosable) {
addDisposableListener(this.action, 'click', (ev) => {
ev.preventDefault() //
this.params.api.close()
})
} else {
this.action.classList.add('disable-close')
}
}
public setVisible(isPanelVisible: boolean, isGroupVisible: boolean) {
this._isPanelVisible = isPanelVisible
this._isGroupActive = isGroupVisible
this.render()
}
private render() {
//
}
} }

View File

@ -1,21 +1,21 @@
.watermark { .watermark {
display: flex;
width: 100%;
&.has-actions {
.watermark-title {
.actions-bar {
display: none;
}
}
}
.watermark-title {
height: 35px;
width: 100%;
display: flex; display: flex;
} width: 100%;
.watermark-content {
flex-grow: 1; &.has-actions {
} .watermark-title {
.actions-bar {
display: none;
}
}
}
.watermark-title {
height: 35px;
width: 100%;
display: flex;
}
.watermark-content {
flex-grow: 1;
}
} }

View File

@ -1,81 +1,81 @@
import { import {
WatermarkPart, WatermarkPart,
WatermarkPartInitParameters, WatermarkPartInitParameters,
} from "../../../groupview/panel/parts"; } from '../../../groupview/panel/parts'
import { IGroupAccessor } from "../../layout"; import { IGroupAccessor } from '../../layout'
import { IGroupview } from "../../../groupview/groupview"; import { IGroupview } from '../../../groupview/groupview'
import { ActionContainer } from "../../../groupview/actions/actionsContainer"; import { ActionContainer } from '../../../groupview/actions/actionsContainer'
import { addDisposableListener } from "../../../events"; import { addDisposableListener } from '../../../events'
import { toggleClass } from "../../../dom"; import { toggleClass } from '../../../dom'
import { CompositeDisposable } from "../../../lifecycle"; import { CompositeDisposable } from '../../../lifecycle'
export class Watermark extends CompositeDisposable implements WatermarkPart { export class Watermark extends CompositeDisposable implements WatermarkPart {
private _element: HTMLElement; private _element: HTMLElement
private accessor: IGroupAccessor; private accessor: IGroupAccessor
private _visible: boolean; private _visible: boolean
private _group: IGroupview; private _group: IGroupview
constructor() { constructor() {
super(); super()
this._element = document.createElement("div"); this._element = document.createElement('div')
this._element.className = "watermark"; this._element.className = 'watermark'
const title = document.createElement("div"); const title = document.createElement('div')
title.className = "watermark-title"; title.className = 'watermark-title'
const emptySpace = document.createElement("span"); const emptySpace = document.createElement('span')
emptySpace.style.flexGrow = "1"; emptySpace.style.flexGrow = '1'
const content = document.createElement("div"); const content = document.createElement('div')
content.className = "watermark-content"; content.className = 'watermark-content'
this._element.appendChild(title); this._element.appendChild(title)
this._element.appendChild(content); this._element.appendChild(content)
const actions = new ActionContainer(); const actions = new ActionContainer()
title.appendChild(emptySpace); title.appendChild(emptySpace)
title.appendChild(actions.element); title.appendChild(actions.element)
const closeAnchor = document.createElement("a"); const closeAnchor = document.createElement('a')
closeAnchor.className = "close-action"; closeAnchor.className = 'close-action'
actions.add(closeAnchor); actions.add(closeAnchor)
addDisposableListener(closeAnchor, "click", (ev) => { addDisposableListener(closeAnchor, 'click', (ev) => {
ev.preventDefault(); // ev.preventDefault() //
this.accessor.removeGroup(this._group); this.accessor.removeGroup(this._group)
}); })
} }
public init(params: WatermarkPartInitParameters) { public init(params: WatermarkPartInitParameters) {
this.accessor = params.accessor; this.accessor = params.accessor
this.addDisposables( this.addDisposables(
this.accessor.onDidLayoutChange((event) => { this.accessor.onDidLayoutChange((event) => {
this.render(); this.render()
}) })
); )
this.render(); this.render()
} }
public setVisible(visible: boolean, group: IGroupview): void { public setVisible(visible: boolean, group: IGroupview): void {
this._visible = visible; this._visible = visible
this._group = group; this._group = group
this.render(); this.render()
} }
get element() { get element() {
return this._element; return this._element
} }
private render() { private render() {
const isOneGroup = this.accessor.size <= 1; const isOneGroup = this.accessor.size <= 1
toggleClass(this.element, "has-actions", isOneGroup); toggleClass(this.element, 'has-actions', isOneGroup)
} }
public dispose() { public dispose() {
super.dispose(); super.dispose()
} }
} }

View File

@ -1,34 +1,34 @@
import { IGridView, IViewDeserializer } from "../gridview/gridview"; import { IGridView, IViewDeserializer } from '../gridview/gridview'
import { IGroupPanel } from "../groupview/panel/types"; import { IGroupPanel } from '../groupview/panel/types'
import { Layout } from "./layout"; import { Layout } from './layout'
export interface IPanelDeserializer { export interface IPanelDeserializer {
fromJSON(panelData: { [index: string]: any }): IGroupPanel; fromJSON(panelData: { [index: string]: any }): IGroupPanel
} }
export class DefaultDeserializer implements IViewDeserializer { export class DefaultDeserializer implements IViewDeserializer {
constructor( constructor(
private readonly layout: Layout, private readonly layout: Layout,
private panelDeserializer: { createPanel: (id: string) => IGroupPanel } private panelDeserializer: { createPanel: (id: string) => IGroupPanel }
) {} ) {}
public fromJSON(data: { [key: string]: any }): IGridView { public fromJSON(data: { [key: string]: any }): IGridView {
const children = data.views; const children = data.views
const active = data.activeView; const active = data.activeView
const panels: IGroupPanel[] = []; const panels: IGroupPanel[] = []
for (const child of children) { for (const child of children) {
const panel = this.panelDeserializer.createPanel(child); const panel = this.panelDeserializer.createPanel(child)
panels.push(panel); panels.push(panel)
}
const group = this.layout.createGroup({
panels,
activePanel: panels.find((p) => p.id === active),
})
return group
} }
const group = this.layout.createGroup({
panels,
activePanel: panels.find((p) => p.id === active),
});
return group;
}
} }

View File

@ -1,3 +1,3 @@
export * from "./layout"; export * from './layout'
export * from "./componentGridview"; export * from './componentGridview'
export * from "./options"; export * from './options'

View File

@ -1,63 +1,63 @@
.custom-dragging { .custom-dragging {
height: 24px; height: 24px;
line-height: 24px; line-height: 24px;
font-size: 11px; font-size: 11px;
width: 100px; width: 100px;
background-color: dodgerblue; background-color: dodgerblue;
color: ghostwhite; color: ghostwhite;
border-radius: 11px; border-radius: 11px;
position: absolute; position: absolute;
padding-left: 10px; padding-left: 10px;
} }
.groupview { .groupview {
&.active-group { &.active-group {
> .title-container > .tab-container > .tab { > .title-container > .tab-container > .tab {
&.active-tab { &.active-tab {
background-color: var(--active-tab-background-visible); background-color: var(--active-tab-background-visible);
color: var(--active-group-visible-panel-color); color: var(--active-group-visible-panel-color);
.tab-action { .tab-action {
background-color: var(--active-group-visible-panel-color); background-color: var(--active-group-visible-panel-color);
} }
} }
&.inactive-tab { &.inactive-tab {
background-color: var(--active-tab-background-hidden); background-color: var(--active-tab-background-hidden);
color: var(--active-group-hidden-panel-color); color: var(--active-group-hidden-panel-color);
.tab-action { .tab-action {
background-color: var(--active-group-hidden-panel-color); background-color: var(--active-group-hidden-panel-color);
}
}
} }
}
} }
} &.inactive-group {
&.inactive-group { > .title-container > .tab-container > .tab {
> .title-container > .tab-container > .tab { &.active-tab {
&.active-tab { background-color: var(--inactive-tab-background-visible);
background-color: var(--inactive-tab-background-visible); color: var(--inactive-group-visible-panel-color);
color: var(--inactive-group-visible-panel-color);
.tab-action { .tab-action {
background-color: var(--inactive-group-visible-panel-color); background-color: var(--inactive-group-visible-panel-color);
} }
} }
&.inactive-tab { &.inactive-tab {
background-color: var(--inactive-tab-background-hidden); background-color: var(--inactive-tab-background-hidden);
color: var(--inactive-group-hidden-panel-color); color: var(--inactive-group-hidden-panel-color);
.tab-action { .tab-action {
background-color: var(--inactive-group-hidden-panel-color); background-color: var(--inactive-group-hidden-panel-color);
}
}
} }
}
} }
}
} }
// when a tab is dragged we loss the above stylings because they are conditional on parent elements // when a tab is dragged we loss the above stylings because they are conditional on parent elements
// there we also set some stylings for the dragging event // there we also set some stylings for the dragging event
.tab { .tab {
&.dragging { &.dragging {
background-color: var(--active-tab-background-visible); background-color: var(--active-tab-background-visible);
color: var(--active-group-visible-panel-color); color: var(--active-group-visible-panel-color);
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,72 +1,72 @@
import { IGroupview } from "../groupview/groupview"; import { IGroupview } from '../groupview/groupview'
import { import {
PanelContentPart, PanelContentPart,
PanelContentPartConstructor, PanelContentPartConstructor,
PanelHeaderPart, PanelHeaderPart,
PanelHeaderPartConstructor, PanelHeaderPartConstructor,
WatermarkConstructor, WatermarkConstructor,
} from "../groupview/panel/parts"; } from '../groupview/panel/parts'
import { IGroupPanel } from "../groupview/panel/types"; import { IGroupPanel } from '../groupview/panel/types'
import { FrameworkFactory } from "../types"; import { FrameworkFactory } from '../types'
import { Api } from "./layout"; import { Api } from './layout'
export interface GroupPanelFrameworkComponentFactory { export interface GroupPanelFrameworkComponentFactory {
content: FrameworkFactory<PanelContentPart>; content: FrameworkFactory<PanelContentPart>
tab: FrameworkFactory<PanelHeaderPart>; tab: FrameworkFactory<PanelHeaderPart>
} }
export interface TabContextMenuEvent { export interface TabContextMenuEvent {
event: MouseEvent; event: MouseEvent
api: Api; api: Api
panel: IGroupPanel; panel: IGroupPanel
} }
export interface LayoutOptions { export interface LayoutOptions {
tabComponents?: { tabComponents?: {
[componentName: string]: PanelHeaderPartConstructor; [componentName: string]: PanelHeaderPartConstructor
}; }
components?: { components?: {
[componentName: string]: PanelContentPartConstructor; [componentName: string]: PanelContentPartConstructor
}; }
frameworkTabComponents?: { frameworkTabComponents?: {
[componentName: string]: any; [componentName: string]: any
}; }
frameworkComponents?: { frameworkComponents?: {
[componentName: string]: any; [componentName: string]: any
}; }
watermarkComponent?: WatermarkConstructor; watermarkComponent?: WatermarkConstructor
watermarkFrameworkComponent?: any; watermarkFrameworkComponent?: any
frameworkComponentFactory: GroupPanelFrameworkComponentFactory; frameworkComponentFactory: GroupPanelFrameworkComponentFactory
tabHeight?: number; tabHeight?: number
debug?: boolean; debug?: boolean
enableExternalDragEvents?: boolean; enableExternalDragEvents?: boolean
} }
export interface PanelOptions { export interface PanelOptions {
componentName: string; componentName: string
tabComponentName?: string; tabComponentName?: string
params?: { [key: string]: any }; params?: { [key: string]: any }
id: string; id: string
title?: string; title?: string
suppressClosable?: boolean; suppressClosable?: boolean
} }
export interface AddPanelOptions export interface AddPanelOptions
extends Omit<PanelOptions, "componentName" | "tabComponentName"> { extends Omit<PanelOptions, 'componentName' | 'tabComponentName'> {
componentName: string | PanelContentPartConstructor; componentName: string | PanelContentPartConstructor
tabComponentName?: string | PanelHeaderPartConstructor; tabComponentName?: string | PanelHeaderPartConstructor
position?: { position?: {
direction?: "left" | "right" | "above" | "below" | "within"; direction?: 'left' | 'right' | 'above' | 'below' | 'within'
referencePanel: string; referencePanel: string
}; }
} }
export interface AddGroupOptions { export interface AddGroupOptions {
direction?: "left" | "right" | "above" | "below"; direction?: 'left' | 'right' | 'above' | 'below'
referencePanel: string; referencePanel: string
} }
export interface MovementOptions { export interface MovementOptions {
group?: IGroupview; group?: IGroupview
includePanel?: boolean; includePanel?: boolean
} }

View File

@ -1,56 +1,56 @@
export interface IDisposable { export interface IDisposable {
dispose: () => void; dispose: () => void
} }
export interface IValueDisposable<T> { export interface IValueDisposable<T> {
value: T; value: T
disposable: IDisposable; disposable: IDisposable
} }
export interface ISerializable { export interface ISerializable {
toJSON(): object; toJSON(): object
fromJSON(data: object): void; fromJSON(data: object): void
} }
export namespace Disposable { export namespace Disposable {
export const NONE: IDisposable = { dispose: () => {} }; export const NONE: IDisposable = { dispose: () => {} }
} }
export class CompositeDisposable { export class CompositeDisposable {
private disposables: IDisposable[]; private disposables: IDisposable[]
public static from(...args: IDisposable[]) { public static from(...args: IDisposable[]) {
return new CompositeDisposable(...args); return new CompositeDisposable(...args)
} }
constructor(...args: IDisposable[]) { constructor(...args: IDisposable[]) {
this.disposables = args; this.disposables = args
} }
public addDisposables(...args: IDisposable[]) { public addDisposables(...args: IDisposable[]) {
args?.forEach((arg) => this.disposables.push(arg)); args?.forEach((arg) => this.disposables.push(arg))
} }
public dispose() { public dispose() {
this.disposables.forEach((arg) => arg.dispose()); this.disposables.forEach((arg) => arg.dispose())
} }
} }
export class MutableDisposable implements IDisposable { export class MutableDisposable implements IDisposable {
private _disposable: IDisposable; private _disposable: IDisposable
constructor() {} constructor() {}
set value(disposable: IDisposable) { set value(disposable: IDisposable) {
if (this._disposable) { if (this._disposable) {
this._disposable.dispose(); this._disposable.dispose()
}
this._disposable = disposable
} }
this._disposable = disposable;
}
public dispose() { public dispose() {
if (this._disposable) { if (this._disposable) {
this._disposable.dispose(); this._disposable.dispose()
}
} }
}
} }

View File

@ -1,8 +1,8 @@
export const clamp = (value: number, min: number, max: number) => { export const clamp = (value: number, min: number, max: number) => {
return Math.min(max, Math.max(value, min)); return Math.min(max, Math.max(value, min))
}; }
export const sequentialNumberGenerator = () => { export const sequentialNumberGenerator = () => {
let value = 1; let value = 1
return { next: () => (value++).toString() }; return { next: () => (value++).toString() }
}; }

View File

@ -1,161 +1,163 @@
import { PanelDimensionChangeEvent } from "./types"; import { PanelDimensionChangeEvent } from './types'
import { Emitter, Event } from "../events"; import { Emitter, Event } from '../events'
import { CompositeDisposable, IDisposable } from "../lifecycle"; import { CompositeDisposable, IDisposable } from '../lifecycle'
import { FunctionOrValue } from "../types"; import { FunctionOrValue } from '../types'
// we've tried to do a bit better than the 'any' type. // we've tried to do a bit better than the 'any' type.
// anything that is serializable JSON should be valid here // anything that is serializable JSON should be valid here
type StateObject = type StateObject =
| number | number
| string | string
| boolean | boolean
| undefined | undefined
| null | null
| object | object
| StateObject[] | StateObject[]
| { [key: string]: StateObject }; | { [key: string]: StateObject }
interface State { interface State {
[key: string]: StateObject; [key: string]: StateObject
} }
interface ChangeFocusEvent { interface ChangeFocusEvent {
isFocused: boolean; isFocused: boolean
} }
interface PanelConstraintChangeEvent { interface PanelConstraintChangeEvent {
minimumSize?: number | (() => number); minimumSize?: number | (() => number)
maximumSize?: number | (() => number); maximumSize?: number | (() => number)
} }
export interface IBaseViewApi extends IDisposable { export interface IBaseViewApi extends IDisposable {
// events // events
onDidDimensionsChange: Event<PanelDimensionChangeEvent>; onDidDimensionsChange: Event<PanelDimensionChangeEvent>
onDidStateChange: Event<void>; onDidStateChange: Event<void>
onDidFocusChange: Event<ChangeFocusEvent>; onDidFocusChange: Event<ChangeFocusEvent>
// state // state
setState(key: string, value: StateObject): void; setState(key: string, value: StateObject): void
setState(state: State): void; setState(state: State): void
getState: () => State; getState: () => State
getStateKey: <T extends StateObject>(key: string) => T; getStateKey: <T extends StateObject>(key: string) => T
// //
readonly isFocused: boolean; readonly isFocused: boolean
} }
/** /**
* A core api implementation that should be used across all panel-like objects * A core api implementation that should be used across all panel-like objects
*/ */
export class BaseViewApi extends CompositeDisposable implements IBaseViewApi { export class BaseViewApi extends CompositeDisposable implements IBaseViewApi {
private _state: State = {}; private _state: State = {}
private _isFocused: boolean; private _isFocused: boolean
readonly _onDidStateChange = new Emitter<void>(); readonly _onDidStateChange = new Emitter<void>()
readonly onDidStateChange: Event<void> = this._onDidStateChange.event; readonly onDidStateChange: Event<void> = this._onDidStateChange.event
// //
readonly _onDidPanelDimensionChange = new Emitter<PanelDimensionChangeEvent>({ readonly _onDidPanelDimensionChange = new Emitter<
emitLastValue: true, PanelDimensionChangeEvent
}); >({
readonly onDidDimensionsChange = this._onDidPanelDimensionChange.event; emitLastValue: true,
// })
readonly _onDidChangeFocus = new Emitter<ChangeFocusEvent>({ readonly onDidDimensionsChange = this._onDidPanelDimensionChange.event
emitLastValue: true, //
}); readonly _onDidChangeFocus = new Emitter<ChangeFocusEvent>({
readonly onDidFocusChange: Event<ChangeFocusEvent> = this._onDidChangeFocus emitLastValue: true,
.event; })
// readonly onDidFocusChange: Event<ChangeFocusEvent> = this._onDidChangeFocus
.event
//
get isFocused() { get isFocused() {
return this._isFocused; return this._isFocused
}
constructor() {
super();
this.addDisposables(
this._onDidStateChange,
this._onDidChangeFocus,
this._onDidPanelDimensionChange,
this.onDidFocusChange((event) => {
this._isFocused = event.isFocused;
})
);
}
public setState(
key: string | { [key: string]: StateObject },
value?: StateObject
) {
if (typeof key === "object") {
this._state = key;
} else {
this._state[key] = value;
} }
this._onDidStateChange.fire(undefined);
}
public getState(): State { constructor() {
return this._state; super()
}
public getStateKey<T extends StateObject>(key: string): T { this.addDisposables(
return this._state[key] as T; this._onDidStateChange,
} this._onDidChangeFocus,
this._onDidPanelDimensionChange,
this.onDidFocusChange((event) => {
this._isFocused = event.isFocused
})
)
}
public dispose() { public setState(
super.dispose(); key: string | { [key: string]: StateObject },
} value?: StateObject
) {
if (typeof key === 'object') {
this._state = key
} else {
this._state[key] = value
}
this._onDidStateChange.fire(undefined)
}
public getState(): State {
return this._state
}
public getStateKey<T extends StateObject>(key: string): T {
return this._state[key] as T
}
public dispose() {
super.dispose()
}
} }
interface PanelConstraintChangeEvent { interface PanelConstraintChangeEvent {
minimumSize?: FunctionOrValue<number>; minimumSize?: FunctionOrValue<number>
maximumSize?: FunctionOrValue<number>; maximumSize?: FunctionOrValue<number>
} }
export interface IPanelApi extends IBaseViewApi { export interface IPanelApi extends IBaseViewApi {
onDidConstraintsChange: Event<PanelConstraintChangeEvent>; onDidConstraintsChange: Event<PanelConstraintChangeEvent>
setConstraints(value: PanelConstraintChangeEvent): void; setConstraints(value: PanelConstraintChangeEvent): void
} }
export class PanelApi extends BaseViewApi implements IBaseViewApi { export class PanelApi extends BaseViewApi implements IBaseViewApi {
readonly _onDidConstraintsChange = new Emitter<PanelConstraintChangeEvent>({ readonly _onDidConstraintsChange = new Emitter<PanelConstraintChangeEvent>({
emitLastValue: true, emitLastValue: true,
}); })
readonly onDidConstraintsChange: Event<PanelConstraintChangeEvent> = this readonly onDidConstraintsChange: Event<PanelConstraintChangeEvent> = this
._onDidConstraintsChange.event; ._onDidConstraintsChange.event
constructor() { constructor() {
super(); super()
} }
public setConstraints(value: PanelConstraintChangeEvent) { public setConstraints(value: PanelConstraintChangeEvent) {
this._onDidConstraintsChange.fire(value); this._onDidConstraintsChange.fire(value)
} }
} }
interface GridConstraintChangeEvent { interface GridConstraintChangeEvent {
minimumWidth?: FunctionOrValue<number>; minimumWidth?: FunctionOrValue<number>
minimumHeight?: FunctionOrValue<number>; minimumHeight?: FunctionOrValue<number>
maximumWidth?: FunctionOrValue<number>; maximumWidth?: FunctionOrValue<number>
maximumHeight?: FunctionOrValue<number>; maximumHeight?: FunctionOrValue<number>
} }
export interface IGridApi extends IBaseViewApi { export interface IGridApi extends IBaseViewApi {
onDidConstraintsChange: Event<GridConstraintChangeEvent>; onDidConstraintsChange: Event<GridConstraintChangeEvent>
setConstraints(value: GridConstraintChangeEvent): void; setConstraints(value: GridConstraintChangeEvent): void
} }
export class GridApi extends BaseViewApi implements IBaseViewApi { export class GridApi extends BaseViewApi implements IBaseViewApi {
readonly _onDidConstraintsChange = new Emitter<GridConstraintChangeEvent>({ readonly _onDidConstraintsChange = new Emitter<GridConstraintChangeEvent>({
emitLastValue: true, emitLastValue: true,
}); })
readonly onDidConstraintsChange: Event<GridConstraintChangeEvent> = this readonly onDidConstraintsChange: Event<GridConstraintChangeEvent> = this
._onDidConstraintsChange.event; ._onDidConstraintsChange.event
constructor() { constructor() {
super(); super()
} }
public setConstraints(value: GridConstraintChangeEvent) { public setConstraints(value: GridConstraintChangeEvent) {
this._onDidConstraintsChange.fire(value); this._onDidConstraintsChange.fire(value)
} }
} }

View File

@ -1,19 +1,19 @@
export interface InitParameters { export interface InitParameters {
params: { [index: string]: any }; params: { [index: string]: any }
state?: { [index: string]: any }; state?: { [index: string]: any }
} }
export interface PanelUpdateEvent { export interface PanelUpdateEvent {
params: { [index: string]: any }; params: { [index: string]: any }
} }
export interface IPanel { export interface IPanel {
init?(params: InitParameters): void; init?(params: InitParameters): void
layout?(width: number, height: number): void; layout?(width: number, height: number): void
update?(event: PanelUpdateEvent): void; update?(event: PanelUpdateEvent): void
} }
export interface PanelDimensionChangeEvent { export interface PanelDimensionChangeEvent {
width: number; width: number
height: number; height: number
} }

View File

@ -1,41 +1,41 @@
.pane-container { .pane-container {
&.animated { &.animated {
.view {
transition-duration: 0.15s;
transition-timing-function: ease-out;
}
}
.view { .view {
transition-duration: 0.15s; overflow: hidden;
transition-timing-function: ease-out; display: flex;
} flex-direction: column;
}
.view {
overflow: hidden;
display: flex;
flex-direction: column;
&:not(:first-child)::before { &:not(:first-child)::before {
background-color: transparent !important; background-color: transparent !important;
}
} }
}
.pane { .pane {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overflow: hidden; overflow: hidden;
.pane-header { .pane-header {
background-color: #383838; background-color: #383838;
color: white; color: white;
&:focus, &:focus,
:focus-within { :focus-within {
outline-width: 1px; outline-width: 1px;
outline-style: solid; outline-style: solid;
outline-offset: -1px; outline-offset: -1px;
opacity: 1 !important; opacity: 1 !important;
outline-color: dodgerblue; outline-color: dodgerblue;
} }
}
.pane-body {
overflow: auto;
background-color: #252526;
}
} }
.pane-body {
overflow: auto;
background-color: #252526;
}
}
} }

View File

@ -1,274 +1,276 @@
import { SplitView, IView, Orientation } from "../splitview/splitview"; import { SplitView, IView, Orientation } from '../splitview/splitview'
import { IDisposable } from "../lifecycle"; import { IDisposable } from '../lifecycle'
import { Emitter } from "../events"; import { Emitter } from '../events'
import { addClasses, removeClasses } from "../dom"; import { addClasses, removeClasses } from '../dom'
export interface IPaneOptions { export interface IPaneOptions {
minimumBodySize?: number; minimumBodySize?: number
maximumBodySize?: number; maximumBodySize?: number
orientation?: Orientation; orientation?: Orientation
isExpanded?: boolean; isExpanded?: boolean
} }
export abstract class Pane implements IView { export abstract class Pane implements IView {
public element: HTMLElement; public element: HTMLElement
private header: HTMLElement; private header: HTMLElement
private body: HTMLElement; private body: HTMLElement
private _onDidChangeExpansionState: Emitter<boolean> = new Emitter<boolean>(); private _onDidChangeExpansionState: Emitter<boolean> = new Emitter<
public onDidChangeExpansionState = this._onDidChangeExpansionState.event; boolean
>()
public onDidChangeExpansionState = this._onDidChangeExpansionState.event
private _onDidChange: Emitter<number | undefined> = new Emitter< private _onDidChange: Emitter<number | undefined> = new Emitter<
number | undefined number | undefined
>(); >()
public onDidChange = this._onDidChange.event; public onDidChange = this._onDidChange.event
private _minimumBodySize: number; private _minimumBodySize: number
private _maximumBodySize: number; private _maximumBodySize: number
private _minimumSize: number; private _minimumSize: number
private _maximumSize: number; private _maximumSize: number
private _isExpanded: boolean; private _isExpanded: boolean
private _orientation: Orientation; private _orientation: Orientation
private _orthogonalSize: number; private _orthogonalSize: number
private animationTimer: NodeJS.Timeout; private animationTimer: NodeJS.Timeout
private expandedSize: number; private expandedSize: number
private headerSize = 22; private headerSize = 22
constructor(options: IPaneOptions) { constructor(options: IPaneOptions) {
this.element = document.createElement("div"); this.element = document.createElement('div')
this.element.className = "pane"; this.element.className = 'pane'
this._minimumBodySize = this._minimumBodySize =
typeof options.minimumBodySize === "number" typeof options.minimumBodySize === 'number'
? options.minimumBodySize ? options.minimumBodySize
: 120; : 120
this._maximumBodySize = this._maximumBodySize =
typeof options.maximumBodySize === "number" typeof options.maximumBodySize === 'number'
? options.maximumBodySize ? options.maximumBodySize
: Number.POSITIVE_INFINITY; : Number.POSITIVE_INFINITY
this._isExpanded = options.isExpanded; this._isExpanded = options.isExpanded
this.orientation = options.orientation; this.orientation = options.orientation
}
public get minimumSize(): number {
const headerSize = this.headerSize;
const expanded = this.isExpanded();
const minimumBodySize = expanded
? this._minimumBodySize
: this._orientation === Orientation.HORIZONTAL
? 50
: 0;
return headerSize + minimumBodySize;
}
public get maximumSize(): number {
const headerSize = this.headerSize;
const expanded = this.isExpanded();
const maximumBodySize = expanded
? this._maximumBodySize
: this._orientation === Orientation.HORIZONTAL
? 50
: 0;
return headerSize + maximumBodySize;
}
public isExpanded() {
return this._isExpanded;
}
public get orientation() {
return this._orientation;
}
public get orthogonalSize() {
return this._orthogonalSize;
}
public set minimumSize(size: number) {
this._minimumSize = size;
this._onDidChange.fire(undefined);
}
public set maximumSize(size: number) {
this._maximumSize = size;
this._onDidChange.fire(undefined);
}
public setExpanded(expanded: boolean) {
this._isExpanded = expanded;
if (expanded) {
if (this.animationTimer) {
clearTimeout(this.animationTimer);
}
this.element.appendChild(this.body);
} else {
this.animationTimer = setTimeout(() => {
this.body.remove();
}, 200);
} }
this._onDidChangeExpansionState.fire(expanded); public get minimumSize(): number {
this._onDidChange.fire(expanded ? this.expandedSize : undefined); const headerSize = this.headerSize
} const expanded = this.isExpanded()
const minimumBodySize = expanded
? this._minimumBodySize
: this._orientation === Orientation.HORIZONTAL
? 50
: 0
public set orientation(orientation: Orientation) { return headerSize + minimumBodySize
this._orientation = orientation;
}
public set orthogonalSize(size: number) {
this._orthogonalSize = size;
}
public layout(size: number, orthogonalSize: number) {
if (this.isExpanded()) {
this.expandedSize = size;
} }
}
public render() { public get maximumSize(): number {
this.header = document.createElement("div"); const headerSize = this.headerSize
this.header.className = "pane-header"; const expanded = this.isExpanded()
this.header.style.height = `${this.headerSize}px`; const maximumBodySize = expanded
this.header.style.lineHeight = `${this.headerSize}px`; ? this._maximumBodySize
this.element.appendChild(this.header); : this._orientation === Orientation.HORIZONTAL
this.renderHeader(this.header); ? 50
: 0
// this.updateHeader(); return headerSize + maximumBodySize
}
this.body = document.createElement("div"); public isExpanded() {
this.body.className = "pane-body"; return this._isExpanded
this.element.appendChild(this.body); }
this.renderBody(this.body);
// if (!this.isExpanded()) { public get orientation() {
// this.body.remove(); return this._orientation
}
public get orthogonalSize() {
return this._orthogonalSize
}
public set minimumSize(size: number) {
this._minimumSize = size
this._onDidChange.fire(undefined)
}
public set maximumSize(size: number) {
this._maximumSize = size
this._onDidChange.fire(undefined)
}
public setExpanded(expanded: boolean) {
this._isExpanded = expanded
if (expanded) {
if (this.animationTimer) {
clearTimeout(this.animationTimer)
}
this.element.appendChild(this.body)
} else {
this.animationTimer = setTimeout(() => {
this.body.remove()
}, 200)
}
this._onDidChangeExpansionState.fire(expanded)
this._onDidChange.fire(expanded ? this.expandedSize : undefined)
}
public set orientation(orientation: Orientation) {
this._orientation = orientation
}
public set orthogonalSize(size: number) {
this._orthogonalSize = size
}
public layout(size: number, orthogonalSize: number) {
if (this.isExpanded()) {
this.expandedSize = size
}
}
public render() {
this.header = document.createElement('div')
this.header.className = 'pane-header'
this.header.style.height = `${this.headerSize}px`
this.header.style.lineHeight = `${this.headerSize}px`
this.element.appendChild(this.header)
this.renderHeader(this.header)
// this.updateHeader();
this.body = document.createElement('div')
this.body.className = 'pane-body'
this.element.appendChild(this.body)
this.renderBody(this.body)
// if (!this.isExpanded()) {
// this.body.remove();
// }
}
// protected updateHeader(): void {
// const expanded = this.isExpanded();
// this.header.style.height = `${this.headerSize}px`;
// this.header.style.lineHeight = `${this.headerSize}px`;
// toggleClass(this.header, "hidden", !this.headerVisible);
// toggleClass(this.header, "expanded", expanded);
// this.header.setAttribute("aria-expanded", String(expanded));
// this.header.style.color = this.styles.headerForeground
// ? this.styles.headerForeground.toString()
// : "";
// this.header.style.backgroundColor = this.styles.headerBackground
// ? this.styles.headerBackground.toString()
// : "";
// this.header.style.borderTop =
// this.styles.headerBorder && this.orientation === Orientation.VERTICAL
// ? `1px solid ${this.styles.headerBorder}`
// : "";
// this._dropBackground = this.styles.dropBackground;
// } // }
}
// protected updateHeader(): void { protected abstract renderHeader(container: HTMLElement): void
// const expanded = this.isExpanded(); protected abstract renderBody(container: HTMLElement): void
// this.header.style.height = `${this.headerSize}px`;
// this.header.style.lineHeight = `${this.headerSize}px`;
// toggleClass(this.header, "hidden", !this.headerVisible);
// toggleClass(this.header, "expanded", expanded);
// this.header.setAttribute("aria-expanded", String(expanded));
// this.header.style.color = this.styles.headerForeground
// ? this.styles.headerForeground.toString()
// : "";
// this.header.style.backgroundColor = this.styles.headerBackground
// ? this.styles.headerBackground.toString()
// : "";
// this.header.style.borderTop =
// this.styles.headerBorder && this.orientation === Orientation.VERTICAL
// ? `1px solid ${this.styles.headerBorder}`
// : "";
// this._dropBackground = this.styles.dropBackground;
// }
protected abstract renderHeader(container: HTMLElement): void;
protected abstract renderBody(container: HTMLElement): void;
} }
interface PaneItem { interface PaneItem {
pane: Pane; pane: Pane
disposable: IDisposable; disposable: IDisposable
} }
export class PaneView implements IDisposable { export class PaneView implements IDisposable {
private element: HTMLElement; private element: HTMLElement
private splitview: SplitView; private splitview: SplitView
private paneItems: PaneItem[] = []; private paneItems: PaneItem[] = []
private _orientation: Orientation; private _orientation: Orientation
private animationTimer: NodeJS.Timeout; private animationTimer: NodeJS.Timeout
private orthogonalSize: number; private orthogonalSize: number
private size: number; private size: number
constructor(container: HTMLElement, options: { orientation: Orientation }) { constructor(container: HTMLElement, options: { orientation: Orientation }) {
this._orientation = options.orientation ?? Orientation.VERTICAL; this._orientation = options.orientation ?? Orientation.VERTICAL
this.element = document.createElement("div"); this.element = document.createElement('div')
this.element.className = "pane-container"; this.element.className = 'pane-container'
this.setupAnimation = this.setupAnimation.bind(this); this.setupAnimation = this.setupAnimation.bind(this)
container.appendChild(this.element); container.appendChild(this.element)
this.splitview = new SplitView(this.element, { this.splitview = new SplitView(this.element, {
orientation: this._orientation, orientation: this._orientation,
}); })
}
public setOrientation(orientation: Orientation) {
this._orientation = orientation;
}
public addPane(pane: Pane, size?: number, index = this.splitview.length) {
const disposable = pane.onDidChangeExpansionState(this.setupAnimation);
const paneItem: PaneItem = {
pane,
disposable: {
dispose: () => {
disposable.dispose();
},
},
};
this.paneItems.splice(index, 0, paneItem);
pane.orientation = this._orientation;
pane.orthogonalSize = this.orthogonalSize;
this.splitview.addView(pane, size, index);
}
public getPanes() {
return this.splitview.getViews() as Pane[];
}
public removePane(index: number) {
this.splitview.removeView(index);
const paneItem = this.paneItems.splice(index, 1)[0];
paneItem.disposable.dispose();
return paneItem;
}
public moveView(from: number, to: number) {
const view = this.removePane(from);
this.addPane(view.pane, to);
}
public layout(size: number, orthogonalSize: number): void {
this.orthogonalSize = orthogonalSize;
this.size = size;
for (const paneItem of this.paneItems) {
paneItem.pane.orthogonalSize = this.orthogonalSize;
} }
this.splitview.layout(this.size, this.orthogonalSize); public setOrientation(orientation: Orientation) {
} this._orientation = orientation
private setupAnimation() {
if (this.animationTimer) {
clearTimeout(this.animationTimer);
} }
addClasses(this.element, "animated"); public addPane(pane: Pane, size?: number, index = this.splitview.length) {
const disposable = pane.onDidChangeExpansionState(this.setupAnimation)
this.animationTimer = setTimeout(() => { const paneItem: PaneItem = {
this.animationTimer = undefined; pane,
removeClasses(this.element, "animated"); disposable: {
}, 200); dispose: () => {
} disposable.dispose()
},
},
}
public dispose() { this.paneItems.splice(index, 0, paneItem)
this.paneItems.forEach((paneItem) => { pane.orientation = this._orientation
paneItem.disposable.dispose(); pane.orthogonalSize = this.orthogonalSize
}); this.splitview.addView(pane, size, index)
} }
public getPanes() {
return this.splitview.getViews() as Pane[]
}
public removePane(index: number) {
this.splitview.removeView(index)
const paneItem = this.paneItems.splice(index, 1)[0]
paneItem.disposable.dispose()
return paneItem
}
public moveView(from: number, to: number) {
const view = this.removePane(from)
this.addPane(view.pane, to)
}
public layout(size: number, orthogonalSize: number): void {
this.orthogonalSize = orthogonalSize
this.size = size
for (const paneItem of this.paneItems) {
paneItem.pane.orthogonalSize = this.orthogonalSize
}
this.splitview.layout(this.size, this.orthogonalSize)
}
private setupAnimation() {
if (this.animationTimer) {
clearTimeout(this.animationTimer)
}
addClasses(this.element, 'animated')
this.animationTimer = setTimeout(() => {
this.animationTimer = undefined
removeClasses(this.element, 'animated')
}, 200)
}
public dispose() {
this.paneItems.forEach((paneItem) => {
paneItem.disposable.dispose()
})
}
} }

View File

@ -1,48 +1,48 @@
import { IGroupPanel } from "../groupview/panel/types"; import { IGroupPanel } from '../groupview/panel/types'
import { Layout } from "../layout/layout"; import { Layout } from '../layout/layout'
import { DefaultPanel } from "../groupview/panel/panel"; import { DefaultPanel } from '../groupview/panel/panel'
import { PanelContentPart, PanelHeaderPart } from "../groupview/panel/parts"; import { PanelContentPart, PanelHeaderPart } from '../groupview/panel/parts'
import { IPanelDeserializer } from "../layout/deserializer"; import { IPanelDeserializer } from '../layout/deserializer'
import { import {
createContentComponent, createContentComponent,
createTabComponent, createTabComponent,
} from "../layout/componentFactory"; } from '../layout/componentFactory'
export class ReactPanelDeserialzier implements IPanelDeserializer { export class ReactPanelDeserialzier implements IPanelDeserializer {
constructor(private readonly layout: Layout) {} constructor(private readonly layout: Layout) {}
public fromJSON(panelData: { [index: string]: any }): IGroupPanel { public fromJSON(panelData: { [index: string]: any }): IGroupPanel {
const panelId = panelData.id; const panelId = panelData.id
const content = panelData.content; const content = panelData.content
const tab = panelData.tab; const tab = panelData.tab
const props = panelData.props; const props = panelData.props
const title = panelData.title; const title = panelData.title
const state = panelData.state; const state = panelData.state
const suppressClosable = panelData.suppressClosable; const suppressClosable = panelData.suppressClosable
const contentPart = createContentComponent( const contentPart = createContentComponent(
content.id, content.id,
this.layout.options.components, this.layout.options.components,
this.layout.options.frameworkComponents, this.layout.options.frameworkComponents,
this.layout.options.frameworkComponentFactory.content this.layout.options.frameworkComponentFactory.content
) as PanelContentPart; ) as PanelContentPart
const headerPart = createTabComponent( const headerPart = createTabComponent(
tab.id, tab.id,
this.layout.options.tabComponents, this.layout.options.tabComponents,
this.layout.options.frameworkComponentFactory, this.layout.options.frameworkComponentFactory,
this.layout.options.frameworkComponentFactory.tab this.layout.options.frameworkComponentFactory.tab
) as PanelHeaderPart; ) as PanelHeaderPart
const panel = new DefaultPanel(panelId, headerPart, contentPart); const panel = new DefaultPanel(panelId, headerPart, contentPart)
panel.init({ panel.init({
title, title,
suppressClosable, suppressClosable,
params: props || {}, params: props || {},
state: state || {}, state: state || {},
}); })
return panel; return panel
} }
} }

View File

@ -1,67 +1,71 @@
import * as React from "react"; import * as React from 'react'
import { import {
ComponentGridview, ComponentGridview,
IComponentGridviewLayout, IComponentGridviewLayout,
} from "../layout/componentGridview"; } from '../layout/componentGridview'
import { IGridApi } from "../panel/api"; import { IGridApi } from '../panel/api'
import { Orientation } from "../splitview/splitview"; import { Orientation } from '../splitview/splitview'
import { ReactComponentGridView } from "./reactComponentGridView"; import { ReactComponentGridView } from './reactComponentGridView'
export interface GridviewReadyEvent { export interface GridviewReadyEvent {
api: IComponentGridviewLayout; api: IComponentGridviewLayout
} }
export interface IGridviewPanelProps { export interface IGridviewPanelProps {
api: IGridApi; api: IGridApi
} }
export interface IGridviewComponentProps { export interface IGridviewComponentProps {
orientation: Orientation; orientation: Orientation
onReady?: (event: GridviewReadyEvent) => void; onReady?: (event: GridviewReadyEvent) => void
components: { components: {
[index: string]: React.FunctionComponent<IGridviewPanelProps>; [index: string]: React.FunctionComponent<IGridviewPanelProps>
}; }
} }
export const GridviewComponent = (props: IGridviewComponentProps) => { export const GridviewComponent = (props: IGridviewComponentProps) => {
const domReference = React.useRef<HTMLDivElement>(); const domReference = React.useRef<HTMLDivElement>()
const gridview = React.useRef<IComponentGridviewLayout>(); const gridview = React.useRef<IComponentGridviewLayout>()
const [portals, setPortals] = React.useState<React.ReactPortal[]>([]); const [portals, setPortals] = React.useState<React.ReactPortal[]>([])
const addPortal = React.useCallback((p: React.ReactPortal) => { const addPortal = React.useCallback((p: React.ReactPortal) => {
setPortals((portals) => [...portals, p]); setPortals((portals) => [...portals, p])
return { return {
dispose: () => { dispose: () => {
setPortals((portals) => portals.filter((portal) => portal !== p)); setPortals((portals) =>
}, portals.filter((portal) => portal !== p)
}; )
}, []); },
}
}, [])
React.useEffect(() => { React.useEffect(() => {
gridview.current = new ComponentGridview(domReference.current, { gridview.current = new ComponentGridview(domReference.current, {
orientation: props.orientation, orientation: props.orientation,
frameworkComponents: props.components, frameworkComponents: props.components,
frameworkComponentFactory: { frameworkComponentFactory: {
createComponent: (id: string, component: any) => { createComponent: (id: string, component: any) => {
return new ReactComponentGridView(id, id, component, { addPortal }); return new ReactComponentGridView(id, id, component, {
}, addPortal,
}, })
}); },
},
})
if (props.onReady) { if (props.onReady) {
props.onReady({ api: gridview.current }); props.onReady({ api: gridview.current })
} }
}, []); }, [])
return ( return (
<div <div
style={{ style={{
height: "100%", height: '100%',
width: "100%", width: '100%',
}} }}
ref={domReference} ref={domReference}
> >
{portals} {portals}
</div> </div>
); )
}; }

View File

@ -1,134 +1,142 @@
import * as React from "react"; import * as React from 'react'
import { IDisposable } from "../lifecycle"; import { IDisposable } from '../lifecycle'
import { Layout, Api } from "../layout/layout"; import { Layout, Api } from '../layout/layout'
import { ReactPanelContentPart } from "./reactContentPart"; import { ReactPanelContentPart } from './reactContentPart'
import { ReactPanelHeaderPart } from "./reactHeaderPart"; import { ReactPanelHeaderPart } from './reactHeaderPart'
import { IPanelProps } from "./react"; import { IPanelProps } from './react'
import { ReactPanelDeserialzier } from "./deserializer"; import { ReactPanelDeserialzier } from './deserializer'
import { GroupPanelFrameworkComponentFactory, TabContextMenuEvent } from "../layout/options"; import {
GroupPanelFrameworkComponentFactory,
TabContextMenuEvent,
} from '../layout/options'
export interface OnReadyEvent { export interface OnReadyEvent {
api: Api; api: Api
} }
export interface ReactLayout { export interface ReactLayout {
addPortal: (portal: React.ReactPortal) => IDisposable; addPortal: (portal: React.ReactPortal) => IDisposable
} }
export interface IReactGridProps { export interface IReactGridProps {
components?: { components?: {
[componentName: string]: React.FunctionComponent<IPanelProps>; [componentName: string]: React.FunctionComponent<IPanelProps>
}; }
tabComponents?: { tabComponents?: {
[componentName: string]: React.FunctionComponent<IPanelProps>; [componentName: string]: React.FunctionComponent<IPanelProps>
}; }
watermarkComponent?: React.FunctionComponent; watermarkComponent?: React.FunctionComponent
onReady?: (event: OnReadyEvent) => void; onReady?: (event: OnReadyEvent) => void
autoSizeToFitContainer?: boolean; autoSizeToFitContainer?: boolean
serializedLayout?: {}; serializedLayout?: {}
deserializer?: { deserializer?: {
fromJSON: ( fromJSON: (
data: any data: any
) => { ) => {
component: React.FunctionComponent<IPanelProps>; component: React.FunctionComponent<IPanelProps>
tabComponent?: React.FunctionComponent<IPanelProps>; tabComponent?: React.FunctionComponent<IPanelProps>
props?: { [key: string]: any }; props?: { [key: string]: any }
}; }
}; }
debug?: boolean; debug?: boolean
tabHeight?: number; tabHeight?: number
enableExternalDragEvents?: boolean; enableExternalDragEvents?: boolean
onTabContextMenu?: (event: TabContextMenuEvent) => void; onTabContextMenu?: (event: TabContextMenuEvent) => void
} }
export const ReactGrid = (props: IReactGridProps) => { export const ReactGrid = (props: IReactGridProps) => {
const domReference = React.useRef<HTMLDivElement>(); const domReference = React.useRef<HTMLDivElement>()
const layoutReference = React.useRef<Layout>(); const layoutReference = React.useRef<Layout>()
const [portals, setPortals] = React.useState<React.ReactPortal[]>([]); const [portals, setPortals] = React.useState<React.ReactPortal[]>([])
React.useEffect(() => { React.useEffect(() => {
const addPortal = (p: React.ReactPortal) => { const addPortal = (p: React.ReactPortal) => {
setPortals((portals) => [...portals, p]); setPortals((portals) => [...portals, p])
return { return {
dispose: () => { dispose: () => {
setPortals((portals) => portals.filter((portal) => portal !== p)); setPortals((portals) =>
}, portals.filter((portal) => portal !== p)
}; )
}; },
}
}
const factory: GroupPanelFrameworkComponentFactory = { const factory: GroupPanelFrameworkComponentFactory = {
content: { content: {
createComponent: ( createComponent: (
id: string, id: string,
component: React.FunctionComponent<IPanelProps> component: React.FunctionComponent<IPanelProps>
) => { ) => {
return new ReactPanelContentPart(id, component, { addPortal }); return new ReactPanelContentPart(id, component, {
}, addPortal,
}, })
tab: { },
createComponent: ( },
id: string, tab: {
component: React.FunctionComponent<IPanelProps> createComponent: (
) => { id: string,
return new ReactPanelHeaderPart(id, component, { addPortal }); component: React.FunctionComponent<IPanelProps>
}, ) => {
}, return new ReactPanelHeaderPart(id, component, {
}; addPortal,
})
},
},
}
const layout = new Layout({ const layout = new Layout({
frameworkComponentFactory: factory, frameworkComponentFactory: factory,
frameworkComponents: props.components, frameworkComponents: props.components,
frameworkTabComponents: props.tabComponents, frameworkTabComponents: props.tabComponents,
tabHeight: props.tabHeight, tabHeight: props.tabHeight,
debug: props.debug, debug: props.debug,
enableExternalDragEvents: props.enableExternalDragEvents, enableExternalDragEvents: props.enableExternalDragEvents,
}); })
layoutReference.current = layout; layoutReference.current = layout
domReference.current.appendChild(layoutReference.current.element); domReference.current.appendChild(layoutReference.current.element)
layout.deserializer = new ReactPanelDeserialzier(layout); layout.deserializer = new ReactPanelDeserialzier(layout)
layout.resizeToFit()
layout.resizeToFit(); if (props.serializedLayout) {
layout.deserialize(props.serializedLayout)
}
if (props.serializedLayout) { if (props.onReady) {
layout.deserialize(props.serializedLayout); props.onReady({ api: layout })
} }
if (props.onReady) { return () => {
props.onReady({ api: layout }); layout.dispose()
} }
}, [])
return () => { React.useEffect(() => {
layout.dispose(); const disposable = layoutReference.current.onTabContextMenu((event) => {
}; props.onTabContextMenu(event)
}, []); })
React.useEffect(() => { return () => {
const disposable = layoutReference.current.onTabContextMenu((event) => { disposable.dispose()
props.onTabContextMenu(event); }
}); }, [props.onTabContextMenu])
return () => { React.useEffect(() => {
disposable.dispose() layoutReference.current.setAutoResizeToFit(props.autoSizeToFitContainer)
} }, [props.autoSizeToFitContainer])
}, [props.onTabContextMenu])
React.useEffect(() => { return (
layoutReference.current.setAutoResizeToFit(props.autoSizeToFitContainer); <div
}, [props.autoSizeToFitContainer]); style={{
// height: "100%",
return ( width: '100%',
<div }}
style={{ ref={domReference}
// height: "100%", >
width: "100%", {portals}
}} </div>
ref={domReference} )
> }
{portals}
</div>
);
};

View File

@ -1,103 +1,112 @@
import * as React from "react"; import * as React from 'react'
import * as ReactDOM from "react-dom"; import * as ReactDOM from 'react-dom'
import { IDisposable } from "../lifecycle"; import { IDisposable } from '../lifecycle'
import { IGroupPanelApi } from "../groupview/panel/api"; import { IGroupPanelApi } from '../groupview/panel/api'
import { sequentialNumberGenerator } from "../math"; import { sequentialNumberGenerator } from '../math'
import { IBaseViewApi } from "../panel/api"; import { IBaseViewApi } from '../panel/api'
export interface IPanelProps { export interface IPanelProps {
api: IGroupPanelApi; api: IGroupPanelApi
} }
interface IPanelWrapperProps { interface IPanelWrapperProps {
component: React.FunctionComponent<IPanelProps>; component: React.FunctionComponent<IPanelProps>
componentProps: any; componentProps: any
} }
interface IPanelWrapperRef { interface IPanelWrapperRef {
update: (props: { [key: string]: any }) => void; update: (props: { [key: string]: any }) => void
} }
const PanelWrapper = React.forwardRef( const PanelWrapper = React.forwardRef(
(props: IPanelWrapperProps, ref: React.RefObject<IPanelWrapperRef>) => { (props: IPanelWrapperProps, ref: React.RefObject<IPanelWrapperRef>) => {
const [_, triggerRender] = React.useState<number>(); const [_, triggerRender] = React.useState<number>()
const _props = React.useRef<{ [key: string]: any }>(props.componentProps); const _props = React.useRef<{ [key: string]: any }>(
props.componentProps
)
React.useImperativeHandle( React.useImperativeHandle(
ref, ref,
() => ({ () => ({
update: (props: { [key: string]: any }) => { update: (props: { [key: string]: any }) => {
_props.current = { ..._props.current, ...props }; _props.current = { ..._props.current, ...props }
triggerRender(Date.now()); triggerRender(Date.now())
}, },
}), }),
[] []
); )
React.useEffect(() => { React.useEffect(() => {
console.debug("[reactwrapper] component mounted "); console.debug('[reactwrapper] component mounted ')
return () => { return () => {
console.debug("[reactwrapper] component unmounted "); console.debug('[reactwrapper] component unmounted ')
}; }
}, []); }, [])
return React.createElement(props.component, _props.current as IPanelProps); return React.createElement(
} props.component,
); _props.current as IPanelProps
)
}
)
const counter = sequentialNumberGenerator(); const counter = sequentialNumberGenerator()
export class ReactPart implements IDisposable { export class ReactPart implements IDisposable {
private componentInstance: IPanelWrapperRef; private componentInstance: IPanelWrapperRef
private ref: { portal: React.ReactPortal; disposable: IDisposable }; private ref: { portal: React.ReactPortal; disposable: IDisposable }
private disposed: boolean; private disposed: boolean
constructor( constructor(
private readonly parent: HTMLElement, private readonly parent: HTMLElement,
private readonly api: IBaseViewApi, private readonly api: IBaseViewApi,
private readonly addPortal: (portal: React.ReactPortal) => IDisposable, private readonly addPortal: (portal: React.ReactPortal) => IDisposable,
private readonly component: React.FunctionComponent<{}>, private readonly component: React.FunctionComponent<{}>,
private readonly parameters: { [key: string]: any } private readonly parameters: { [key: string]: any }
) { ) {
this.createPortal(); this.createPortal()
}
public update(props: {}) {
if (this.disposed) {
throw new Error("invalid operation");
} }
this.componentInstance?.update(props); public update(props: {}) {
} if (this.disposed) {
throw new Error('invalid operation')
}
private createPortal() { this.componentInstance?.update(props)
if (this.disposed) {
throw new Error("invalid operation");
} }
let props = { private createPortal() {
api: this.api, if (this.disposed) {
...this.parameters, throw new Error('invalid operation')
} as any; }
const wrapper = React.createElement(PanelWrapper, { let props = {
component: this.component, api: this.api,
componentProps: props, ...this.parameters,
ref: (element: any) => { } as any
this.componentInstance = element;
},
});
const portal = ReactDOM.createPortal(wrapper, this.parent, counter.next());
this.ref = { const wrapper = React.createElement(PanelWrapper, {
portal, component: this.component,
disposable: this.addPortal(portal), componentProps: props,
}; ref: (element: any) => {
} this.componentInstance = element
},
})
const portal = ReactDOM.createPortal(
wrapper,
this.parent,
counter.next()
)
public dispose() { this.ref = {
this.ref?.disposable?.dispose(); portal,
this.ref = undefined; disposable: this.addPortal(portal),
this.disposed = true; }
} }
public dispose() {
this.ref?.disposable?.dispose()
this.ref = undefined
this.disposed = true
}
} }

View File

@ -1,142 +1,144 @@
import { trackFocus } from "../dom"; import { trackFocus } from '../dom'
import { Emitter } from "../events"; import { Emitter } from '../events'
import { GridApi } from "../panel/api"; import { GridApi } from '../panel/api'
import { CompositeDisposable } from "../lifecycle"; import { CompositeDisposable } from '../lifecycle'
import { ReactLayout } from "./layout"; import { ReactLayout } from './layout'
import { ReactPart } from "./react"; import { ReactPart } from './react'
import { ISplitviewPanelProps } from "./splitview"; import { ISplitviewPanelProps } from './splitview'
import { PanelUpdateEvent, InitParameters, IPanel } from "../panel/types"; import { PanelUpdateEvent, InitParameters, IPanel } from '../panel/types'
import { IComponentGridview } from "../layout/componentGridview"; import { IComponentGridview } from '../layout/componentGridview'
import { FunctionOrValue } from "../types"; import { FunctionOrValue } from '../types'
export class ReactComponentGridView export class ReactComponentGridView
extends CompositeDisposable extends CompositeDisposable
implements IComponentGridview, IPanel { implements IComponentGridview, IPanel {
private _element: HTMLElement; private _element: HTMLElement
private part: ReactPart; private part: ReactPart
private params: { params: any }; private params: { params: any }
private api: GridApi; private api: GridApi
private _onDidChange: Emitter<number | undefined> = new Emitter< private _onDidChange: Emitter<number | undefined> = new Emitter<
number | undefined number | undefined
>(); >()
public onDidChange = this._onDidChange.event; public onDidChange = this._onDidChange.event
get element() { get element() {
return this._element; return this._element
}
private _minimumWidth: FunctionOrValue<number> = 200;
private _minimumHeight: FunctionOrValue<number> = 200;
private _maximumWidth: FunctionOrValue<number> = Number.MAX_SAFE_INTEGER;
private _maximumHeight: FunctionOrValue<number> = Number.MAX_SAFE_INTEGER;
get minimumWidth() {
return typeof this._minimumWidth === "function"
? this._minimumWidth()
: this._minimumWidth;
}
get minimumHeight() {
return typeof this._minimumHeight === "function"
? this._minimumHeight()
: this._minimumHeight;
}
get maximumHeight() {
return typeof this._maximumHeight === "function"
? this._maximumHeight()
: this._maximumHeight;
}
get maximumWidth() {
return typeof this._maximumWidth === "function"
? this._maximumWidth()
: this._maximumWidth;
}
constructor(
public readonly id: string,
private readonly componentName: string,
private readonly component: React.FunctionComponent<ISplitviewPanelProps>,
private readonly parent: ReactLayout
) {
super();
this.api = new GridApi();
if (!this.component) {
throw new Error("React.FunctionalComponent cannot be undefined");
} }
this._element = document.createElement("div"); private _minimumWidth: FunctionOrValue<number> = 200
this._element.tabIndex = -1; private _minimumHeight: FunctionOrValue<number> = 200
this._element.style.outline = "none"; private _maximumWidth: FunctionOrValue<number> = Number.MAX_SAFE_INTEGER
private _maximumHeight: FunctionOrValue<number> = Number.MAX_SAFE_INTEGER
const { onDidFocus, onDidBlur } = trackFocus(this._element); get minimumWidth() {
return typeof this._minimumWidth === 'function'
? this._minimumWidth()
: this._minimumWidth
}
get minimumHeight() {
return typeof this._minimumHeight === 'function'
? this._minimumHeight()
: this._minimumHeight
}
get maximumHeight() {
return typeof this._maximumHeight === 'function'
? this._maximumHeight()
: this._maximumHeight
}
get maximumWidth() {
return typeof this._maximumWidth === 'function'
? this._maximumWidth()
: this._maximumWidth
}
this.addDisposables( constructor(
this.api.onDidConstraintsChange((event) => { public readonly id: string,
if ( private readonly componentName: string,
typeof event.minimumWidth === "number" || private readonly component: React.FunctionComponent<
typeof event.minimumWidth === "function" ISplitviewPanelProps
) { >,
this._minimumWidth = event.minimumWidth; private readonly parent: ReactLayout
) {
super()
this.api = new GridApi()
if (!this.component) {
throw new Error('React.FunctionalComponent cannot be undefined')
} }
if (
typeof event.minimumHeight === "number" || this._element = document.createElement('div')
typeof event.minimumHeight === "function" this._element.tabIndex = -1
) { this._element.style.outline = 'none'
this._minimumHeight = event.minimumHeight;
const { onDidFocus, onDidBlur } = trackFocus(this._element)
this.addDisposables(
this.api.onDidConstraintsChange((event) => {
if (
typeof event.minimumWidth === 'number' ||
typeof event.minimumWidth === 'function'
) {
this._minimumWidth = event.minimumWidth
}
if (
typeof event.minimumHeight === 'number' ||
typeof event.minimumHeight === 'function'
) {
this._minimumHeight = event.minimumHeight
}
if (
typeof event.maximumWidth === 'number' ||
typeof event.maximumWidth === 'function'
) {
this._maximumWidth = event.maximumWidth
}
if (
typeof event.maximumHeight === 'number' ||
typeof event.maximumHeight === 'function'
) {
this._maximumHeight = event.maximumHeight
}
}),
onDidFocus(() => {
this.api._onDidChangeFocus.fire({ isFocused: true })
}),
onDidBlur(() => {
this.api._onDidChangeFocus.fire({ isFocused: false })
})
)
}
layout(width: number, height: number) {
this.api._onDidPanelDimensionChange.fire({ width, height })
}
init(parameters: InitParameters): void {
this.params = parameters
this.part = new ReactPart(
this.element,
this.api,
this.parent.addPortal,
this.component,
parameters.params
)
}
update(params: PanelUpdateEvent) {
this.params = { ...this.params.params, ...params }
this.part.update(params)
}
toJSON(): object {
return {
id: this.id,
component: this.componentName,
props: this.params.params,
state: this.api.getState(),
} }
if ( }
typeof event.maximumWidth === "number" ||
typeof event.maximumWidth === "function"
) {
this._maximumWidth = event.maximumWidth;
}
if (
typeof event.maximumHeight === "number" ||
typeof event.maximumHeight === "function"
) {
this._maximumHeight = event.maximumHeight;
}
}),
onDidFocus(() => {
this.api._onDidChangeFocus.fire({ isFocused: true });
}),
onDidBlur(() => {
this.api._onDidChangeFocus.fire({ isFocused: false });
})
);
}
layout(width: number, height: number) { dispose() {
this.api._onDidPanelDimensionChange.fire({ width, height }); super.dispose()
} this.api.dispose()
}
init(parameters: InitParameters): void {
this.params = parameters;
this.part = new ReactPart(
this.element,
this.api,
this.parent.addPortal,
this.component,
parameters.params
);
}
update(params: PanelUpdateEvent) {
this.params = { ...this.params.params, ...params };
this.part.update(params);
}
toJSON(): object {
return {
id: this.id,
component: this.componentName,
props: this.params.params,
state: this.api.getState(),
};
}
dispose() {
super.dispose();
this.api.dispose();
}
} }

View File

@ -1,118 +1,120 @@
import { trackFocus } from "../dom"; import { trackFocus } from '../dom'
import { Emitter } from "../events"; import { Emitter } from '../events'
import { PanelApi } from "../panel/api"; import { PanelApi } from '../panel/api'
import { PanelDimensionChangeEvent } from "../panel/types"; import { PanelDimensionChangeEvent } from '../panel/types'
import { CompositeDisposable } from "../lifecycle"; import { CompositeDisposable } from '../lifecycle'
import { IView } from "../splitview/splitview"; import { IView } from '../splitview/splitview'
import { ReactLayout } from "./layout"; import { ReactLayout } from './layout'
import { ReactPart } from "./react"; import { ReactPart } from './react'
import { ISplitviewPanelProps } from "./splitview"; import { ISplitviewPanelProps } from './splitview'
import { PanelUpdateEvent, InitParameters, IPanel } from "../panel/types"; import { PanelUpdateEvent, InitParameters, IPanel } from '../panel/types'
/** /**
* A no-thrills implementation of IView that renders a React component * A no-thrills implementation of IView that renders a React component
*/ */
export class ReactComponentView export class ReactComponentView
extends CompositeDisposable extends CompositeDisposable
implements IView, IPanel { implements IView, IPanel {
private _element: HTMLElement; private _element: HTMLElement
private part: ReactPart; private part: ReactPart
private params: { params: any }; private params: { params: any }
private api: PanelApi; private api: PanelApi
private _onDidChange: Emitter<number | undefined> = new Emitter< private _onDidChange: Emitter<number | undefined> = new Emitter<
number | undefined number | undefined
>(); >()
public onDidChange = this._onDidChange.event; public onDidChange = this._onDidChange.event
get element() { get element() {
return this._element; return this._element
}
private _minimumSize: number = 200;
private _maximumSize: number = Number.MAX_SAFE_INTEGER;
private _snapSize: number;
get minimumSize() {
return this._minimumSize;
}
set minimumSize(value: number) {
this._minimumSize = value;
}
get snapSize() {
return this._snapSize;
}
set snapSize(value: number) {
this._snapSize = value;
}
get maximumSize() {
return this._maximumSize;
}
set maximumSize(value: number) {
this._maximumSize = value;
}
constructor(
public readonly id: string,
private readonly componentName: string,
private readonly component: React.FunctionComponent<ISplitviewPanelProps>,
private readonly parent: ReactLayout
) {
super();
this.api = new PanelApi();
if (!this.component) {
throw new Error("React.FunctionalComponent cannot be undefined");
} }
this._element = document.createElement("div"); private _minimumSize: number = 200
this._element.tabIndex = -1; private _maximumSize: number = Number.MAX_SAFE_INTEGER
this._element.style.outline = "none"; private _snapSize: number
const { onDidFocus, onDidBlur } = trackFocus(this._element); get minimumSize() {
return this._minimumSize
}
set minimumSize(value: number) {
this._minimumSize = value
}
this.addDisposables( get snapSize() {
onDidFocus(() => { return this._snapSize
this.api._onDidChangeFocus.fire({ isFocused: true }); }
}), set snapSize(value: number) {
onDidBlur(() => { this._snapSize = value
this.api._onDidChangeFocus.fire({ isFocused: false }); }
})
);
}
layout(width: number, height: number) { get maximumSize() {
this.api._onDidPanelDimensionChange.fire({ width, height }); return this._maximumSize
} }
set maximumSize(value: number) {
this._maximumSize = value
}
init(parameters: InitParameters): void { constructor(
this.params = parameters; public readonly id: string,
this.part = new ReactPart( private readonly componentName: string,
this.element, private readonly component: React.FunctionComponent<
this.api, ISplitviewPanelProps
this.parent.addPortal, >,
this.component, private readonly parent: ReactLayout
parameters.params ) {
); super()
} this.api = new PanelApi()
if (!this.component) {
throw new Error('React.FunctionalComponent cannot be undefined')
}
update(params: PanelUpdateEvent) { this._element = document.createElement('div')
this.params = { ...this.params.params, ...params }; this._element.tabIndex = -1
this.part.update(params); this._element.style.outline = 'none'
}
toJSON(): object { const { onDidFocus, onDidBlur } = trackFocus(this._element)
return {
id: this.id,
component: this.componentName,
props: this.params.params,
state: this.api.getState(),
};
}
dispose() { this.addDisposables(
super.dispose(); onDidFocus(() => {
this.api.dispose(); this.api._onDidChangeFocus.fire({ isFocused: true })
} }),
onDidBlur(() => {
this.api._onDidChangeFocus.fire({ isFocused: false })
})
)
}
layout(width: number, height: number) {
this.api._onDidPanelDimensionChange.fire({ width, height })
}
init(parameters: InitParameters): void {
this.params = parameters
this.part = new ReactPart(
this.element,
this.api,
this.parent.addPortal,
this.component,
parameters.params
)
}
update(params: PanelUpdateEvent) {
this.params = { ...this.params.params, ...params }
this.part.update(params)
}
toJSON(): object {
return {
id: this.id,
component: this.componentName,
props: this.params.params,
state: this.api.getState(),
}
}
dispose() {
super.dispose()
this.api.dispose()
}
} }

View File

@ -1,62 +1,62 @@
import * as React from "react"; import * as React from 'react'
import { import {
PanelContentPart, PanelContentPart,
PartInitParameters, PartInitParameters,
ClosePanelResult, ClosePanelResult,
} from "../groupview/panel/parts"; } from '../groupview/panel/parts'
import { ReactPart, IPanelProps } from "./react"; import { ReactPart, IPanelProps } from './react'
import { ReactLayout } from "./layout"; import { ReactLayout } from './layout'
export class ReactPanelContentPart implements PanelContentPart { export class ReactPanelContentPart implements PanelContentPart {
private _element: HTMLElement; private _element: HTMLElement
private part: ReactPart; private part: ReactPart
get element() { get element() {
return this._element; return this._element
} }
constructor( constructor(
public readonly id: string, public readonly id: string,
private readonly component: React.FunctionComponent<IPanelProps>, private readonly component: React.FunctionComponent<IPanelProps>,
private readonly parent: ReactLayout private readonly parent: ReactLayout
) { ) {
this._element = document.createElement("div"); this._element = document.createElement('div')
} }
public init(parameters: PartInitParameters): void { public init(parameters: PartInitParameters): void {
this.part = new ReactPart( this.part = new ReactPart(
this.element, this.element,
parameters.api, parameters.api,
this.parent.addPortal, this.parent.addPortal,
this.component, this.component,
parameters.params parameters.params
); )
} }
public toJSON() { public toJSON() {
return { return {
id: this.id, id: this.id,
}; }
} }
public update(params: {}) { public update(params: {}) {
this.part.update(params); this.part.update(params)
} }
public setVisible(isPanelVisible: boolean, isGroupVisible: boolean): void { public setVisible(isPanelVisible: boolean, isGroupVisible: boolean): void {
// noop - retrieval from api // noop - retrieval from api
} }
public layout(width: number, height: number): void {} public layout(width: number, height: number): void {}
public close(): Promise<ClosePanelResult> { public close(): Promise<ClosePanelResult> {
return Promise.resolve(ClosePanelResult.CLOSE); return Promise.resolve(ClosePanelResult.CLOSE)
} }
public focus(): void {} public focus(): void {}
public onHide(): void {} public onHide(): void {}
public dispose() { public dispose() {
this.part?.dispose(); this.part?.dispose()
} }
} }

View File

@ -1,49 +1,49 @@
import * as React from "react"; import * as React from 'react'
import { PanelHeaderPart, PartInitParameters } from "../groupview/panel/parts"; import { PanelHeaderPart, PartInitParameters } from '../groupview/panel/parts'
import { ReactPart, IPanelProps } from "./react"; import { ReactPart, IPanelProps } from './react'
import { ReactLayout } from "./layout"; import { ReactLayout } from './layout'
export class ReactPanelHeaderPart implements PanelHeaderPart { export class ReactPanelHeaderPart implements PanelHeaderPart {
private _element: HTMLElement; private _element: HTMLElement
private part: ReactPart; private part: ReactPart
get element() { get element() {
return this._element; return this._element
} }
constructor( constructor(
public readonly id: string, public readonly id: string,
private readonly component: React.FunctionComponent<IPanelProps>, private readonly component: React.FunctionComponent<IPanelProps>,
private readonly parent: ReactLayout private readonly parent: ReactLayout
) { ) {
this._element = document.createElement("div"); this._element = document.createElement('div')
} }
public init(parameters: PartInitParameters): void { public init(parameters: PartInitParameters): void {
this.part = new ReactPart( this.part = new ReactPart(
this.element, this.element,
parameters.api, parameters.api,
this.parent.addPortal, this.parent.addPortal,
this.component, this.component,
parameters.params parameters.params
); )
} }
public toJSON() { public toJSON() {
return { return {
id: this.id, id: this.id,
}; }
} }
public layout(height: string) { public layout(height: string) {
// noop - retrieval from api // noop - retrieval from api
} }
public setVisible(isPanelVisible: boolean, isGroupVisible: boolean): void { public setVisible(isPanelVisible: boolean, isGroupVisible: boolean): void {
// noop - retrieval from api // noop - retrieval from api
} }
public dispose() { public dispose() {
this.part?.dispose(); this.part?.dispose()
} }
} }

View File

@ -1,93 +1,97 @@
import * as React from "react"; import * as React from 'react'
import { IPanelApi } from "../panel/api"; import { IPanelApi } from '../panel/api'
import { IDisposable } from "../lifecycle"; import { IDisposable } from '../lifecycle'
import { import {
IComponentSplitview, IComponentSplitview,
ComponentSplitview, ComponentSplitview,
} from "../splitview/componentSplitview"; } from '../splitview/componentSplitview'
import { Orientation } from "../splitview/splitview"; import { Orientation } from '../splitview/splitview'
import { ReactComponentView } from "./reactComponentView"; import { ReactComponentView } from './reactComponentView'
export interface SplitviewFacade { export interface SplitviewFacade {
addFromComponent(options: { addFromComponent(options: {
id: string; id: string
component: string; component: string
params?: { [index: string]: any }; params?: { [index: string]: any }
}): void; }): void
layout(size: number, orthogonalSize: number): void; layout(size: number, orthogonalSize: number): void
onChange: (cb: (event: { proportions: number[] }) => void) => IDisposable; onChange: (cb: (event: { proportions: number[] }) => void) => IDisposable
toJSON: () => any; toJSON: () => any
deserialize: (data: any) => void; deserialize: (data: any) => void
minimumSize: number; minimumSize: number
} }
export interface SplitviewReadyEvent { export interface SplitviewReadyEvent {
api: IComponentSplitview; api: IComponentSplitview
} }
export interface ISplitviewPanelProps { export interface ISplitviewPanelProps {
api: IPanelApi; api: IPanelApi
} }
export interface ISplitviewComponentProps { export interface ISplitviewComponentProps {
orientation: Orientation; orientation: Orientation
onReady?: (event: SplitviewReadyEvent) => void; onReady?: (event: SplitviewReadyEvent) => void
components: { components: {
[index: string]: React.FunctionComponent<ISplitviewPanelProps>; [index: string]: React.FunctionComponent<ISplitviewPanelProps>
}; }
} }
export const SplitViewComponent = (props: ISplitviewComponentProps) => { export const SplitViewComponent = (props: ISplitviewComponentProps) => {
const domReference = React.useRef<HTMLDivElement>(); const domReference = React.useRef<HTMLDivElement>()
const splitpanel = React.useRef<IComponentSplitview>(); const splitpanel = React.useRef<IComponentSplitview>()
const [portals, setPortals] = React.useState<React.ReactPortal[]>([]); const [portals, setPortals] = React.useState<React.ReactPortal[]>([])
const addPortal = React.useCallback((p: React.ReactPortal) => { const addPortal = React.useCallback((p: React.ReactPortal) => {
setPortals((portals) => [...portals, p]); setPortals((portals) => [...portals, p])
return { return {
dispose: () => { dispose: () => {
setPortals((portals) => portals.filter((portal) => portal !== p)); setPortals((portals) =>
}, portals.filter((portal) => portal !== p)
}; )
}, []); },
}
}, [])
React.useEffect(() => { React.useEffect(() => {
splitpanel.current = new ComponentSplitview(domReference.current, { splitpanel.current = new ComponentSplitview(domReference.current, {
orientation: props.orientation, orientation: props.orientation,
frameworkComponents: props.components, frameworkComponents: props.components,
frameworkWrapper: { frameworkWrapper: {
createComponent: (id: string, component: any) => { createComponent: (id: string, component: any) => {
return new ReactComponentView(id, id, component, { addPortal }); return new ReactComponentView(id, id, component, {
}, addPortal,
}, })
proportionalLayout: false, },
}); },
proportionalLayout: false,
})
const { width, height } = domReference.current.getBoundingClientRect(); const { width, height } = domReference.current.getBoundingClientRect()
const [size, orthogonalSize] = const [size, orthogonalSize] =
props.orientation === Orientation.HORIZONTAL props.orientation === Orientation.HORIZONTAL
? [width, height] ? [width, height]
: [height, width]; : [height, width]
splitpanel.current.layout(size, orthogonalSize); splitpanel.current.layout(size, orthogonalSize)
if (props.onReady) { if (props.onReady) {
props.onReady({ api: splitpanel.current }); props.onReady({ api: splitpanel.current })
} }
return () => { return () => {
splitpanel.current.dispose(); splitpanel.current.dispose()
}; }
}, []); }, [])
return ( return (
<div <div
style={{ style={{
height: "100%", height: '100%',
width: "100%", width: '100%',
}} }}
ref={domReference} ref={domReference}
> >
{portals} {portals}
</div> </div>
); )
}; }

View File

@ -1,156 +1,158 @@
import { IDisposable } from "../lifecycle"; import { IDisposable } from '../lifecycle'
import { LayoutPriority, Orientation, SplitView } from "./splitview"; import { LayoutPriority, Orientation, SplitView } from './splitview'
import { import {
createComponent, createComponent,
ISerializableView, ISerializableView,
SplitPanelOptions, SplitPanelOptions,
} from "./options"; } from './options'
export interface IComponentSplitview extends IDisposable { export interface IComponentSplitview extends IDisposable {
addFromComponent(options: { addFromComponent(options: {
id: string; id: string
component: string; component: string
params?: { params?: {
[index: string]: any; [index: string]: any
}; }
priority?: LayoutPriority; priority?: LayoutPriority
}): IDisposable; }): IDisposable
layout(width: number, height: number): void; layout(width: number, height: number): void
onChange(cb: (event: { proportions: number[] }) => void): IDisposable; onChange(cb: (event: { proportions: number[] }) => void): IDisposable
toJSON(): object; toJSON(): object
deserialize(data: any): void; deserialize(data: any): void
minimumSize: number; minimumSize: number
} }
/** /**
* A high-level implementation of splitview that works using 'panels' * A high-level implementation of splitview that works using 'panels'
*/ */
export class ComponentSplitview implements IComponentSplitview { export class ComponentSplitview implements IComponentSplitview {
private splitview: SplitView; private splitview: SplitView
constructor( constructor(
private readonly element: HTMLElement, private readonly element: HTMLElement,
private readonly options: SplitPanelOptions private readonly options: SplitPanelOptions
) { ) {
if (!options.components) { if (!options.components) {
options.components = {}; options.components = {}
} }
if (!options.frameworkComponents) { if (!options.frameworkComponents) {
options.frameworkComponents = {}; options.frameworkComponents = {}
}
this.splitview = new SplitView(this.element, options)
} }
this.splitview = new SplitView(this.element, options); get minimumSize() {
} return this.splitview.minimumSize
}
get minimumSize() { addFromComponent(options: {
return this.splitview.minimumSize; id: string
} component: string
params?: {
addFromComponent(options: { [index: string]: any
id: string; }
component: string; priority?: LayoutPriority
params?: { }): IDisposable {
[index: string]: any; const view = createComponent(
}; options.component,
priority?: LayoutPriority;
}): IDisposable {
const view = createComponent(
options.component,
this.options.components,
this.options.frameworkComponents,
this.options.frameworkWrapper.createComponent
);
this.registerView(view);
this.splitview.addView(view, { type: "distribute" });
view.init({ params: options.params });
view.priority = options.priority;
return {
dispose: () => {
//
},
};
}
private registerView(view: ISerializableView) {
//
}
layout(width: number, height: number): void {
const [size, orthogonalSize] =
this.splitview.orientation === Orientation.HORIZONTAL
? [width, height]
: [height, width];
this.splitview.layout(size, orthogonalSize);
}
onChange(cb: (event: { proportions: number[] }) => void): IDisposable {
return this.splitview.onDidSashEnd(() => {
cb({ proportions: this.splitview.proportions });
});
}
toJSON(): object {
const views = this.splitview.getViews().map((v: ISerializableView, i) => {
const size = this.splitview.getViewSize(i);
return {
size,
data: v.toJSON ? v.toJSON() : {},
minimumSize: v.minimumSize,
maximumSize: v.maximumSize,
snapSize: v.snapSize,
};
});
return {
views,
size: this.splitview.size,
orientation: this.splitview.orientation,
};
}
deserialize(data: any): void {
const { views, orientation, size } = data;
this.splitview.dispose();
this.splitview = new SplitView(this.element, {
orientation,
proportionalLayout: false,
descriptor: {
size,
views: views.map((v) => {
const data = v.data;
const view = createComponent(
data.component,
this.options.components, this.options.components,
this.options.frameworkComponents, this.options.frameworkComponents,
this.options.frameworkWrapper.createComponent this.options.frameworkWrapper.createComponent
); )
if (typeof v.minimumSize === "number") { this.registerView(view)
view.minimumSize = v.minimumSize;
}
if (typeof v.maximumSize === "number") {
view.maximumSize = v.maximumSize;
}
if (typeof v.snapSize === "number") {
view.snapSize = v.snapSize;
}
view.init({ params: v.props }); this.splitview.addView(view, { type: 'distribute' })
view.init({ params: options.params })
view.priority = options.priority
view.priority = v.priority; return {
dispose: () => {
//
},
}
}
return { size: v.size, view }; private registerView(view: ISerializableView) {
}), //
}, }
});
this.splitview.orientation = orientation; layout(width: number, height: number): void {
} const [size, orthogonalSize] =
this.splitview.orientation === Orientation.HORIZONTAL
? [width, height]
: [height, width]
this.splitview.layout(size, orthogonalSize)
}
public dispose() { onChange(cb: (event: { proportions: number[] }) => void): IDisposable {
this.splitview.dispose(); return this.splitview.onDidSashEnd(() => {
} cb({ proportions: this.splitview.proportions })
})
}
toJSON(): object {
const views = this.splitview
.getViews()
.map((v: ISerializableView, i) => {
const size = this.splitview.getViewSize(i)
return {
size,
data: v.toJSON ? v.toJSON() : {},
minimumSize: v.minimumSize,
maximumSize: v.maximumSize,
snapSize: v.snapSize,
}
})
return {
views,
size: this.splitview.size,
orientation: this.splitview.orientation,
}
}
deserialize(data: any): void {
const { views, orientation, size } = data
this.splitview.dispose()
this.splitview = new SplitView(this.element, {
orientation,
proportionalLayout: false,
descriptor: {
size,
views: views.map((v) => {
const data = v.data
const view = createComponent(
data.component,
this.options.components,
this.options.frameworkComponents,
this.options.frameworkWrapper.createComponent
)
if (typeof v.minimumSize === 'number') {
view.minimumSize = v.minimumSize
}
if (typeof v.maximumSize === 'number') {
view.maximumSize = v.maximumSize
}
if (typeof v.snapSize === 'number') {
view.snapSize = v.snapSize
}
view.init({ params: v.props })
view.priority = v.priority
return { size: v.size, view }
}),
},
})
this.splitview.orientation = orientation
}
public dispose() {
this.splitview.dispose()
}
} }

View File

@ -1,58 +1,58 @@
import { IView, ISplitViewOptions } from "../splitview/splitview"; import { IView, ISplitViewOptions } from '../splitview/splitview'
import { Constructor, FrameworkFactory } from "../types"; import { Constructor, FrameworkFactory } from '../types'
export interface ISerializableView extends IView { export interface ISerializableView extends IView {
toJSON: () => object; toJSON: () => object
init: (params: { params: any }) => void; init: (params: { params: any }) => void
} }
export interface SplitPanelOptions extends ISplitViewOptions { export interface SplitPanelOptions extends ISplitViewOptions {
components?: { components?: {
[componentName: string]: ISerializableView; [componentName: string]: ISerializableView
}; }
frameworkComponents?: { frameworkComponents?: {
[componentName: string]: any; [componentName: string]: any
}; }
frameworkWrapper?: FrameworkFactory<ISerializableView>; frameworkWrapper?: FrameworkFactory<ISerializableView>
} }
export interface ISerializableViewConstructor export interface ISerializableViewConstructor
extends Constructor<ISerializableView> {} extends Constructor<ISerializableView> {}
export function createComponent<T>( export function createComponent<T>(
componentName: string | Constructor<T> | any, componentName: string | Constructor<T> | any,
components: { components: {
[componentName: string]: T; [componentName: string]: T
}, },
frameworkComponents: { frameworkComponents: {
[componentName: string]: any; [componentName: string]: any
}, },
createFrameworkComponent: (id: string, component: any) => T createFrameworkComponent: (id: string, component: any) => T
): T { ): T {
const Component = const Component =
typeof componentName === "string" typeof componentName === 'string'
? components[componentName] ? components[componentName]
: componentName; : componentName
const FrameworkComponent = const FrameworkComponent =
typeof componentName === "string" typeof componentName === 'string'
? frameworkComponents[componentName] ? frameworkComponents[componentName]
: componentName; : componentName
if (Component && FrameworkComponent) { if (Component && FrameworkComponent) {
throw new Error( throw new Error(
`cannot register component ${componentName} as both a component and frameworkComponent` `cannot register component ${componentName} as both a component and frameworkComponent`
); )
}
if (FrameworkComponent) {
if (!createFrameworkComponent) {
throw new Error(
"you must register a frameworkPanelWrapper to use framework components"
);
} }
const wrappedComponent = createFrameworkComponent( if (FrameworkComponent) {
componentName, if (!createFrameworkComponent) {
FrameworkComponent throw new Error(
); 'you must register a frameworkPanelWrapper to use framework components'
return wrappedComponent; )
} }
return new Component() as T; const wrappedComponent = createFrameworkComponent(
componentName,
FrameworkComponent
)
return wrappedComponent
}
return new Component() as T
} }

View File

@ -1,130 +1,130 @@
.split-view-container { .split-view-container {
position: relative;
overflow: hidden;
height: 100%;
width: 100%;
&.animation {
.view,
.sash {
transition-duration: 0.15s;
transition-timing-function: ease-out;
}
}
// debug
// .sash {
// &.enabled {
// background-color: black;
// }
// &.disabled {
// background-color: orange;
// }
// &.maximum {
// background-color: green;
// }
// &.minimum {
// background-color: red;
// }
// }
&.horizontal {
height: 100%;
& > .sash-container > .sash {
height: 100%;
width: 4px;
&.enabled {
cursor: ew-resize;
}
&.disabled {
cursor: none;
}
&.maximum {
cursor: w-resize;
}
&.minimum {
cursor: e-resize;
}
}
& > .view-container > .view {
&:not(:first-child) {
padding-left: 1px;
&::before {
height: 100%;
width: 1px;
}
}
}
}
&.vertical {
width: 100%;
& > .sash-container > .sash {
width: 100%;
height: 4px;
&.enabled {
cursor: ns-resize;
}
&.disabled {
cursor: none;
}
&.maximum {
cursor: n-resize;
}
&.minimum {
cursor: s-resize;
}
}
& > .view-container > .view {
width: 100%;
&:not(:first-child) {
padding-top: 1px;
&::before {
height: 1px;
width: 100%;
}
}
}
}
.sash-container {
height: 100%;
width: 100%;
position: absolute;
.sash {
position: absolute;
z-index: 99;
outline: none;
}
}
.view-container {
position: relative; position: relative;
overflow: hidden;
height: 100%; height: 100%;
width: 100%; width: 100%;
.view:not(:first-child)::before { &.animation {
content: " "; .view,
position: absolute; .sash {
top: 0; transition-duration: 0.15s;
left: 0; transition-timing-function: ease-out;
z-index: 5; }
pointer-events: none;
background-color: var(--splitview-divider-color);
} }
.view { // debug
height: 100%; // .sash {
box-sizing: border-box; // &.enabled {
overflow: auto; // background-color: black;
position: absolute; // }
// &.disabled {
// background-color: orange;
// }
// &.maximum {
// background-color: green;
// }
// &.minimum {
// background-color: red;
// }
// }
&.horizontal {
height: 100%;
& > .sash-container > .sash {
height: 100%;
width: 4px;
&.enabled {
cursor: ew-resize;
}
&.disabled {
cursor: none;
}
&.maximum {
cursor: w-resize;
}
&.minimum {
cursor: e-resize;
}
}
& > .view-container > .view {
&:not(:first-child) {
padding-left: 1px;
&::before {
height: 100%;
width: 1px;
}
}
}
}
&.vertical {
width: 100%;
& > .sash-container > .sash {
width: 100%;
height: 4px;
&.enabled {
cursor: ns-resize;
}
&.disabled {
cursor: none;
}
&.maximum {
cursor: n-resize;
}
&.minimum {
cursor: s-resize;
}
}
& > .view-container > .view {
width: 100%;
&:not(:first-child) {
padding-top: 1px;
&::before {
height: 1px;
width: 100%;
}
}
}
}
.sash-container {
height: 100%;
width: 100%;
position: absolute;
.sash {
position: absolute;
z-index: 99;
outline: none;
}
}
.view-container {
position: relative;
height: 100%;
width: 100%;
.view:not(:first-child)::before {
content: ' ';
position: absolute;
top: 0;
left: 0;
z-index: 5;
pointer-events: none;
background-color: var(--splitview-divider-color);
}
.view {
height: 100%;
box-sizing: border-box;
overflow: auto;
position: absolute;
}
} }
}
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,41 +1,41 @@
:root { :root {
--group-view-background-color: #252526; --group-view-background-color: #252526;
// //
--title-bar-background-color: #252526; --title-bar-background-color: #252526;
--title-bar-scroll-bar-color: #888; --title-bar-scroll-bar-color: #888;
// //
--active-tab-background-visible: #1e1e1e; --active-tab-background-visible: #1e1e1e;
--active-tab-background-hidden: #2d2d2d; --active-tab-background-hidden: #2d2d2d;
--inactive-tab-background-visible: #1e1e1e; --inactive-tab-background-visible: #1e1e1e;
--inactive-tab-background-hidden: #2d2d2d; --inactive-tab-background-hidden: #2d2d2d;
--tab-divider-color: #1e1e1e; --tab-divider-color: #1e1e1e;
// //
--drag-over-background-color: rgba(83, 89, 93, 0.5); --drag-over-background-color: rgba(83, 89, 93, 0.5);
// //
--active-group-visible-panel-color: white; --active-group-visible-panel-color: white;
--active-group-hidden-panel-color: #969696; --active-group-hidden-panel-color: #969696;
--inactive-group-visible-panel-color: #8f8f8f; --inactive-group-visible-panel-color: #8f8f8f;
--inactive-group-hidden-panel-color: #626262; --inactive-group-hidden-panel-color: #626262;
// //
--tab-close-icon: url("https://fonts.gstatic.com/s/i/materialicons/close/v8/24px.svg"); --tab-close-icon: url('https://fonts.gstatic.com/s/i/materialicons/close/v8/24px.svg');
--tab-dirty-icon: url("https://fonts.gstatic.com/s/i/materialicons/lens/v6/24px.svg"); --tab-dirty-icon: url('https://fonts.gstatic.com/s/i/materialicons/lens/v6/24px.svg');
// //
--splitview-divider-color: rgb(68, 68, 68); --splitview-divider-color: rgb(68, 68, 68);
} }
.visual-studio-theme { .visual-studio-theme {
--active-tab-background-visible: dodgerblue; --active-tab-background-visible: dodgerblue;
.groupview { .groupview {
&.active-group { &.active-group {
> .title-container { > .title-container {
border-bottom: 2px solid var(--active-tab-background-visible); border-bottom: 2px solid var(--active-tab-background-visible);
} }
}
&.inactive-group {
> .title-container {
border-bottom: 2px solid var(--inactive-tab-background-visible);
}
}
} }
&.inactive-group {
> .title-container {
border-bottom: 2px solid var(--inactive-tab-background-visible);
}
}
}
} }

View File

@ -1,9 +1,9 @@
export interface Constructor<T> { export interface Constructor<T> {
new (): T; new (): T
} }
export interface FrameworkFactory<T> { export interface FrameworkFactory<T> {
createComponent: (id: string, component: any) => T; createComponent: (id: string, component: any) => T
} }
export type FunctionOrValue<T> = (() => T) | T; export type FunctionOrValue<T> = (() => T) | T

View File

@ -1,10 +1,10 @@
{ {
"extends": "../../tsconfig.build.json", "extends": "../../tsconfig.build.json",
"compilerOptions": { "compilerOptions": {
"composite": true, "composite": true,
"declaration": true, "declaration": true,
"outDir": "./dist", "outDir": "./dist",
"rootDir": "./src" "rootDir": "./src"
}, },
"include": ["src/**/*"] "include": ["src/**/*"]
} }

View File

@ -1,3 +1,3 @@
{ {
"extends": "../../tsconfig.json" "extends": "../../tsconfig.json"
} }

View File

@ -1,11 +1,11 @@
{ {
"out": "typedocs", "out": "typedocs",
"mode": "file", "mode": "file",
"inputFiles": ["./src"], "inputFiles": ["./src"],
"exclude": ["**/_test/**/*.*", "**/index.ts"], "exclude": ["**/_test/**/*.*", "**/index.ts"],
"ignoreCompilerErrors": true, "ignoreCompilerErrors": true,
"disableOutputCheck": true, "disableOutputCheck": true,
"excludeExternals": true, "excludeExternals": true,
"excludePrivate": true, "excludePrivate": true,
"excludeNotExported": true "excludeNotExported": true
} }

View File

@ -1,63 +1,63 @@
const gulp = require("gulp"); const gulp = require('gulp')
const gulpClean = require("gulp-clean"); const gulpClean = require('gulp-clean')
const gulpTypescript = require("gulp-typescript"); const gulpTypescript = require('gulp-typescript')
const merge = require("merge2"); const merge = require('merge2')
const header = require("gulp-header"); const header = require('gulp-header')
const gulpSass = require("gulp-sass"); const gulpSass = require('gulp-sass')
const concat = require("gulp-concat"); const concat = require('gulp-concat')
const sourcemaps = require("gulp-sourcemaps"); const sourcemaps = require('gulp-sourcemaps')
const headerTemplate = [ const headerTemplate = [
"/**", '/**',
" * <%= pkg.name %> - <%= pkg.description %>", ' * <%= pkg.name %> - <%= pkg.description %>',
" * @version v<%= pkg.version %>", ' * @version v<%= pkg.version %>',
" * @link <%= pkg.homepage %>", ' * @link <%= pkg.homepage %>',
" * @licence <%= pkg.licence %>", ' * @licence <%= pkg.licence %>',
" */\n", ' */\n',
].join("\n"); ].join('\n')
const dtsHeaderTemplate = [ const dtsHeaderTemplate = [
"// Type definitions for <%= pkg.name %> v <%= pkg.version %>", '// Type definitions for <%= pkg.name %> v <%= pkg.version %>',
"// Project <%= pkg.homepage %>\n", '// Project <%= pkg.homepage %>\n',
].join("\n"); ].join('\n')
const build = (options) => { const build = (options) => {
const { tsconfig, package } = options; const { tsconfig, package } = options
gulp.task("clean", () => gulp.task('clean', () =>
gulp.src("dist", { read: false, allowEmpty: true }).pipe(gulpClean()) gulp.src('dist', { read: false, allowEmpty: true }).pipe(gulpClean())
); )
gulp.task("esm", () => { gulp.task('esm', () => {
const ts = gulpTypescript.createProject(tsconfig); const ts = gulpTypescript.createProject(tsconfig)
const tsResult = gulp const tsResult = gulp
.src(["src/**/*.ts", "src/**/*.tsx"]) .src(['src/**/*.ts', 'src/**/*.tsx'])
.pipe(sourcemaps.init()) .pipe(sourcemaps.init())
.pipe(ts()); .pipe(ts())
return merge([ return merge([
tsResult.dts tsResult.dts
.pipe(header(dtsHeaderTemplate, { pkg: package })) .pipe(header(dtsHeaderTemplate, { pkg: package }))
.pipe(gulp.dest("./dist/esm")), .pipe(gulp.dest('./dist/esm')),
tsResult.js tsResult.js
.pipe(header(headerTemplate, { pkg: package })) .pipe(header(headerTemplate, { pkg: package }))
.pipe(gulp.dest("./dist/esm")), .pipe(gulp.dest('./dist/esm')),
tsResult tsResult
.pipe(sourcemaps.write(".", { includeContent: false })) .pipe(sourcemaps.write('.', { includeContent: false }))
.pipe(gulp.dest("./dist/esm")), .pipe(gulp.dest('./dist/esm')),
]); ])
}); })
gulp.task("sass", () => { gulp.task('sass', () => {
return ( return (
gulp gulp
.src("./src/**/*.scss") .src('./src/**/*.scss')
// .pipe( // .pipe(
// concat("styles.scss") // concat("styles.scss")
.pipe(gulpSass().on("error", gulpSass.logError)) .pipe(gulpSass().on('error', gulpSass.logError))
// ) // )
.pipe(concat("styles.css")) .pipe(concat('styles.css'))
.pipe(gulp.dest("./dist")) .pipe(gulp.dest('./dist'))
); )
}); })
}; }
module.exports = { build }; module.exports = { build }

Some files were not shown because too many files have changed in this diff Show More