Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 4 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
"@angular/core": "^17.0.0",
"@angular/platform-browser": "^17.0.0",
"@angular/platform-browser-dynamic": "^17.0.0",
"@nx/js": "^20.4.0",
"@nx/js": "^22.7.1",
"@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-terser": "^0.4.4",
"@rollup/plugin-typescript": "^11.1.5",
Expand All @@ -70,7 +70,7 @@
"jest-sonar-reporter": "^2.0.0",
"jsdom": "^23.0.1",
"ng-packagr": "^17.0.0",
"nx": "^20.4.0",
"nx": "^22.7.1",
"postcss": "^8.5.6",
"prettier": "^3.8.1",
"react": "^18.2.0",
Expand All @@ -84,15 +84,13 @@
"ts-node": "^10.9.2",
"tslib": "^2.6.2",
"typedoc": "^0.25.6",
"typescript": "^5.3.3",
"typescript": "~5.4.0",
"vite": "^5.1.5",
"vue": "^3.4.21",
"vue-sfc-loader": "^0.1.0",
"vue-tsc": "^2.0.5",
"zone.js": "~0.14.0"
},
"engines": {
"node": ">=18.0"
"node": "^22.13.0 || >=24.0.0"
},
"version": ""
}
4 changes: 3 additions & 1 deletion packages/dockview-vue/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@
"devDependencies": {
"@vue/test-utils": "^2.4.6",
"jsdom": "^29.0.2",
"vitest": "^4.1.4"
"vitest": "^4.1.4",
"vue": "^3.4.21",
"vue-tsc": "^2.0.5"
}
}
22 changes: 22 additions & 0 deletions packages/dockview-vue/src/__tests__/dockview.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,28 @@ describe('DockviewVue Component', () => {
);
});

test('should update tabGroupColors and tabGroupAccent when props change', async () => {
wrapper = mountDockview();
await flushPromises();

const api = (wrapper.emitted('ready')![0][0] as any).api as DockviewApi;
const updateSpy = vi.spyOn(api, 'updateOptions');

const tabGroupColors = [{ name: 'cyan', value: '#00ffff' }];
await wrapper.setProps({
tabGroupColors,
tabGroupAccent: 'off',
});
await nextTick();

expect(updateSpy).toHaveBeenCalledWith(
expect.objectContaining({ tabGroupColors })
);
expect(updateSpy).toHaveBeenCalledWith(
expect.objectContaining({ tabGroupAccent: 'off' })
);
});

test('should dispose api on unmount', async () => {
wrapper = mountDockview();
await flushPromises();
Expand Down
8 changes: 8 additions & 0 deletions packages/docs/docs/core/groups/popoutGroups.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,14 @@ choose a new location.

<CodeRunner id="dockview/popout-group"/>

## Overlays Inside Popout Windows

Floating overlays — including drag-target indicators, the tab context menu, and tab group chip popovers (color picker, rename) — render inside the DOM of the window that hosts them. When a group lives in a popout window, those overlays render in the popout window's document, not the parent. This means:

- Custom chip components and context menu components are mounted in the popout window's DOM tree.
- CSS that you load into the main page is automatically copied into popout windows; if you ship custom styles outside the standard import pipeline (for example, lazily injected stylesheets), make sure they are also available in the popout window.
- Event handlers and portals inside custom components should not assume the document is the main window — use the element's `ownerDocument` when you need a reference.

## Popout Window Events

`DockviewApi` exposes three events specific to popout windows:
Expand Down
37 changes: 37 additions & 0 deletions packages/docs/docs/core/panels/rendering.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -210,3 +210,40 @@ Toggling the checkbox in the live example shows the panel component being destro
react={RenderingDockview}
/>

## Visibility Lifecycle Hooks

When you implement a custom `IContentRenderer` (typically only relevant for vanilla TypeScript consumers), you can opt into two optional lifecycle hooks that fire when a panel transitions between hidden and visible. These hooks complement `api.onDidVisibilityChange` and run regardless of which `renderer` mode is in use:

- `onShow()` — fires when the panel becomes visible (active in its group, or when its group is unhidden).
- `onHide()` — fires when the panel becomes hidden (another tab in the group is selected, or the group is hidden).

```ts
import { IContentRenderer, GroupPanelPartInitParameters } from 'dockview-core';

class MyPanel implements IContentRenderer {
private readonly _element: HTMLElement;

get element() {
return this._element;
}

constructor() {
this._element = document.createElement('div');
}

init(parameters: GroupPanelPartInitParameters): void {}

onShow(): void {
// attach listeners, resume work, restart animations
}

onHide(): void {
// detach listeners, pause work, suspend timers
}

dispose(): void {}
}
```

Both hooks are optional. The framework wrappers (React, Vue, Angular) handle visibility internally — these hooks are primarily intended for vanilla `IContentRenderer` implementations and for framework adapters that need to detach/reattach DOM (for example, the Angular adapter uses them to detach and reattach `TemplateRef` views).

47 changes: 47 additions & 0 deletions packages/docs/docs/core/theming.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,53 @@ You can define your own `DockviewTheme` object and pass it to the `theme` proper

<DocRef declaration="DockviewTheme" />

## Theme properties

The `DockviewTheme` object accepts a number of optional properties beyond `name` / `className` that change layout behaviour rather than CSS-variable values. The most commonly used ones are listed below.

### `tabAnimation`

Controls how tabs animate when they are reordered or moved across groups.

- `'none'` (default) — no animation, tabs snap to their new position.
- `'smooth'` — animated FLIP transition between positions, including for vertical tab strips and cross-group moves.

```ts
import { themeAbyss } from 'dockview-react';

const theme = { ...themeAbyss, tabAnimation: 'smooth' as const };
```

### `tabGroupIndicator`

Controls how the indicator under a tab group's tabs is rendered.

- `'wrap'` (default) — a continuous coloured bar wraps the group's tabs.
- `'none'` — no indicator is drawn.

### `dndOverlayBorder`

Controls whether drop-target overlays (the highlight that appears when dragging a panel over a group) render an outline border. Themes can opt out for a flatter look. Boolean, default depends on the theme.

### `dndTabIndicator`

Controls how the drop position indicator within a tab strip is drawn while dragging a tab.

- `'line'` — a thin coloured line marks the insertion point.
- `'fill'` — the target slot is filled with the active accent colour.

### `edgeGroupCollapsedSize`

Per-theme override for the pixel size of an [edge group](/docs/core/groups/edgeGroups) when it is collapsed. The default differs between compact and spaced themes; override this when you need an exact rail size.

### `colorScheme`

Hint passed to the browser via the `color-scheme` CSS property — `'light'` or `'dark'`. Native form controls and scrollbars adapt accordingly.

## Interactive theme builder

The [live demo](/demo) includes an interactive theme builder that lets you tweak any CSS variable and the theme properties above, then copy the resulting theme object and CSS. This is the fastest way to author a custom theme without writing CSS by hand.


## Customizing Theme

Expand Down
2 changes: 1 addition & 1 deletion packages/docs/src/pages/examples.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ const COMPONENTS: ComponentGroup[] = [
{ name: 'resize', frameworks: ['react', 'vue'] },
{ name: 'resize-container', frameworks: ['react', 'vue'] },
{ name: 'scrollbars', frameworks: ['react', 'vue', 'typescript'] },
{ name: 'tab-groups', frameworks: ['react'] },
{ name: 'tab-groups', frameworks: ['react', 'vue', 'angular', 'typescript'] },
{ name: 'tabview', frameworks: ['react', 'vue', 'typescript'] },
{ name: 'update-parameters', frameworks: ['react', 'vue', 'typescript'] },
{ name: 'update-title', frameworks: ['react', 'vue', 'typescript'] },
Expand Down
31 changes: 31 additions & 0 deletions packages/docs/src/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,25 @@ const IconTheme = () => (
</svg>
);

const IconTabGroups = () => (
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<rect x="3" y="6" width="6" height="3" rx="1" />
<rect x="10" y="6" width="6" height="3" rx="1" />
<rect x="3" y="12" width="13" height="8" rx="1" />
<line x1="18" y1="6" x2="21" y2="6" />
<line x1="18" y1="9" x2="21" y2="9" />
</svg>
);

const IconEdgeGroups = () => (
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<rect x="3" y="3" width="18" height="18" rx="2" />
<line x1="3" y1="8" x2="21" y2="8" />
<line x1="8" y1="8" x2="8" y2="21" />
<line x1="16" y1="8" x2="16" y2="21" />
</svg>
);

// ─── Feature cards ────────────────────────────────────────────────────────────

const FEATURES = [
Expand Down Expand Up @@ -132,6 +151,18 @@ const FEATURES = [
description:
'Built-in themes with full CSS variable customization. Override individual properties or build your own theme from scratch.',
},
{
icon: <IconTabGroups />,
title: 'Tab Groups',
description:
'Cluster related tabs into named, color-coded groups within a tab strip. Collapse, drag, and reorder groups; bring your own chip renderer.',
},
{
icon: <IconEdgeGroups />,
title: 'Edge Groups',
description:
'IDE-style collapsible side panels docked to any edge of the layout. Toggle via the API, persist their state, and host any panel content.',
},
];

function FeatureCard({
Expand Down
119 changes: 119 additions & 0 deletions packages/docs/templates/dockview/tab-groups/angular/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import 'zone.js';
import '@angular/compiler';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { Component, Type, NgModule, Input } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import {
DockviewAngularModule,
DockviewReadyEvent,
themeAbyss,
} from 'dockview-angular';
import 'dockview-core/dist/styles/dockview.css';

@Component({
selector: 'default-panel',
template: `<div style="padding: 10px;">{{ title }}</div>`,
})
export class DefaultPanelComponent {
@Input() api: any;

get title() {
return this.api?.title || this.api?.id || 'Panel';
}
}

@Component({
selector: 'app-root',
template: `
<div style="height: 100%;">
<dv-dockview
[components]="components"
[theme]="theme"
[disableFloatingGroups]="true"
[getTabGroupChipContextMenuItems]="getTabGroupChipContextMenuItems"
(ready)="onReady($event)">
</dv-dockview>
</div>
`,
})
export class AppComponent {
components: Record<string, Type<any>>;
theme = { ...themeAbyss, tabAnimation: 'smooth' as const };
getTabGroupChipContextMenuItems = () =>
['rename', 'colorPicker'] as const;

constructor() {
this.components = {
default: DefaultPanelComponent,
};
}

onReady(event: DockviewReadyEvent) {
const api = event.api;

const titles = [
'Dashboard',
'Settings',
'Users',
'Analytics',
'Reports',
'Billing',
'Notifications',
'Logs',
];

const first = api.addPanel({
id: 'panel_1',
component: 'default',
title: titles[0],
});
for (let i = 1; i < titles.length; i++) {
api.addPanel({
id: `panel_${i + 1}`,
component: 'default',
title: titles[i],
});
}

const groupId = first.group.id;

const featureGroup = api.createTabGroup({
groupId,
label: 'Feature',
color: 'blue',
});
['panel_1', 'panel_2', 'panel_3', 'panel_4'].forEach((panelId) => {
api.addPanelToTabGroup({
groupId,
tabGroupId: featureGroup.id,
panelId,
});
});

const monitoringGroup = api.createTabGroup({
groupId,
label: 'Monitoring',
color: 'purple',
});
['panel_5', 'panel_7', 'panel_8'].forEach((panelId) => {
api.addPanelToTabGroup({
groupId,
tabGroupId: monitoringGroup.id,
panelId,
});
});
monitoringGroup.collapse();
}
}

@NgModule({
declarations: [AppComponent, DefaultPanelComponent],
imports: [BrowserModule, DockviewAngularModule],
providers: [],
bootstrap: [AppComponent],
})
export class AppModule {}

platformBrowserDynamic()
.bootstrapModule(AppModule)
.catch((err) => console.error(err));
Loading
Loading