vue-dockable-desktop 1.8.2 → 1.10.0

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/CHANGELOG.md CHANGED
@@ -11,6 +11,97 @@ correspondence lives, alongside the feature-by-feature map in [docs/PARITY.md](d
11
11
 
12
12
  ## [Unreleased]
13
13
 
14
+ ## [1.10.0] — 2026-10-06
15
+
16
+ **Parity: react-dockable-desktop 7.9.0.** Control over where users can move panels: per-kind rules and
17
+ an app-wide veto, all opt-in. Without them nothing changes, and the app's own calls are never
18
+ restricted. Same names as rdd 7.9.0.
19
+
20
+ ### Added
21
+
22
+ - **`canFloat` and `canDock` per kind**, next to `canDrag`, `canMinimize` and `canClose`.
23
+ `canFloat: false` keeps a panel in the grid: a tab drag can't end in a floating window, and
24
+ "Float Window" and the taskbar's "Maximize" are hidden. `canDock: false` keeps a panel floating:
25
+ dragging it offers no group, tab or edge targets.
26
+ - **`canDrop`, an app-wide veto.** `createWorkspace({ canDrop: ({ panelId, component, to }) => boolean })`
27
+ is asked for every place a dragged panel could go, and for the "Float" and "Maximize" menu items;
28
+ `to` is a `PanelDropTarget` (a group, an edge, or a float). A forbidden target isn't offered, and
29
+ it's asked again at release. Positions and corners are the ones the move applies, mirrored under
30
+ RTL. If it throws, the move is allowed and the error is logged. New types: `PanelDrop`,
31
+ `PanelDropTarget`.
32
+ - Both apply only to what the user does; the app's own calls always work.
33
+
34
+ ### Fixed
35
+
36
+ - **A drag built on `startPointerDrag` could outlive its element.** It ended on pointerup,
37
+ pointercancel and window blur, but not when the capturing element was removed mid-drag (an
38
+ overlay widget closed during its own drag), which left its listeners attached and its classes on
39
+ the page. It now also ends on a lost pointer capture, listened for on the document, where browsers
40
+ fire it; `onCancel` runs if given.
41
+
42
+ ### Docs
43
+
44
+ - Layout (Chapter 4): **Controlling where users can move panels**. Panels (Chapter 3) and the API
45
+ reference: the new options. `PARITY.md`: the rdd 7.9.0 trace.
46
+
47
+ ### Tests
48
+
49
+ - `test/components/releaseB.test.ts` (12 tests): `canFloat` (no float on nothing, no corners, no
50
+ "Float Window" or "Maximize" from a group, the app's `floatPanel` still works), `canDock` (no group
51
+ or edge targets, only corners), `canDrop` (its argument, vetoed zones and edges not offered, RTL
52
+ positions, tab insertion into a vetoed group, rules changed mid-drag for zones and tabs, a throwing
53
+ `canDrop`, no rules meaning every target), and `startPointerDrag` ending on a lost capture. Each
54
+ rule was seen failing with it broken. `api-surface.json` gains the two types.
55
+
56
+ ## [1.9.0] — 2026-10-06
57
+
58
+ **Parity: react-dockable-desktop 7.8.0.** More control over how the desktop looks and where panels
59
+ open, all opt-in: without the new options, nothing changes. Same names as rdd 7.8.0, in Vue's idiom.
60
+
61
+ ### Added
62
+
63
+ - **Your own empty-workspace view.** `<VddDesktop>` takes a `#empty-workspace` slot, shown while no
64
+ panel is docked in place of the built-in message: a welcome screen, a logo, "Open a file"
65
+ buttons. Floating windows still show over it, and a window can still be dropped onto it to dock.
66
+ Empty groups inside a split keep the built-in message.
67
+ - **Open a panel beside another.** `openPanel(id, component, { dockTo: { panel, position, size } })`
68
+ docks a new panel as a tab in that panel's group (`position: 'center'`) or in a new group split
69
+ off on one side, taking `size` (0.1–0.9) of the split. It wins over `initialTarget`. If the
70
+ target isn't docked, the panel is placed as usual and a warning says why.
71
+ - **State attributes for your CSS.** Tabs carry `data-vdd-selected`, `data-vdd-focused` and
72
+ `data-vdd-dirty`; floating windows carry `data-vdd-focused` and `data-vdd-maximized`. Each is
73
+ present only while it is true. The existing classes are unchanged.
74
+ - **Per-kind classes.** `className` and `tabClassName` in a kind's `defaultOptions` add your class
75
+ to every panel of that kind (on its content element, which moves with it) and to its tab.
76
+ - **`keepAlive: false`**, per kind: the component unmounts while the panel is an unselected tab or
77
+ minimised, and mounts afresh when shown. Its own state is lost each time, and in such a panel
78
+ `onUnmounted` means hidden, not closed; a guard it registered with `onBeforeClose` is inactive
79
+ while it is unmounted, though its dirty flag still applies. The default is unchanged: panels
80
+ stay mounted. Recorded as [ADR 0021](docs/decisions/0021-opt-in-unmount-while-hidden.md), which
81
+ amends 0002 for kinds that opt in.
82
+
83
+ ### Docs
84
+
85
+ - Theming (Chapter 10): **Your own empty-workspace view**, **Styling by state and by panel kind**,
86
+ and **Using it with Vuetify, PrimeVue, Bootstrap or Tailwind** (dark mode, the library's
87
+ z-index layers, why a framework popup opened inside one of the library's modals appears behind
88
+ it and the fixes, overriding styles). Layout (Chapter 4): **Opening beside another panel**.
89
+ Panels (Chapter 3): the new registration options and **Freeing a hidden panel**. Concepts
90
+ (Chapter 2) notes the `keepAlive: false` exception beside the zero-unmount guarantee.
91
+ - `PARITY.md`: the rdd 7.8.0 trace.
92
+
93
+ ### Tests
94
+
95
+ - `test/components/releaseA.test.ts` (15 tests): the empty-workspace slot (shown and gone as panels
96
+ dock and close; empty groups inside a split keep the built-in message), the state attributes on
97
+ tabs and windows, `dockTo` (sides, centre, size clamping, winning over `initialTarget`, the
98
+ fallback and its warning, no effect on an open panel), the per-kind classes (also after
99
+ floating), and `keepAlive: false` (unmount and fresh remount, the default unchanged, no leak
100
+ over 50 hide/show cycles). Each was seen failing with its feature broken.
101
+ - M13's browser tour gains a step that shows the playground's `#empty-workspace` view, so the new
102
+ `.vdd-empty-workspace` rule is checked against a real render (the gate first failed on it as a
103
+ dead rule, which is what it is for).
104
+
14
105
  ## [1.8.2] — 2026-10-06
15
106
 
16
107
  **Parity: react-dockable-desktop 7.7.3** (code health). Neither of rdd's last two releases needs a
@@ -15,7 +15,11 @@ type __VLS_Props = {
15
15
  /** Fallback icon for panels that register none. */
16
16
  defaultPanelIcon?: unknown;
17
17
  };
18
- declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
18
+ declare var __VLS_13: {};
19
+ type __VLS_Slots = {} & {
20
+ 'empty-workspace'?: (props: typeof __VLS_13) => any;
21
+ };
22
+ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
19
23
  taskbarContextMenu: (panelId: string, event: MouseEvent | PointerEvent) => any;
20
24
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
21
25
  onTaskbarContextMenu?: ((panelId: string, event: MouseEvent | PointerEvent) => any) | undefined;
@@ -25,5 +29,11 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
25
29
  taskbar: TaskbarVisibility;
26
30
  defaultPanelIcon: undefined;
27
31
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
32
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
28
33
  declare const _default: typeof __VLS_export;
29
34
  export default _default;
35
+ type __VLS_WithSlots<T, S> = T & {
36
+ new (): {
37
+ $slots: S;
38
+ };
39
+ };
@@ -2,6 +2,16 @@ import type { LayoutLeafNode } from '../types';
2
2
  type __VLS_Props = {
3
3
  leaf: LayoutLeafNode;
4
4
  };
5
- declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
5
+ declare var __VLS_16: {};
6
+ type __VLS_Slots = {} & {
7
+ 'empty-workspace'?: (props: typeof __VLS_16) => any;
8
+ };
9
+ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
10
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
6
11
  declare const _default: typeof __VLS_export;
7
12
  export default _default;
13
+ type __VLS_WithSlots<T, S> = T & {
14
+ new (): {
15
+ $slots: S;
16
+ };
17
+ };
@@ -3,6 +3,16 @@ type __VLS_Props = {
3
3
  node: LayoutNode;
4
4
  path: number[];
5
5
  };
6
- declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
6
+ declare var __VLS_9: {};
7
+ type __VLS_Slots = {} & {
8
+ 'empty-workspace'?: (props: typeof __VLS_9) => any;
9
+ };
10
+ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
11
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
7
12
  declare const _default: typeof __VLS_export;
8
13
  export default _default;
14
+ type __VLS_WithSlots<T, S> = T & {
15
+ new (): {
16
+ $slots: S;
17
+ };
18
+ };
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Docking rules (1.10.0): what the *user* may do with a panel. The kind's own `canFloat` /
3
+ * `canDock` first, then the workspace's `canDrop`. Never consulted by the app's own calls.
4
+ */
5
+ import type { PanelDropTarget } from '../types';
6
+ import type { Workspace } from './workspace';
7
+ type RuleHost = Pick<Workspace<never>, 'state' | 'registry' | 'config'>;
8
+ export declare function isDropAllowed(ws: RuleHost, panelId: string, to: PanelDropTarget): boolean;
9
+ export {};
@@ -8,6 +8,7 @@
8
8
  import type { ContextMenuItem } from './contextMenu';
9
9
  import type { PanelDefaultOptions } from './registry';
10
10
  import type { Label, MessageDescriptor } from '../types';
11
+ import type { Workspace } from './workspace';
11
12
  /**
12
13
  * Just enough of the workspace to build a menu, so this module does not depend on the store.
13
14
  *
@@ -19,6 +20,10 @@ interface MenuHost {
19
20
  format: (label: Label | undefined) => string;
20
21
  messages: Record<string, MessageDescriptor>;
21
22
  panelMenuItems: (id: string) => ContextMenuItem[];
23
+ /** For the docking rules (1.10.0): floating is a move they decide. */
24
+ state: Workspace<never>['state'];
25
+ registry: Workspace<never>['registry'];
26
+ config: Workspace<never>['config'];
22
27
  }
23
28
  export interface PanelMenuActions {
24
29
  float: () => void;
@@ -23,6 +23,19 @@ export interface PanelDefaultOptions {
23
23
  canMinimize?: boolean;
24
24
  /** Allow dragging the tab, which is also what allows floating by drag. @default true */
25
25
  canDrag?: boolean;
26
+ /**
27
+ * `false`: the user can't make this panel floating. A tab drag can't end in a floating window
28
+ * (dropped on nothing or on a corner, it stays where it was), and "Float Window" and the taskbar's
29
+ * "Maximize" are hidden. A window the app floated itself can still be moved. The app's own
30
+ * `floatPanel` always works. @default true (1.10.0)
31
+ */
32
+ canFloat?: boolean;
33
+ /**
34
+ * `false`: the user can't dock this panel. Dragging it offers no group, tab or edge targets
35
+ * (corners still pin a floating window), so it stays floating. The app's own docking calls always
36
+ * work; give the kind `initialTarget: 'floating'` so it opens floating. @default true (1.10.0)
37
+ */
38
+ canDock?: boolean;
26
39
  /** Show a letter tile instead of a live thumbnail in the taskbar hover preview. @default false */
27
40
  disableLivePreview?: boolean;
28
41
  /**
@@ -32,6 +45,22 @@ export interface PanelDefaultOptions {
32
45
  * @see docs/decisions/0014-preserve-scroll-and-focus.md
33
46
  */
34
47
  preserveScroll?: boolean;
48
+ /**
49
+ * Class added to each panel of this kind, on its own content element (`.vdd-panel-content`),
50
+ * which moves with the panel between groups, windows and the taskbar preview. (1.9.0)
51
+ */
52
+ className?: string;
53
+ /** Class added to the tab of each panel of this kind. (1.9.0) */
54
+ tabClassName?: string;
55
+ /**
56
+ * `false` unmounts the panel's component while it is hidden (an unselected tab, or minimised)
57
+ * and mounts it afresh when shown, to free what a heavy, rarely shown panel holds. Its own
58
+ * state is lost each time, and a guard it registered with `onBeforeClose` is not active while
59
+ * it is unmounted (its dirty flag still is). Its tab, title and lifecycle continue, and the
60
+ * taskbar shows a letter tile instead of a live preview. @default true
61
+ * @see docs/decisions/0021-opt-in-unmount-while-hidden.md
62
+ */
63
+ keepAlive?: boolean;
35
64
  }
36
65
  /** A registered panel kind. */
37
66
  export interface PanelRegistryEntry {
@@ -1,5 +1,5 @@
1
1
  import type { App, Component, ComputedRef, InjectionKey } from 'vue';
2
- import type { DirtyStateOptions, DropPosition, FloatAnchor, FloatingWindow, Label, LayoutNode, MessageDescriptor, MessageFormatter, PanelInfo, SplitDirection } from '../types';
2
+ import type { DirtyStateOptions, DropPosition, FloatAnchor, PanelDrop, FloatingWindow, Label, LayoutNode, MessageDescriptor, MessageFormatter, PanelInfo, SplitDirection } from '../types';
3
3
  import { PanelRegistry } from './registry';
4
4
  import type { PanelDefaultOptions } from './registry';
5
5
  import type { ContextMenuItem, ContextMenuRequest, ShowContextMenuOptions } from './contextMenu';
@@ -43,6 +43,14 @@ export interface WorkspaceConfig {
43
43
  * rendered elements, which is the part that can actually break.
44
44
  */
45
45
  classes?: HostClasses;
46
+ /**
47
+ * Veto where users can move panels (1.10.0). Called for each place a dragged panel could go, and
48
+ * for the built-in "Float" and "Maximize" menu items: return `false` and that target isn't offered
49
+ * and the move doesn't happen. It runs after the kind's own `canFloat` / `canDock`, and only for
50
+ * what the user does: the app's own calls always work. Keep it fast and pure; it runs while the
51
+ * pointer moves.
52
+ */
53
+ canDrop?: (drop: PanelDrop) => boolean;
46
54
  }
47
55
  /** Consumer classes added to the library's chrome. Every field is optional. */
48
56
  export interface HostClasses {
@@ -74,6 +82,18 @@ export interface OpenPanelOptions<P extends object = Record<string, unknown>> {
74
82
  props?: P;
75
83
  /** If another open panel of the same `component` has this key, focus that one instead. */
76
84
  dedupeKey?: string;
85
+ /**
86
+ * Dock the new panel beside an open, docked panel: in that panel's group (`position: 'center'`,
87
+ * as a tab) or in a new group split off on one side of it. `size` is the new group's share of
88
+ * that split, from 0.1 to 0.9 (the workspace's split ratio when omitted). Wins over
89
+ * `initialTarget`. If `panel` is not docked (not open, floating or minimised), the new panel is
90
+ * placed as usual and a warning says why. Applies only to a newly opened panel. (1.9.0)
91
+ */
92
+ dockTo?: {
93
+ panel: string;
94
+ position: DropPosition;
95
+ size?: number;
96
+ };
77
97
  }
78
98
  /** The live workspace state. */
79
99
  export interface WorkspaceState {