dockview/packages/docs/versioned_docs/version-1.6.0/components/dockview.mdx
2023-03-19 21:43:24 +04:00

662 lines
28 KiB
Plaintext

---
description: Dockview Documentation
---
import {
RenderingDockview,
Checkbox,
} from '@site/src/components/dockview/rendering';
import Link from '@docusaurus/Link';
import useBaseUrl from '@docusaurus/useBaseUrl';
import DockviewPersistance from '@site/sandboxes/layout-dockview/src/app';
import SimpleDockview from '@site/sandboxes/simple-dockview/src/app';
import ResizeDockview from '@site/sandboxes/resize-dockview/src/app';
import NestedDockview from '@site/sandboxes/nested-dockview/src/app';
import EventsDockview from '@site/sandboxes/events-dockview/src/app';
import DndDockview from '@site/sandboxes/dnd-dockview/src/app';
import DockviewGroupControl from '@site/sandboxes/groupcontrol-dockview/src/app';
import DockviewWatermark from '@site/sandboxes/watermark-dockview/src/app';
import CustomHeadersDockview from '@site/sandboxes/customheader-dockview/src/app';
import DockviewNative from '@site/sandboxes/fullwidthtab-dockview/src/app';
import DockviewNative2 from '@site/sandboxes/nativeapp-dockview/src/app';
# Dockview
## Introduction
Dockview is an abstraction built on top of [Gridviews](./gridview) where each view is a container of many tabbed panels.
<div
style={{
height: '300px',
backgroundColor: 'rgb(30,30,30)',
color: 'white',
margin: '20px 0px',
}}
>
<SimpleDockview />
</div>
You can access the panels associated group through the `panel.group` variable.
The group will always be defined and will change if a panel is moved into another group.
## DockviewReact Component
You can create a Dockview through the use of the `ReactDockview` component.
```tsx
import { ReactDockview } from 'dockview';
```
| Property | Type | Optional | Default | Description |
| --------------------- | ------------------------------------ | -------- | --------- | ------------------------------------------------------------ |
| onReady | (event: SplitviewReadyEvent) => void | No | | |
| components | object | No | | |
| tabComponents | object | Yes | | |
| watermarkComponent | object | Yes | | |
| hideBorders | boolean | Yes | false | |
| className | string | Yes | '' | |
| disableAutoResizing | boolean | Yes | false | See <Link to="../basics/#auto-resizing">Auto Resizing</Link> |
| onDidDrop | Event | Yes | false | |
| showDndOverlay | Event | Yes | false | |
| defaultTabComponent | object | Yes | | |
| groupControlComponent | object | Yes | | |
| tabHeight | number | Yes | | |
| singleTabMode | 'fullwidth' \| 'default' | Yes | 'default' | |
## Dockview API
The Dockview API is exposed both at the `onReady` event and on each panel through `props.containerApi`.
Through this API you can control general features of the component and access all added panels.
```tsx title="Dockview API via Panel component"
const MyComponent = (props: IDockviewPanelProps<{ title: string }>) => {
// props.containerApi...
return <div>{`My first panel has the title: ${props.params.title}`}</div>;
};
```
```tsx title="Dockview API via the onReady callback"
const onReady = (event: DockviewReadyEvent) => {
// event.api...
};
```
| Property | Type | Description |
| ---------------------- | ---------------------------------------------------- | -------------------------------------------------------- |
| height | `number` | Component pixel height |
| width | `number` | Component pixel width |
| minimumHeight | `number` | |
| maximumHeight | `number` | |
| maximumWidth | `number` | |
| maximumWidth | `number` | |
| length | `number` | Number of panels |
| size | `number` | Number of Groups |
| panels | `IDockviewPanel[]` | |
| groups | `GroupPanel[]` | |
| activePanel | `IDockviewPanel \| undefined` | |
| activeGroup | `IDockviewPanel \| undefined` | |
| | | |
| onDidLayoutChange | `Event<void>` | |
| onDidLayoutFromJSON | `Event<void>` | |
| onDidAddGroup | `Event<GroupPanel>` | |
| onDidRemoveGroup | `Event<GroupPanel>` | |
| onDidActiveGroupChange | `Event<GroupPanel \| undefined>` | |
| onDidAddPanel | `Event<IDockviewPanel>` | |
| onDidRemovePanel | `Event<IDockviewPanel>` | |
| onDidActivePanelChange | `Event<IDockviewPanel \| undefined>` | |
| onDidDrop | `Event<DockviewDropEvent` | |
| | | |
| addPanel | `addPanel(options: AddPanelOptions): IDockviewPanel` | |
| getPanel | `(id: string) \| IDockviewPanel \| undefined` | |
| addGroup | `(options? AddGroupOptions): void` | |
| closeAllGroups | `(): void` | |
| removeGroup | `(group: GroupPanel): void` | |
| getGroup | `(id: string): GroupPanel \| undefined` | |
| | | |
| getTabHeight | `(): number \| undefined` | |
| setTabHeight | `(height: number \| undefined): void` | |
| updateOptions | `(options:SplitviewComponentUpdateOptions): void` | |
| focus | `(): void` | |
| layout | `(width: number, height:number): void` | <Link to="../basics/#auto-resizing">Auto Resizing</Link> |
| fromJSON | `(data: SerializedDockview): void` | <Link to="../basics/#serialization">Serialization</Link> |
| toJSON | `(): SerializedDockview` | <Link to="../basics/#serialization">Serialization</Link> |
| clear | `(): void` | Clears the current layout |
## Dockview Panel API
```tsx
const MyComponent = (props: IDockviewPanelProps<{ title: string }>) => {
// props.api...
return <div>{`My first panel has the title: ${props.params.title}`}</div>;
};
```
| Property | Type | Description |
| ---------------------- | ----------------------------------------------------------- | ---------------- |
| id | `string` | Panel id |
| isFocused | `boolean` | Is panel focused |
| isActive | `boolean` | Is panel active |
| width | `number` | Panel width |
| height | `number` | Panel height |
| onDidDimensionsChange | `Event<PanelDimensionChangeEvent>` | |
| onDidFocusChange | `Event<FocusEvent>` | |
| onDidVisibilityChange | `Event<VisibilityEvent>` | |
| onDidActiveChange | `Event<ActiveEvent>` | |
| setActive | `(): void` | |
| | | |
| onDidConstraintsChange | `onDidConstraintsChange: Event<PanelConstraintChangeEvent>` | |
| setConstraints | `(value: PanelConstraintChangeEvent2): void;` | |
| setSize | `(event: SizeEvent): void` | |
| | | |
| group | `GroupPanel | undefined` |
| isGroupActive | `boolean` | |
| title | `string` | |
| suppressClosable | `boolean` | |
| close | `(): void` | |
| setTitle | `(title: string): void` | |
## Layout Persistance
Layouts are loaded and saved via to `fromJSON` and `toJSON` methods on the Dockview api.
The api also exposes an event `onDidLayoutChange` you can listen on to determine when the layout has changed.
Below are some snippets showing how you might load from and save to localStorage.
```tsx title="Saving the layout state to localStorage"
React.useEffect(() => {
if (!api) {
return;
}
const disposable = api.onDidLayoutChange(() => {
const layout = api.toJSON();
localStorage.setItem(
'dockview_persistance_layout',
JSON.stringify(layout)
);
});
return () => {
disposable.dispose();
};
}, [api]);
```
```tsx title="Loading a layout from localStorage"
const onReady = (event: DockviewReadyEvent) => {
const layoutString = localStorage.getItem('dockview_persistance_layout');
let success = false;
if (layoutString) {
try {
const layout = JSON.parse(layoutString);
event.api.fromJSON(layout);
success = true;
} catch (err) {
//
}
}
if (!success) {
// do something if there is no layout or there was a loading error
}
};
```
Here is an example using the above code loading from and saving to localStorage.
If you refresh the page you should notice your layout is loaded as you left it.
<DockviewPersistance />
## Resizing
Each Dockview contains of a number of groups and each group has a number of panels.
Logically a user may want to resize a panel, but this translates to resizing the group which contains that panel.
You can set the size of a panel using `props.api.setSize(...)`.
You can also set the size of the group associated with the panel using `props.api.group.api.setSize(...)` although this isn't recommended
due to the clunky syntax.
```tsx
// it's mandatory to provide either a height or a width, providing both is optional
props.api.setSize({
height: 100,
width: 200,
});
// you could also resize the panels group, although not recommended it achieved the same result
props.api.group.api.setSize({
height: 100,
width: 200,
});
```
You can see an example invoking both approaches below.
<ResizeDockview />
## Watermark
When the dockview is empty you may want to display some fallback content, this is refered to as the `watermark`.
By default there the watermark has no content but you can provide as a prop to `DockviewReact` a `watermarkComponent`
which will be rendered when there are no panels or groups.
<DockviewWatermark />
## Drag And Drop
### Built-in behaviours
Dockview supports a wide variety of built-in Drag and Drop possibilities.
Below are some examples of the operations you can perform.
<img style={{ width: '60%' }} src={useBaseUrl('/img/add_to_tab.svg')} />
> Drag a tab onto another tab to place it inbetween existing tabs.
<img style={{ width: '60%' }} src={useBaseUrl('/img/add_to_empty_space.svg')} />
> Drag a tab to the right of the last tab to place it after the existing tabs.
<img style={{ width: '60%' }} src={useBaseUrl('/img/add_to_group.svg')} />
> Drag a group onto an existing group to merge the two groups.
<div style={{ display: 'flex', justifyContent: 'space-around' }}>
<img style={{ width: '40%' }} src={useBaseUrl('/img/drop_positions.svg')} />
<img
style={{ width: '40%' }}
src={useBaseUrl('/img/magnet_drop_positions.svg')}
/>
</div>
> Drag into the left/right/top/bottom target zone of a panel to create a new group in the selected direction.
> Drag into the center of a panel to add to that group.
> Drag to the edge of the dockview component to create a new group on the selected edge.
### Extended behaviours
For interaction with the Drag events directly the component exposes some method to help determine whether external drag events should be interacted with or not.
```tsx
/**
* called when an ondrop event which does not originate from the dockview libray and
* passes the showDndOverlay condition occurs
**/
const onDidDrop = (event: DockviewDropEvent) => {
const { group } = event;
event.api.addPanel({
id: 'test',
component: 'default',
position: {
referencePanel: group.activePanel.id,
direction: 'within',
},
});
};
/**
* called for drag over events which do not originate from the dockview library
* allowing the developer to decide where the overlay should be shown for a
* particular drag event
**/
const showDndOverlay = (event: DockviewDndOverlayEvent) => {
return true;
};
return (
<DockviewReact
components={components}
onReady={onReady}
className="dockview-theme-abyss"
onDidDrop={onDidDrop}
showDndOverlay={showDndOverlay}
/>
);
```
<DndDockview />
## Panels
### Add Panel
Using the dockview API you can access the `addPanel` method which returns an instance of the created panel.
The minimum method signature is:
```ts
const panel = api.addPanel({
id: 'my_unique_panel_id',
component: 'my_component',
});
```
where `id` is the unique id of the panel and `component` is the implenentation which
will be used to render the panel. You will have registered this using the `components` prop of the `DockviewReactComponent` component.
You can optionally provide a `tabComponent` parameters to the `addPanel` method which will render the tab using a custom renderer.
You will have registered this using the `tabComponents` prop of the `DockviewReactComponent` component.
```ts
const panel = api.addPanel({
id: 'my_unique_panel_id',
component: 'my_component',
tabComponent: 'my_tab_component',
});
```
You can pass properties to the panel using the `params` key.
You can update these properties through the panels `api` object and its `updateParameters` method.
```ts
const panel = api.addPanel({
id: 'my_unique_panel_id',
component: 'my_component',
params: {
myCustomKey: 'my_custom_value',
},
});
panel.api.updateParameters({
myCustomKey: 'my_custom_value',
myOtherCustomKey: 'my_other_custom_key',
});
```
> Note `updateParameters` does not accept partial parameter updates, you should call it with the entire set of parameters
> you want the panel to receive.
Finally `addPanel` accepts a `position` object which tells dockview where to place the panel.
- This object optionally accepts either a `referencePanel` or `referenceGroup` which can be the associated id as a string
or the panel/group object reference.
- This object accepts a `direction` property which dictates where,
relative to the provided reference the new panel will be placed.
> If neither a `referencePanel` or `referenceGroup` then the provided `direction` will be treated as absolute.
> If no `direction` is provided the library will place the new panel in a pre-determined position.
```ts
const panel = api.addPanel({
id: 'panel_1',
component: 'default',
});
const panel2 = api.addPanel({
id: 'panel_2',
component: 'default',
position: {
referencePanel: panel1,
direction: 'right',
},
});
```
### Panel Rendering
By default `DockviewReact` only adds to the DOM those panels that are visible,
if a panel is not the active tab and not shown the contents of the hidden panel will be removed from the DOM.
However the React Components associated with each panel are only created once and will always exist for as long as the panel exists, hidden or not.
> For example this means that any hooks in those components will run whether the panel is visible or not which may lead to excessive background work depending
> on the panels implementation.
This is the default behaviour to ensure the greatest flexibility for the user but through the panels `props.api` you can listen to the visiblity state of the panel
and write additional logic to optimize your application.
For example if you wanted to unmount the React Components when the panel is not visible you could create a Higher-Order-Component that listens to the panels
visiblity state and only renders the panel when visible.
```tsx title="Only rendering the React Component when the panel is visible, otherwise rendering a null React Component"
import { IDockviewPanelProps } from 'dockview';
import * as React from 'react';
function RenderWhenVisible(
component: React.FunctionComponent<IDockviewPanelProps>
) {
const HigherOrderComponent = (props: IDockviewPanelProps) => {
const [visible, setVisible] = React.useState<boolean>(
props.api.isVisible
);
React.useEffect(() => {
const disposable = props.api.onDidVisibilityChange((event) =>
setVisible(event.isVisible)
);
return () => {
disposable.dispose();
};
}, [props.api]);
if (!visible) {
return null;
}
return React.createElement(component, props);
};
return HigherOrderComponent;
}
```
```tsx
const components = { default: RenderWhenVisible(MyComponent) };
```
Toggling the checkbox you can see that when you only render those panels which are visible the underling React component is destroyed when it becomes hidden and re-created when it becomes visible.
<Checkbox />
<div
style={{
height: '300px',
backgroundColor: 'rgb(30,30,30)',
color: 'white',
margin: '20px 0px',
}}
>
<RenderingDockview renderVisibleOnly={false} />
</div>
## Headers
### Custom Tab Headers
You can provide custom renderers for your tab headers for maximum customization.
A default implementation of `DockviewDefaultTab` is provided should you only wish to attach minor
changes and events that do not alter the default behaviour, for example to add a custom context menu event
handler.
```tsx title="Attaching a custom context menu event handlers to a custom header"
import { IDockviewPanelHeaderProps, DockviewDefaultTab } from 'dockview';
const MyCustomheader = (props: IDockviewPanelHeaderProps) => {
const onContextMenu = (event: React.MouseEvent) => {
event.preventDefault();
alert('context menu');
};
return <DockviewDefaultTab onContextMenu={onContextMenu} {...props} />;
};
```
You are also free to define a custom renderer entirely from scratch and not make use of the `DockviewDefaultTab` component.
To use a custom renderer you can must register a collection of tab components.
```tsx
const tabComponents = {
myCustomHeader: MyCustomHeader,
};
return <DockviewReact tabComponents={tabComponents} ... />;
```
```tsx
api.addPanel({
id: 'panel_1',
component: 'default',
tabComponent: 'myCustomHeader', // <-- your registered renderers
title: 'Panel 1',
});
```
You can also override the default tab renderer which will be used when no `tabComponent` is provided to the `addPanel` function.
```tsx
<DockviewReact defaultTabComponent={MyCustomHeader} ... />;
```
As a simple example the below attachs a custom event handler for the context menu on all tabs as a default tab renderer
The below example uses a custom tab renderer to reigster a popover when the user right clicked on a tab.
This still makes use of the `DockviewDefaultTab` since it's only a minor change.
<CustomHeadersDockview />
### Default Tab Title
If you are using the default tab renderer you can set the title of a tab when creating it
```tsx
api.addPanel({
id: 'panel_1',
component: 'my_component',
title: 'my_custom_title', // <-- special param for title
});
```
You can update the title through the panel api which can be accessed via `props.api` if you are inside the panel
component or via `api.getPanel('panel1').api` if you are accessing from outside of the panel component.
```tsx
api.updateTitle('my_new_custom_title');
```
> Note this only works when using the default tab implementation.
### Custom Tab Title
If you are using a custom tab implementation you should pass variables through as a parameter and render them
through your tab components implementation.
```tsx title="Add a panel with custom parameters"
api.addPanel({
id: 'panel_2',
component: 'my_component',
tabComponent: 'my_tab',
params: {
myTitle: 'Window 2', // <-- passing a variable to use as a title
},
});
```
```tsx title="Accessing custom parameters from a custom tab renderer"
const tabComponents = {
default: (props: IDockviewPanelHeaderProps<{ myTitle: string }>) => {
const title = props.params.myTitle; // <-- accessing my custom varaible
return <div>{/** tab implementation as chosen by developer */}</div>;
},
};
```
### Hidden Headers
You may wish to hide the header section of a group. This can achieved through the `hidden` variable on `panel.group.header`.
```tsx
panel.group.header.hidden = true;
```
### Full width tabs
`DockviewReactComponent` accepts the prop `singleTabMode`. If set `singleTabMode=fullwidth` then when there is only one tab in a group this tab will expand
to the entire width of the group. For example:
> This can be conmbined with <Link to="./dockview/#locked-group">Locked Groups</Link> to create an application that feels more like a Window Manager
> rather than a collection of groups and tabs.
```tsx
<DockviewReactComponent singleTabMode="fullwidth" {...otherProps} />
```
<DockviewNative />
## Groups
### Locked group
Locking a group will disable all drop events for this group ensuring no additional panels can be added to the group through drop events.
You can still add groups to a locked panel programatically using the API though.
```tsx
panel.group.locked = true;
```
### Group Controls Panel
`DockviewReact` accepts a prop `groupControlComponent` which expects a React component whos props are `IDockviewGroupControlProps`.
This control will be rendered inside the header bar on the right hand side for each group of tabs.
```tsx
const Component: React.FunctionComponent<IDockviewGroupControlProps> = () => {
return <div>{'...'}</div>;
};
return <DockviewReact {...props} groupControlComponent={Component} />;
```
As a simple example the below uses the `groupControlComponent` to render a small control that indicates whether the group
is active and which panel is active in that group.
```tsx
const GroupControlComponent = (props: IDockviewGroupControlProps) => {
const isGroupActive = props.isGroupActive;
const activePanel = props.activePanel;
return (
<div className="dockview-groupcontrol-demo">
<span
className="dockview-groupcontrol-demo-group-active"
style={{
background: isGroupActive ? 'green' : 'red',
}}
>
{isGroupActive ? 'Group Active' : 'Group Inactive'}
</span>
<span className="dockview-groupcontrol-demo-active-panel">{`activePanel: ${
activePanel?.id || 'null'
}`}</span>
</div>
);
};
```
<DockviewGroupControl />
## Events
<EventsDockview />
## Advanced Examples
### Nested Dockviews
You can safely create multiple dockview instances within one page and nest dockviews within other dockviews.
If you wish to interact with the drop event from one dockview instance in another dockview instance you can implement the `showDndOverlay` and `onDidDrop` props on `DockviewReact`.
<NestedDockview />
### Example
hello
<DockviewNative2 />