dockview-svelte 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -0
- package/README.md +480 -0
- package/dist/components/DefaultTab.svelte +55 -0
- package/dist/components/DefaultTab.svelte.d.ts +7 -0
- package/dist/components/Dockview.svelte +517 -0
- package/dist/components/Dockview.svelte.d.ts +113 -0
- package/dist/components/DvWidget.svelte +120 -0
- package/dist/components/DvWidget.svelte.d.ts +41 -0
- package/dist/components/Gridview.svelte +341 -0
- package/dist/components/Gridview.svelte.d.ts +73 -0
- package/dist/components/Paneview.svelte +307 -0
- package/dist/components/Paneview.svelte.d.ts +64 -0
- package/dist/components/Splitview.svelte +332 -0
- package/dist/components/Splitview.svelte.d.ts +70 -0
- package/dist/core/context.d.ts +62 -0
- package/dist/core/context.js +1 -0
- package/dist/core/effect-helpers.d.ts +7 -0
- package/dist/core/effect-helpers.js +23 -0
- package/dist/core/factory.svelte.d.ts +16 -0
- package/dist/core/factory.svelte.js +248 -0
- package/dist/core/gridview.svelte.d.ts +31 -0
- package/dist/core/gridview.svelte.js +177 -0
- package/dist/core/paneview-test-helpers.js +28 -0
- package/dist/core/paneview.svelte.d.ts +29 -0
- package/dist/core/paneview.svelte.js +186 -0
- package/dist/core/registry.d.ts +88 -0
- package/dist/core/registry.js +143 -0
- package/dist/core/splitview.svelte.d.ts +32 -0
- package/dist/core/splitview.svelte.js +179 -0
- package/dist/core/types.d.ts +424 -0
- package/dist/core/types.js +1 -0
- package/dist/core/utils.d.ts +12 -0
- package/dist/core/utils.js +59 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +11 -0
- package/package.json +66 -0
package/README.md
ADDED
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
# dockview-svelte
|
|
2
|
+
|
|
3
|
+
Svelte 5 (runes) wrapper around the [Dockview](https://dockview.dev) layout engine — docking panels, tab groups, drag-and-drop, floating groups, popouts, and serialization, with a Svelte-native API.
|
|
4
|
+
|
|
5
|
+
- **Widgets, not portals**: register Svelte components in a `widgets` map; the library mounts them into Dockview's content and tab slots via `mount`/`unmount`.
|
|
6
|
+
- **Shared `PanelState`**: each panel gets one reactive object shared by reference between its tab header and its content — `params`, `size`, `shown`/`visible`, `active`, `pinned`, `title`, `api`, plus a `custom` channel for app data (e.g. unread badges).
|
|
7
|
+
- **Typed `openPanel`**: per-widget params inference via `defineWidgets`.
|
|
8
|
+
- **CSS from the dependency**: the stylesheet comes straight from `dockview` — `import 'dockview/dist/styles/dockview.css'`. Themes (`themeAbyss`, `themeDark`, …) come from the same package.
|
|
9
|
+
- **Client-only**: the `DockviewComponent` is created in `onMount`; SSR renders an empty container.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
npm install dockview-svelte dockview svelte
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
`dockview` is a runtime dependency (it re-exports `dockview-core` and ships the stylesheet + themes). `svelte@^5` is a peer dependency.
|
|
18
|
+
|
|
19
|
+
## Quick start
|
|
20
|
+
|
|
21
|
+
```svelte
|
|
22
|
+
<script lang="ts">
|
|
23
|
+
import { Dockview, type DockviewHandle } from 'dockview-svelte';
|
|
24
|
+
import 'dockview/dist/styles/dockview.css';
|
|
25
|
+
import { themeAbyss, type SerializedDockview } from 'dockview';
|
|
26
|
+
import ChatPanel from './ChatPanel.svelte';
|
|
27
|
+
import ChatTab from './ChatTab.svelte';
|
|
28
|
+
|
|
29
|
+
const widgets = {
|
|
30
|
+
chat: { component: ChatPanel, tab: ChatTab, title: 'Chat' },
|
|
31
|
+
help: { component: HelpPanel }, // default tab, widget-key title fallback
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
let handle = $state<DockviewHandle<typeof widgets> | undefined>(undefined);
|
|
35
|
+
let layout = $state<SerializedDockview | undefined>(undefined);
|
|
36
|
+
</script>
|
|
37
|
+
|
|
38
|
+
<Dockview bind:handle bind:layout {widgets} options={{ theme: themeAbyss }} />
|
|
39
|
+
|
|
40
|
+
<button onclick={() => handle?.openPanel('chat', { params: { room: 'general' } })}>
|
|
41
|
+
New chat
|
|
42
|
+
</button>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Import the stylesheet once from `dockview` — the library does not force it.
|
|
46
|
+
|
|
47
|
+
## Widgets
|
|
48
|
+
|
|
49
|
+
A **widget** is a Svelte component definition; a **panel** is an instantiated widget (`widget + id + reactive params`).
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
const widgets = {
|
|
53
|
+
chat: { component: ChatPanel, tab: ChatTab, title: 'Chat' },
|
|
54
|
+
help: { component: HelpPanel },
|
|
55
|
+
};
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
| Field | Required | Notes |
|
|
59
|
+
| ----------- | -------- | ------------------------------------------------------------ |
|
|
60
|
+
| `component` | yes | Rendered in the panel body. Receives `{ state: PanelState }` |
|
|
61
|
+
| `tab` | no | Header override. Falls back to built-in `DefaultTab` |
|
|
62
|
+
| `title` | no | Default title. Falls back to the widget key |
|
|
63
|
+
|
|
64
|
+
Per-widget param types are inferred automatically from the `widgets` object
|
|
65
|
+
(the view components declare `const W` generics), so `openPanel` stays typed
|
|
66
|
+
with a plain object literal.
|
|
67
|
+
|
|
68
|
+
Or declare widgets inline with `<DvWidget>` children — same registry, no `widgets` prop:
|
|
69
|
+
|
|
70
|
+
```svelte
|
|
71
|
+
<Dockview bind:handle>
|
|
72
|
+
<DvWidget name="chat" title="Chat">
|
|
73
|
+
{#snippet children(state)}
|
|
74
|
+
<ChatPanel {state} />
|
|
75
|
+
{/snippet}
|
|
76
|
+
{#snippet tab(state)}
|
|
77
|
+
<ChatTab {state} />
|
|
78
|
+
{/snippet}
|
|
79
|
+
</DvWidget>
|
|
80
|
+
<DvWidget name="help">
|
|
81
|
+
{#snippet children(state)}
|
|
82
|
+
<HelpPanel {state} />
|
|
83
|
+
{/snippet}
|
|
84
|
+
</DvWidget>
|
|
85
|
+
</Dockview>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Each snippet receives the shared `state` object — the same object a registry
|
|
89
|
+
component gets as its `state` prop. `tab` is the Dockview tab override, `header`
|
|
90
|
+
the Paneview header override; the rest pass through. Works in all four layouts;
|
|
91
|
+
see `/demos/declarative`.
|
|
92
|
+
|
|
93
|
+
> **Type note:** snippet params default to `never` (Svelte can't infer them
|
|
94
|
+
> per-widget), so annotate them to get typed `state.params`:
|
|
95
|
+
> `{#snippet children(state: PanelState<{ id: number }>)}`.
|
|
96
|
+
>
|
|
97
|
+
> **Layout applicability:** `tab` only affects `<Dockview>`, `header` only
|
|
98
|
+
> `<Paneview>`, and `title` only `<Dockview>`/`<Paneview>`. Passing an
|
|
99
|
+
> inapplicable snippet warns in dev and is ignored.
|
|
100
|
+
|
|
101
|
+
## PanelState
|
|
102
|
+
|
|
103
|
+
One `$state` object per panel, created once and shared by reference between the tab and content mounts:
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
interface PanelState<P = Record<string, unknown>> {
|
|
107
|
+
params: P; // double-bound with dockview (both directions)
|
|
108
|
+
size: { width: number; height: number }; // rAF-throttled, only while `shown`
|
|
109
|
+
shown: boolean; // renderer onShow/onHide — content mounted as the active tab
|
|
110
|
+
visible: boolean; // dockview api.isVisible — gridview-level visibility
|
|
111
|
+
active: boolean; // api.isActive — write `true` to activate
|
|
112
|
+
focused: boolean; // api.isFocused (read-only)
|
|
113
|
+
pinned: boolean; // api.isPinned — two-way (setPinned)
|
|
114
|
+
groupActive: boolean; // api.isGroupActive (read-only)
|
|
115
|
+
api: DockviewPanelApi; // per-panel api (setTitle, close, updateParameters…)
|
|
116
|
+
title: string; // read-only mirror — rename via api.setTitle()
|
|
117
|
+
custom: Record<string, unknown>; // app channel, e.g. custom.unread = 3
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
`shown` (renderer-level, from `onShow`/`onHide`) and `visible` (dockview's
|
|
122
|
+
`api.isVisible`) are two distinct notions — see the table above. `active`,
|
|
123
|
+
`focused`, `pinned` and `groupActive` mirror dockview's per-panel `api.is*`
|
|
124
|
+
(and their `onDid*` events).
|
|
125
|
+
|
|
126
|
+
Widgets receive the whole object as a single `state` prop:
|
|
127
|
+
|
|
128
|
+
```svelte
|
|
129
|
+
<script lang="ts">
|
|
130
|
+
import type { PanelState } from 'dockview-svelte';
|
|
131
|
+
let { state }: { state: PanelState<{ room: string }> } = $props();
|
|
132
|
+
</script>
|
|
133
|
+
|
|
134
|
+
<h2>{state.title}</h2>
|
|
135
|
+
<p>room: {state.params.room}</p>
|
|
136
|
+
<p>size: {state.size.width} × {state.size.height}</p>
|
|
137
|
+
<button onclick={() => (state.custom.unread = Number(state.custom.unread ?? 0) + 1)}>
|
|
138
|
+
unread++
|
|
139
|
+
</button>
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
- `params` is two-way: mutating `state.params` calls `api.updateParameters`; dockview-side updates merge back in.
|
|
143
|
+
- `title` is owned by dockview — read `state.title`, write via `state.api.setTitle()`.
|
|
144
|
+
- `active` is writable (write `true` to activate; `false` is a no-op). `pinned` is two-way. The rest of the booleans are read-only.
|
|
145
|
+
- `custom` is the tab↔content channel: the content sets `state.custom.unread`, the tab badge reads it — same reference, no prop drilling.
|
|
146
|
+
|
|
147
|
+
## Opening panels
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
const panel = handle.openPanel('chat', {
|
|
151
|
+
title: 'General', // default: widgets.chat.title ?? 'chat'
|
|
152
|
+
params: { room: 'general' }, // typed per widget
|
|
153
|
+
// id, position, direction, size… pass through to dockview's addPanel
|
|
154
|
+
});
|
|
155
|
+
panel.state.custom.unread = 3;
|
|
156
|
+
panel.api.setTitle('Renamed');
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
`handle` also exposes the escape hatches:
|
|
160
|
+
|
|
161
|
+
- `handle.api` — the raw `DockviewApi` (`fromJSON`, `addFloatingGroup`, `undo`, …).
|
|
162
|
+
- `handle.registerWidget(key, def)` — register/override a widget at runtime without touching the `widgets` prop.
|
|
163
|
+
- `handle.float(target, options?)` — float an existing panel or group (`IDockviewPanel`, `DockviewGroupPanel`, or panel id string).
|
|
164
|
+
- `handle.popout(target, options?)` — pop a panel or group into its own browser window; resolves `true`/`false`.
|
|
165
|
+
- `handle.dockAll()` — dock every floating window back into the main grid.
|
|
166
|
+
- `handle.openPanel` throws for unknown widget keys and before mount.
|
|
167
|
+
|
|
168
|
+
## Layout persistence
|
|
169
|
+
|
|
170
|
+
`layout` is Dockview's serialized JSON (`toJSON`/`fromJSON` shape) — use it for both defaults and save/restore:
|
|
171
|
+
|
|
172
|
+
```svelte
|
|
173
|
+
<Dockview bind:handle bind:layout {widgets} />
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
localStorage.setItem('layout', JSON.stringify(layout)); // save
|
|
178
|
+
layout = JSON.parse(saved); // restore — applied via fromJSON
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
A `lastEmitted` + deep-equal guard prevents the `fromJSON → onDidLayoutChange → emit` feedback loop.
|
|
182
|
+
|
|
183
|
+
## Events
|
|
184
|
+
|
|
185
|
+
All 33 `DockviewApi` events are forwarded as component props (subscribed on mount, disposed on teardown), plus our own `onReady`:
|
|
186
|
+
|
|
187
|
+
`onReady`, `onDidLayoutChange`, `onDidLayoutFromJSON`, `onDidAddPanel`, `onDidRemovePanel`, `onDidAddGroup`, `onDidRemoveGroup`, `onDidActivePanelChange`, `onDidActiveGroupChange`, `onDidMovePanel`, `onWillDrop`, `onDidDrop`, `onWillDragPanel`, `onWillDragGroup`, `onWillMutateLayout`, `onDidMutateLayout`, `onWillShowOverlay`, `onUnhandledDragOver`, `onDidAddPopoutGroup`, `onDidRemovePopoutGroup`, `onDidPopoutGroupSizeChange`, `onDidPopoutGroupPositionChange`, `onDidOpenPopoutWindowFail`, `onDidCreateTabGroup`, `onDidDestroyTabGroup`, `onDidAddPanelToTabGroup`, `onDidRemovePanelFromTabGroup`, `onDidTabGroupChange`, `onDidTabGroupCollapsedChange`, `onDidPanelPinnedChange`, `onDidMaximizedGroupChange`, `onDidChangeHistory`, `onDidSnapFloat`, `onDidSnapTogether`.
|
|
188
|
+
|
|
189
|
+
```svelte
|
|
190
|
+
<Dockview {widgets} onDidActivePanelChange={(e) => console.log(e.panel?.id)} />
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Empty state (watermark)
|
|
194
|
+
|
|
195
|
+
With no panels open, `Dockview` shows the `watermark` prop — a plain Svelte
|
|
196
|
+
component receiving `{ openPanel }`. No dockview `IWatermarkRenderer` factory
|
|
197
|
+
involved; it renders as a child overlay, so it gets context automatically:
|
|
198
|
+
|
|
199
|
+
```svelte
|
|
200
|
+
<script lang="ts">
|
|
201
|
+
import type { WatermarkProps } from 'dockview-svelte';
|
|
202
|
+
|
|
203
|
+
let { openPanel }: WatermarkProps = $props();
|
|
204
|
+
</script>
|
|
205
|
+
|
|
206
|
+
<button onclick={() => openPanel('chat', { params: { room: 'general' } })}>
|
|
207
|
+
open panel
|
|
208
|
+
</button>
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
```svelte
|
|
212
|
+
<Dockview {widgets} watermark={EmptyWatermark} options={{ theme }} />
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
See `/demos/empty` for a live example (watermark button opens a panel).
|
|
216
|
+
|
|
217
|
+
> Tab context menus (`getTabContextMenuItems`) also pass through `options`, but
|
|
218
|
+
> need the `ContextMenu` module from `dockview-enterprise` — not covered here.
|
|
219
|
+
|
|
220
|
+
## Active panel / group
|
|
221
|
+
|
|
222
|
+
`bind:active` exposes the current active panel + group as a reactive object:
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
import type { ActiveState } from 'dockview-svelte';
|
|
226
|
+
let active = $state<ActiveState>({ panel: undefined, group: undefined });
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
```svelte
|
|
230
|
+
<Dockview bind:active {widgets} />
|
|
231
|
+
<!-- active.panel / active.group update reactively -->
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
For a widget reacting to its *own* activation, prefer `state.active` (writable).
|
|
235
|
+
|
|
236
|
+
## Floating windows
|
|
237
|
+
|
|
238
|
+
`bind:floating` mirrors the open floating windows reactively (`{ count, hasFloating }`) —
|
|
239
|
+
dragging, floating, or docking back all update it:
|
|
240
|
+
|
|
241
|
+
```ts
|
|
242
|
+
import type { FloatingState } from 'dockview-svelte';
|
|
243
|
+
let floating = $state<FloatingState>({ count: 0, hasFloating: false });
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
```svelte
|
|
247
|
+
<Dockview bind:floating {widgets} />
|
|
248
|
+
<!-- floating.count / floating.hasFloating update reactively -->
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
```ts
|
|
252
|
+
handle.float(panelId); // float an existing panel or group
|
|
253
|
+
handle.float(panel.api.group); // same via the group object
|
|
254
|
+
handle.dockAll(); // dock everything back
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Popouts mirror floating, via `bind:popout` and `handle.popout()`:
|
|
258
|
+
|
|
259
|
+
```ts
|
|
260
|
+
import type { PopoutState } from 'dockview-svelte';
|
|
261
|
+
let popout = $state<PopoutState>({ count: 0, hasPopout: false });
|
|
262
|
+
|
|
263
|
+
handle.popout(panelId); // -> Promise<boolean>, resolves false if the window failed
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
```svelte
|
|
267
|
+
<Dockview bind:floating bind:popout {widgets} />
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
See `/demos/floating` for a live example — each group header (via
|
|
271
|
+
`rightHeaderActions`) and each panel tab carries ⧉ float / ↗ popout buttons.
|
|
272
|
+
|
|
273
|
+
## Group header actions
|
|
274
|
+
|
|
275
|
+
Svelte components rendered into dockview's group-header slots — left of tabs,
|
|
276
|
+
right of tabs, or before everything. Each receives `{ containerApi, group, state }`
|
|
277
|
+
per group (mirroring dockview's `IGroupHeaderProps`, plus a reactive `state`);
|
|
278
|
+
set props win over the raw `options` factories:
|
|
279
|
+
|
|
280
|
+
```svelte
|
|
281
|
+
<script lang="ts">
|
|
282
|
+
import type { HeaderActionProps } from 'dockview-svelte';
|
|
283
|
+
|
|
284
|
+
// `group` is the concrete DockviewGroupPanel — no interface re-resolution.
|
|
285
|
+
let { containerApi, group, state }: HeaderActionProps = $props();
|
|
286
|
+
</script>
|
|
287
|
+
|
|
288
|
+
<button onclick={() => containerApi.addFloatingGroup(group)}>float</button>
|
|
289
|
+
{#if state.isCollapsed}
|
|
290
|
+
<span>collapsed</span>
|
|
291
|
+
{/if}
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
```svelte
|
|
295
|
+
<Dockview {widgets} rightHeaderActions={GroupActions} options={{ theme }} />
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
`state` is a reactive `GroupState` mirror (`isCollapsed`, `isPeeking`, `location`),
|
|
299
|
+
kept in sync with the group api's `onDid*` events — the same idiom as `PanelState`.
|
|
300
|
+
|
|
301
|
+
Panel-header (per-tab) buttons need no new API — a custom `tab` component already
|
|
302
|
+
receives the shared `state` (with `state.api.id`), and reads the parent api via
|
|
303
|
+
`getContext(DOCKVIEW_CONTEXT_KEY)` to call `addFloatingGroup` / `addPopoutGroup`.
|
|
304
|
+
See `/demos/floating` (`GroupHeaderActions.svelte` + `PanelHeaderTab.svelte`).
|
|
305
|
+
|
|
306
|
+
## Themes
|
|
307
|
+
|
|
308
|
+
Themes are plain objects from the `dockview` package — already installed, no extra setup:
|
|
309
|
+
|
|
310
|
+
```ts
|
|
311
|
+
import { themeAbyss, themeDark, themeLight, themeDracula } from 'dockview';
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
```svelte
|
|
315
|
+
<Dockview {widgets} options={{ theme: themeAbyss }} />
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
## Context (for widgets)
|
|
319
|
+
|
|
320
|
+
Descendant widgets can access the parent api and register widget types at runtime:
|
|
321
|
+
|
|
322
|
+
```ts
|
|
323
|
+
import { getContext } from 'svelte';
|
|
324
|
+
import { DOCKVIEW_CONTEXT_KEY, type DockviewContext } from 'dockview-svelte';
|
|
325
|
+
const ctx = getContext<DockviewContext>(DOCKVIEW_CONTEXT_KEY);
|
|
326
|
+
ctx.registerWidget('extra', { component: ExtraPanel });
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
The factory forwards this context to every widget mount, so `getContext` works
|
|
330
|
+
inside panel content and tabs (covered by `core/context.test.ts`).
|
|
331
|
+
`<DvWidget>` builds on it: each child registers a snippet-backed wrapper
|
|
332
|
+
component under its `name` (with optional `tab`/`header`/`title`), unregistered
|
|
333
|
+
on destroy.
|
|
334
|
+
|
|
335
|
+
## Splitview
|
|
336
|
+
|
|
337
|
+
A `<Splitview>` component mirrors the `Dockview` idiom for plain resizable split
|
|
338
|
+
panes — no tabs or headers. Same `widgets` registry, typed `openPanel`, reactive
|
|
339
|
+
`state`, and `bind:layout`/`bind:views`:
|
|
340
|
+
|
|
341
|
+
```svelte
|
|
342
|
+
<script lang="ts">
|
|
343
|
+
import { Splitview, defineSplitviewWidgets, type SplitviewHandle } from 'dockview-svelte';
|
|
344
|
+
import { Orientation } from 'dockview';
|
|
345
|
+
|
|
346
|
+
const widgets = defineSplitviewWidgets({
|
|
347
|
+
a: { component: PaneA },
|
|
348
|
+
b: { component: PaneB },
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
let handle = $state<SplitviewHandle<typeof widgets> | undefined>(undefined);
|
|
352
|
+
</script>
|
|
353
|
+
|
|
354
|
+
<Splitview bind:handle {widgets} options={{ orientation: Orientation.HORIZONTAL }} />
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
Widgets receive a `SplitviewState` (same shape as `PanelState` minus the
|
|
358
|
+
tab-only `title`/`shown`/`pinned`/`groupActive`), and `handle` exposes
|
|
359
|
+
`openPanel`, `removePanel(id)`, and `movePanel(from, to)`. See `/demos/splitview`.
|
|
360
|
+
|
|
361
|
+
## Gridview
|
|
362
|
+
|
|
363
|
+
A `<Gridview>` component mirrors the `Splitview` idiom for 2-D grid splits —
|
|
364
|
+
cells arranged in rows and columns, no tabs or headers. Same `widgets`
|
|
365
|
+
registry, typed `openPanel`, reactive `state`, and `bind:layout`/`bind:panels`:
|
|
366
|
+
|
|
367
|
+
```svelte
|
|
368
|
+
<script lang="ts">
|
|
369
|
+
import { Gridview, defineGridviewWidgets, type GridviewHandle } from 'dockview-svelte';
|
|
370
|
+
import { Orientation } from 'dockview';
|
|
371
|
+
|
|
372
|
+
const widgets = defineGridviewWidgets({
|
|
373
|
+
a: { component: CellA },
|
|
374
|
+
b: { component: CellB },
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
let handle = $state<GridviewHandle<typeof widgets> | undefined>(undefined);
|
|
378
|
+
</script>
|
|
379
|
+
|
|
380
|
+
<Gridview bind:handle {widgets} options={{ orientation: Orientation.HORIZONTAL }} />
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
Widgets receive a `GridviewState` (same shape as `SplitviewState`), and `handle`
|
|
384
|
+
exposes `openPanel`, `removePanel(id)`, `movePanel(id, { direction, reference, size? })`,
|
|
385
|
+
`setVisible(id, visible)`, and `setActive(id)`. New cells are positioned with
|
|
386
|
+
`position: { direction, referencePanel }` (direction `'left' | 'right' | 'above' |
|
|
387
|
+
'below' | 'within'`). `bind:panels` mirrors `api.panels`; `bind:activePanel`
|
|
388
|
+
mirrors `onDidActivePanelChange`. `orientation` defaults to `HORIZONTAL`.
|
|
389
|
+
See `/demos/gridview`.
|
|
390
|
+
|
|
391
|
+
## Paneview
|
|
392
|
+
|
|
393
|
+
A `<Paneview>` component mirrors the `Dockview` idiom for collapsible VS Code-style
|
|
394
|
+
sidebars — a vertical stack of panes, each with a body plus an optional custom
|
|
395
|
+
header sharing one reactive `state`. Same `widgets` registry, typed `openPanel`,
|
|
396
|
+
and `bind:layout`/`bind:panels`:
|
|
397
|
+
|
|
398
|
+
```svelte
|
|
399
|
+
<script lang="ts">
|
|
400
|
+
import { Paneview, definePaneviewWidgets, type PaneviewHandle } from 'dockview-svelte';
|
|
401
|
+
|
|
402
|
+
const widgets = definePaneviewWidgets({
|
|
403
|
+
a: { component: PaneA, header: PaneHeaderA, title: 'Pane A' },
|
|
404
|
+
b: { component: PaneB }, // default header (plain title text)
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
let handle = $state<PaneviewHandle<typeof widgets> | undefined>(undefined);
|
|
408
|
+
</script>
|
|
409
|
+
|
|
410
|
+
<Paneview bind:handle {widgets} />
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
Widgets receive a `PaneviewState` (`SplitviewState` plus `title` and two-way
|
|
414
|
+
`expanded`). Titles resolve through the same three-tier chain as `Dockview`
|
|
415
|
+
(explicit → per-widget → widget key). Omit `header` to use dockview's built-in
|
|
416
|
+
`DefaultHeader`. `handle` exposes `openPanel`, `removePanel(id)`,
|
|
417
|
+
`movePanel(from, to)`, `setVisible(id, visible)`, and `setExpanded(id, expanded)`.
|
|
418
|
+
See `/demos/paneview`.
|
|
419
|
+
|
|
420
|
+
## SSR
|
|
421
|
+
|
|
422
|
+
The component renders an empty `<div>` on the server and instantiates `DockviewComponent` in `onMount`. No action needed.
|
|
423
|
+
|
|
424
|
+
## API reference
|
|
425
|
+
|
|
426
|
+
| Export | Kind | Notes |
|
|
427
|
+
| ------ | ---- | ----- |
|
|
428
|
+
| `Dockview` | Component | `widgets`, `options`, `bind:layout`, `bind:handle`, `bind:active`, `bind:floating`, `bind:popout`, `watermark`, `left/right/prefixHeaderActions`, `children` (`<DvWidget>`) |
|
|
429
|
+
| `Splitview` | Component | `widgets`, `options`, `bind:layout`, `bind:handle`, `bind:views`, `children` (`<DvWidget>`) — no tabs/headers |
|
|
430
|
+
| `DvWidget` | Component | Declarative widget: `name`, `children(state)`, `tab(state)?`, `header(state)?`, `title?` — registers into the parent layout's registry |
|
|
431
|
+
| `DefaultTab` | Component | Built-in header (title + close) |
|
|
432
|
+
| `defineWidgets` / `WidgetRegistry` | Function / class | Typed registry construction / mutable registry |
|
|
433
|
+
| `defineSplitviewWidgets` / `SplitviewWidgetRegistry` | Function / class | Splitview registry (no tabs/titles) |
|
|
434
|
+
| `defineGridviewWidgets` / `GridviewWidgetRegistry` | Function / class | Gridview registry (no tabs/titles) |
|
|
435
|
+
| `definePaneviewWidgets` / `PaneviewWidgetRegistry` | Function / class | Paneview registry (body + optional header + title) |
|
|
436
|
+
| `Gridview` | Component | `widgets`, `options`, `bind:layout`, `bind:handle`, `bind:panels`, `bind:activePanel`, `children` (`<DvWidget>`) — no tabs/headers |
|
|
437
|
+
| `Paneview` | Component | `widgets`, `options`, `bind:layout`, `bind:handle`, `bind:panels`, `children` (`<DvWidget>`) — collapsible panes with optional custom headers |
|
|
438
|
+
| `DOCKVIEW_CONTEXT_KEY` / `DockviewContext` / `SplitviewContext` / `GridviewContext` / `PaneviewContext` | const / types | `api` + `registerWidget` + `unregisterWidget` for descendants |
|
|
439
|
+
| `WatermarkComponent` / `WatermarkProps` | types | `watermark` prop: `{ openPanel }` |
|
|
440
|
+
| `HeaderActionComponent` / `HeaderActionProps` | types | header-action props: `{ containerApi, group, state }` |
|
|
441
|
+
| `PanelState`, `PanelHandle`, `DockviewHandle`, `ActiveState`, `FloatingState`, `PopoutState`, `GroupState` | Types | Shared state, open result, bound handle, active panel/group, floating/popout windows, group header state |
|
|
442
|
+
| `SplitviewState`, `SplitviewPanelHandle`, `SplitviewHandle`, `SplitviewWidgets`, `SplitviewWidgetDefinition`, `SplitviewWidgetComponent`, `SplitviewParamsOf`, `SplitviewOpenPanelOptions`, `SplitviewOpenPanelFn` | Types | Splitview state, handle, registry and open typings |
|
|
443
|
+
| `GridviewState`, `GridviewPanelHandle`, `GridviewHandle`, `GridviewMoveOptions`, `GridviewWidgets`, `GridviewWidgetDefinition`, `GridviewWidgetComponent`, `GridviewParamsOf`, `GridviewOpenPanelOptions`, `GridviewOpenPanelFn` | Types | Gridview state, handle, registry and open typings |
|
|
444
|
+
| `PaneviewState`, `PaneviewPanelHandle`, `PaneviewHandle`, `PaneviewWidgets`, `PaneviewWidgetDefinition`, `PaneviewWidgetComponent`, `PaneviewHeaderComponent`, `PaneviewParamsOf`, `PaneviewOpenPanelOptions`, `PaneviewOpenPanelFn` | Types | Paneview state, handle, registry and open typings |
|
|
445
|
+
| `WidgetDefinition`, `Widgets`, `WidgetComponent`, `ParamsOf`, `OpenPanelOptions`, `OpenPanelFn` | Types | Registry and open typings |
|
|
446
|
+
|
|
447
|
+
## Developing
|
|
448
|
+
|
|
449
|
+
```sh
|
|
450
|
+
npm run dev # demo gallery (landing + /demos/*)
|
|
451
|
+
npm run check # svelte-check
|
|
452
|
+
npm run biome # lint + format check
|
|
453
|
+
npm run test:unit # vitest (registry, utils, factory, context, splitview, gridview, paneview, dvwidget)
|
|
454
|
+
npm run test:e2e # playwright (15 tests over the demo gallery)
|
|
455
|
+
npm run prepack # svelte-package + publint
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
## Demos
|
|
459
|
+
|
|
460
|
+
Landing page + one route per concept, each with live `Dockview` and its
|
|
461
|
+
highlighted source (Shiki, display-only):
|
|
462
|
+
|
|
463
|
+
| Route | Concept |
|
|
464
|
+
| ----- | ------- |
|
|
465
|
+
| `/demos/basic` | `openPanel` a widget |
|
|
466
|
+
| `/demos/params` | two-way reactive `params` |
|
|
467
|
+
| `/demos/custom-tab` | custom tab + shared `PanelState.custom` badge |
|
|
468
|
+
| `/demos/layout` | save / restore `bind:layout` |
|
|
469
|
+
| `/demos/themes` | theme switching (`abyss`/`dark`/`light`/`dracula`) |
|
|
470
|
+
| `/demos/events` | `bind:active` + event log |
|
|
471
|
+
| `/demos/floating` | `bind:floating` / `bind:popout` + group header float/popout |
|
|
472
|
+
| `/demos/empty` | `watermark` empty-state overlay |
|
|
473
|
+
| `/demos/splitview` | resizable split panes (no tabs/headers) |
|
|
474
|
+
| `/demos/gridview` | 2-D grid splits (no tabs/headers) |
|
|
475
|
+
| `/demos/paneview` | collapsible panes with optional custom headers |
|
|
476
|
+
| `/demos/declarative` | `<DvWidget>` children instead of the `widgets` prop |
|
|
477
|
+
|
|
478
|
+
## License
|
|
479
|
+
|
|
480
|
+
MIT
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { PanelState } from '../core/types.js'
|
|
3
|
+
|
|
4
|
+
let { state }: { state: PanelState } = $props()
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<div class="dv-svelte-default-tab">
|
|
8
|
+
<span class="dv-svelte-default-tab__title">{state.title}</span>
|
|
9
|
+
<button
|
|
10
|
+
type="button"
|
|
11
|
+
class="dv-svelte-default-tab__close"
|
|
12
|
+
aria-label="Close panel"
|
|
13
|
+
onclick={() => state.api.close()}
|
|
14
|
+
>
|
|
15
|
+
<span aria-hidden="true">×</span>
|
|
16
|
+
</button>
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
<style>
|
|
20
|
+
.dv-svelte-default-tab {
|
|
21
|
+
display: flex;
|
|
22
|
+
align-items: center;
|
|
23
|
+
gap: 0.5rem;
|
|
24
|
+
height: 100%;
|
|
25
|
+
padding: 0 0.25rem 0 0.75rem;
|
|
26
|
+
overflow: hidden;
|
|
27
|
+
white-space: nowrap;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.dv-svelte-default-tab__title {
|
|
31
|
+
flex: 1;
|
|
32
|
+
overflow: hidden;
|
|
33
|
+
text-overflow: ellipsis;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.dv-svelte-default-tab__close {
|
|
37
|
+
display: inline-flex;
|
|
38
|
+
align-items: center;
|
|
39
|
+
justify-content: center;
|
|
40
|
+
width: 1.25rem;
|
|
41
|
+
height: 1.25rem;
|
|
42
|
+
padding: 0;
|
|
43
|
+
border: none;
|
|
44
|
+
background: transparent;
|
|
45
|
+
border-radius: 0.25rem;
|
|
46
|
+
cursor: pointer;
|
|
47
|
+
color: inherit;
|
|
48
|
+
font-size: 1rem;
|
|
49
|
+
line-height: 1;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.dv-svelte-default-tab__close:hover {
|
|
53
|
+
background: var(--dv-hover-background-color, rgba(128, 128, 128, 0.2));
|
|
54
|
+
}
|
|
55
|
+
</style>
|