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.
Files changed (36) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +480 -0
  3. package/dist/components/DefaultTab.svelte +55 -0
  4. package/dist/components/DefaultTab.svelte.d.ts +7 -0
  5. package/dist/components/Dockview.svelte +517 -0
  6. package/dist/components/Dockview.svelte.d.ts +113 -0
  7. package/dist/components/DvWidget.svelte +120 -0
  8. package/dist/components/DvWidget.svelte.d.ts +41 -0
  9. package/dist/components/Gridview.svelte +341 -0
  10. package/dist/components/Gridview.svelte.d.ts +73 -0
  11. package/dist/components/Paneview.svelte +307 -0
  12. package/dist/components/Paneview.svelte.d.ts +64 -0
  13. package/dist/components/Splitview.svelte +332 -0
  14. package/dist/components/Splitview.svelte.d.ts +70 -0
  15. package/dist/core/context.d.ts +62 -0
  16. package/dist/core/context.js +1 -0
  17. package/dist/core/effect-helpers.d.ts +7 -0
  18. package/dist/core/effect-helpers.js +23 -0
  19. package/dist/core/factory.svelte.d.ts +16 -0
  20. package/dist/core/factory.svelte.js +248 -0
  21. package/dist/core/gridview.svelte.d.ts +31 -0
  22. package/dist/core/gridview.svelte.js +177 -0
  23. package/dist/core/paneview-test-helpers.js +28 -0
  24. package/dist/core/paneview.svelte.d.ts +29 -0
  25. package/dist/core/paneview.svelte.js +186 -0
  26. package/dist/core/registry.d.ts +88 -0
  27. package/dist/core/registry.js +143 -0
  28. package/dist/core/splitview.svelte.d.ts +32 -0
  29. package/dist/core/splitview.svelte.js +179 -0
  30. package/dist/core/types.d.ts +424 -0
  31. package/dist/core/types.js +1 -0
  32. package/dist/core/utils.d.ts +12 -0
  33. package/dist/core/utils.js +59 -0
  34. package/dist/index.d.ts +9 -0
  35. package/dist/index.js +11 -0
  36. 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">&times;</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>
@@ -0,0 +1,7 @@
1
+ import type { PanelState } from '../core/types.js';
2
+ type $$ComponentProps = {
3
+ state: PanelState;
4
+ };
5
+ declare const DefaultTab: import("svelte").Component<$$ComponentProps, {}, "">;
6
+ type DefaultTab = ReturnType<typeof DefaultTab>;
7
+ export default DefaultTab;