vue-dockable-desktop 1.0.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 +60 -0
- package/LICENSE +21 -0
- package/README.md +169 -0
- package/dist/components/VddConfirm.vue.d.ts +19 -0
- package/dist/components/VddContextMenu.vue.d.ts +35 -0
- package/dist/components/VddDesktop.vue.d.ts +28 -0
- package/dist/components/VddDragGhost.vue.d.ts +3 -0
- package/dist/components/VddDropZones.vue.d.ts +6 -0
- package/dist/components/VddEdgeZones.vue.d.ts +3 -0
- package/dist/components/VddFloatingWidget.vue.d.ts +51 -0
- package/dist/components/VddFloatingWindow.vue.d.ts +12 -0
- package/dist/components/VddLeafGroup.vue.d.ts +7 -0
- package/dist/components/VddModalHost.vue.d.ts +9 -0
- package/dist/components/VddModals.vue.d.ts +3 -0
- package/dist/components/VddOverlayFrame.vue.d.ts +28 -0
- package/dist/components/VddPanelMount.vue.d.ts +8 -0
- package/dist/components/VddPanelOverlay.vue.d.ts +13 -0
- package/dist/components/VddPanelSlot.vue.d.ts +9 -0
- package/dist/components/VddPanelToolbar.vue.d.ts +26 -0
- package/dist/components/VddSecondarySidebar.vue.d.ts +16 -0
- package/dist/components/VddSidePanelHost.vue.d.ts +10 -0
- package/dist/components/VddSidePanels.vue.d.ts +12 -0
- package/dist/components/VddSidebar.vue.d.ts +54 -0
- package/dist/components/VddSidebarDrawer.vue.d.ts +51 -0
- package/dist/components/VddSidebarRail.vue.d.ts +22 -0
- package/dist/components/VddSidebarTabScope.vue.d.ts +22 -0
- package/dist/components/VddTaskbar.vue.d.ts +17 -0
- package/dist/components/VddTaskbarPreview.vue.d.ts +24 -0
- package/dist/components/VddToastIcon.vue.d.ts +8 -0
- package/dist/components/VddToastItem.vue.d.ts +18 -0
- package/dist/components/VddToasts.vue.d.ts +36 -0
- package/dist/components/VddToolbar.vue.d.ts +20 -0
- package/dist/components/VddToolbarButton.vue.d.ts +25 -0
- package/dist/components/VddToolbarCenter.vue.d.ts +13 -0
- package/dist/components/VddToolbarGroupButton.vue.d.ts +8 -0
- package/dist/components/VddToolbarItem.vue.d.ts +13 -0
- package/dist/components/VddToolbarSearch.vue.d.ts +27 -0
- package/dist/components/VddToolbarSeparator.vue.d.ts +3 -0
- package/dist/components/VddToolbarSpacer.vue.d.ts +3 -0
- package/dist/components/VddToolbarToggle.vue.d.ts +32 -0
- package/dist/components/VddWorkspaceGrid.vue.d.ts +8 -0
- package/dist/composables/useColorScheme.d.ts +19 -0
- package/dist/composables/useContextMenu.d.ts +29 -0
- package/dist/composables/useContributions.d.ts +48 -0
- package/dist/composables/useDragDock.d.ts +59 -0
- package/dist/composables/useOverlayHost.d.ts +30 -0
- package/dist/composables/useOverlays.d.ts +51 -0
- package/dist/composables/usePanel.d.ts +82 -0
- package/dist/composables/usePanelDom.d.ts +7 -0
- package/dist/composables/usePanelOverlay.d.ts +35 -0
- package/dist/composables/useSidebar.d.ts +15 -0
- package/dist/composables/useToolbar.d.ts +9 -0
- package/dist/composables/useWorkspace.d.ts +18 -0
- package/dist/core/anchorGeometry.d.ts +39 -0
- package/dist/core/contextMenu.d.ts +74 -0
- package/dist/core/contributions.d.ts +56 -0
- package/dist/core/dragResize.d.ts +67 -0
- package/dist/core/eventBus.d.ts +73 -0
- package/dist/core/layoutTree.d.ts +96 -0
- package/dist/core/messages.d.ts +117 -0
- package/dist/core/overlayState.d.ts +64 -0
- package/dist/core/overlays.d.ts +114 -0
- package/dist/core/panelDom.d.ts +79 -0
- package/dist/core/panelMenu.d.ts +45 -0
- package/dist/core/panelOverlay.d.ts +121 -0
- package/dist/core/registry.d.ts +61 -0
- package/dist/core/serializable.d.ts +26 -0
- package/dist/core/serialize.d.ts +32 -0
- package/dist/core/sidebarTypes.d.ts +102 -0
- package/dist/core/stretch.d.ts +47 -0
- package/dist/core/toast.d.ts +474 -0
- package/dist/core/toolbarState.d.ts +13 -0
- package/dist/core/toolbarTypes.d.ts +99 -0
- package/dist/core/workspace.d.ts +222 -0
- package/dist/index.cjs +5 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +64 -0
- package/dist/index.js +4851 -0
- package/dist/index.js.map +1 -0
- package/dist/styles.css +3944 -0
- package/dist/types.d.ts +145 -0
- package/package.json +102 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { ComputedRef, InjectionKey, Ref } from 'vue';
|
|
2
|
+
import type { ContainerType, DirtyStateOptions, Label } from '../types';
|
|
3
|
+
/** What a panel's container tells it about itself. @internal */
|
|
4
|
+
export interface PanelContext {
|
|
5
|
+
id: string;
|
|
6
|
+
containerType: Ref<ContainerType>;
|
|
7
|
+
/** Set by a container that is not the workspace (a modal, a side panel). */
|
|
8
|
+
close?: (options?: {
|
|
9
|
+
force?: boolean;
|
|
10
|
+
}) => void | Promise<void>;
|
|
11
|
+
setTitle?: (title: Label) => void;
|
|
12
|
+
setIcon?: (icon: unknown) => void;
|
|
13
|
+
setDirty?: (dirty: boolean, options?: DirtyStateOptions) => void;
|
|
14
|
+
size?: Ref<{
|
|
15
|
+
width: number;
|
|
16
|
+
height: number;
|
|
17
|
+
} | null>;
|
|
18
|
+
}
|
|
19
|
+
export declare const PANEL_KEY: InjectionKey<PanelContext>;
|
|
20
|
+
/** @internal — used by containers to tell their content which panel it is. */
|
|
21
|
+
export declare function providePanel(context: PanelContext): void;
|
|
22
|
+
/** What {@link usePanel} returns. */
|
|
23
|
+
export interface UsePanelReturn {
|
|
24
|
+
/** This panel's instance id. */
|
|
25
|
+
id: string;
|
|
26
|
+
/** Live title. */
|
|
27
|
+
title: ComputedRef<Label>;
|
|
28
|
+
/** Whether this is the globally active panel — the one contributions are read from. */
|
|
29
|
+
isActive: ComputedRef<boolean>;
|
|
30
|
+
/** Whether this panel is minimised. It is still mounted and still running. */
|
|
31
|
+
isMinimized: ComputedRef<boolean>;
|
|
32
|
+
/** Whether this panel is a floating window. */
|
|
33
|
+
isFloating: ComputedRef<boolean>;
|
|
34
|
+
/** Where this panel is rendered. */
|
|
35
|
+
containerType: ComputedRef<ContainerType>;
|
|
36
|
+
/** The panel's rendered size, or `null` before it has been laid out. */
|
|
37
|
+
size: ComputedRef<{
|
|
38
|
+
width: number;
|
|
39
|
+
height: number;
|
|
40
|
+
} | null>;
|
|
41
|
+
/** Unsaved changes. */
|
|
42
|
+
dirty: ComputedRef<boolean>;
|
|
43
|
+
setTitle: (title: Label) => void;
|
|
44
|
+
setIcon: (icon: unknown) => void;
|
|
45
|
+
setDirty: (dirty: boolean, options?: DirtyStateOptions) => void;
|
|
46
|
+
/** Close, honouring dirty state and any `onBeforeClose` guard. */
|
|
47
|
+
close: (options?: {
|
|
48
|
+
force?: boolean;
|
|
49
|
+
}) => void | Promise<void>;
|
|
50
|
+
minimize: () => void;
|
|
51
|
+
/**
|
|
52
|
+
* Veto a close. Return `false` (or a promise of it) to block.
|
|
53
|
+
* Registered in `setup` and disposed with the component — nothing to unsubscribe.
|
|
54
|
+
*/
|
|
55
|
+
onBeforeClose: (guard: () => boolean | Promise<boolean>) => void;
|
|
56
|
+
/**
|
|
57
|
+
* Contribute this panel's live state to `saveLayout()`, pulled fresh on every save — for
|
|
58
|
+
* state that accumulates after opening and so cannot be captured by open-time props.
|
|
59
|
+
* Must be synchronous and JSON-serialisable. Disposed with the component.
|
|
60
|
+
*/
|
|
61
|
+
onSaveState: (provider: () => unknown) => void;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* A panel's view of itself and its container.
|
|
65
|
+
*
|
|
66
|
+
* Everything that describes *state* is a ref, so you react with the tools you already use:
|
|
67
|
+
*
|
|
68
|
+
* ```ts
|
|
69
|
+
* const { isActive, size } = usePanel()
|
|
70
|
+
* watch(isActive, active => active && editor.focus())
|
|
71
|
+
* watch(size, ({ width, height }) => chart.resize(width, height))
|
|
72
|
+
* ```
|
|
73
|
+
*
|
|
74
|
+
* react-dockable-desktop exposed six subscription methods plus two size APIs for this,
|
|
75
|
+
* because React cannot hand a component live state. Those are all refs here
|
|
76
|
+
* (docs/decisions/0006-refs-over-subscriptions.md).
|
|
77
|
+
*
|
|
78
|
+
* Works outside any container too: the refs then report a `'standalone'` panel and the
|
|
79
|
+
* actions are no-ops with a development warning, so a panel component can be rendered on
|
|
80
|
+
* its own — in a test, or a storybook — without special-casing.
|
|
81
|
+
*/
|
|
82
|
+
export declare function usePanel(): UsePanelReturn;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { InjectionKey } from 'vue';
|
|
2
|
+
import type { PanelDomCache } from '../core/panelDom';
|
|
3
|
+
export declare const PANEL_DOM_KEY: InjectionKey<PanelDomCache>;
|
|
4
|
+
/** @internal — `<VddDesktop>` owns the cache and shares it with the slots inside it. */
|
|
5
|
+
export declare function providePanelDom(cache: PanelDomCache): void;
|
|
6
|
+
/** @internal */
|
|
7
|
+
export declare function usePanelDom(): PanelDomCache | null;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { ComputedRef } from 'vue';
|
|
2
|
+
import type { ManagedWidget, PanelOverlayStore } from '../core/overlayState';
|
|
3
|
+
/**
|
|
4
|
+
* The overlay a widget or toolbar is inside.
|
|
5
|
+
*
|
|
6
|
+
* @throws if used outside a `<VddPanelOverlay>`.
|
|
7
|
+
* @internal — applications use {@link useFloatingWidgets}.
|
|
8
|
+
*/
|
|
9
|
+
export declare function usePanelOverlay(): PanelOverlayStore;
|
|
10
|
+
/** Same, but `null` outside an overlay — for components that work either way. */
|
|
11
|
+
export declare function usePanelOverlayOptional(): PanelOverlayStore | null;
|
|
12
|
+
/**
|
|
13
|
+
* Open and close floating widgets by id, without declaring each one in the template.
|
|
14
|
+
*
|
|
15
|
+
* For widgets whose existence is data — one per selected feature, one per running job — where
|
|
16
|
+
* writing a `<VddFloatingWidget>` per case is not possible.
|
|
17
|
+
*
|
|
18
|
+
* ```ts
|
|
19
|
+
* const widgets = useFloatingWidgets()
|
|
20
|
+
* widgets.open('feature-42', {
|
|
21
|
+
* title: 'Feature 42', component: FeatureInfo, props: { id: 42 }, anchor: 'top-right',
|
|
22
|
+
* })
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* A widget placed in the template with `<VddFloatingWidget>` is the simpler option and
|
|
26
|
+
* behaves identically otherwise.
|
|
27
|
+
*/
|
|
28
|
+
export declare function useFloatingWidgets(): {
|
|
29
|
+
/** Ids of the currently open managed widgets, in the order they were opened. */
|
|
30
|
+
openIds: ComputedRef<string[]>;
|
|
31
|
+
open: (id: string, widget: ManagedWidget) => void;
|
|
32
|
+
close: (id: string) => void;
|
|
33
|
+
closeAll: () => void;
|
|
34
|
+
isOpen: (id: string) => boolean;
|
|
35
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { SidebarContext, SidebarTabContext } from '../core/sidebarTypes';
|
|
2
|
+
/**
|
|
3
|
+
* Control the sidebar from anywhere inside it — including from a panel rendered in the
|
|
4
|
+
* workspace, since the workspace is the sidebar's own content.
|
|
5
|
+
*
|
|
6
|
+
* @throws if used outside a `<VddSidebar>`.
|
|
7
|
+
*/
|
|
8
|
+
export declare function useSidebar(): SidebarContext;
|
|
9
|
+
/**
|
|
10
|
+
* Control scoped to the tab you are inside: close the drawer, re-open this tab, or switch to
|
|
11
|
+
* another one, without having to know which tab you are.
|
|
12
|
+
*
|
|
13
|
+
* @throws if used outside a sidebar tab's content.
|
|
14
|
+
*/
|
|
15
|
+
export declare function useSidebarTab(): SidebarTabContext;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ToolbarState } from '../core/toolbarState';
|
|
2
|
+
/**
|
|
3
|
+
* Toolbar selection state — which item is active in each radio group, and which toggles are
|
|
4
|
+
* on — for items the caller has not taken control of.
|
|
5
|
+
*
|
|
6
|
+
* Reachable anywhere `useWorkspace()` is, so a panel can read or set the active tool without
|
|
7
|
+
* the toolbar having to pass anything down. rdd needed a `<ToolbarProvider>` in the tree.
|
|
8
|
+
*/
|
|
9
|
+
export declare function useToolbar(): ToolbarState;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Workspace } from '../core/workspace';
|
|
2
|
+
/**
|
|
3
|
+
* The workspace, from anywhere inside an app that has `app.use(workspace)`.
|
|
4
|
+
*
|
|
5
|
+
* Returns refs and functions, so destructuring keeps reactivity — the shape `storeToRefs`
|
|
6
|
+
* exists to produce in Pinia:
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* const { activePanelId, panels, openPanel } = useWorkspace()
|
|
10
|
+
* ```
|
|
11
|
+
*
|
|
12
|
+
* There is no selector argument. react-dockable-desktop needed one to control re-renders;
|
|
13
|
+
* in Vue a `computed()` is both more capable and the idiom you already know.
|
|
14
|
+
*
|
|
15
|
+
* `subscribe()` is wrapped so a subscription made during `setup` is disposed with the
|
|
16
|
+
* component. Outside a scope it behaves exactly like `workspace.subscribe`.
|
|
17
|
+
*/
|
|
18
|
+
export declare function useWorkspace<TEvents extends Record<string, unknown> = Record<string, unknown>>(): Workspace<TEvents>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { FloatAnchor } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Mirrors a physical workspace corner to its horizontal opposite.
|
|
4
|
+
*
|
|
5
|
+
* Used only to translate a physically-hovered corner (raw pointer/screen
|
|
6
|
+
* position, which CSS cannot reason about) into the logical `FloatAnchor`
|
|
7
|
+
* value stored on a `FloatingWindow` under RTL. Render-time positioning
|
|
8
|
+
* should use CSS logical properties (`insetInlineStart`/`insetInlineEnd`)
|
|
9
|
+
* driven by the element's `dir` attribute instead of calling this.
|
|
10
|
+
*/
|
|
11
|
+
export declare function flipZoneHorizontal(zone: FloatAnchor): FloatAnchor;
|
|
12
|
+
/** A floating window's stored box, as numbers. */
|
|
13
|
+
export interface FloatingRect {
|
|
14
|
+
x: number;
|
|
15
|
+
y: number;
|
|
16
|
+
width: number;
|
|
17
|
+
height: number;
|
|
18
|
+
}
|
|
19
|
+
/** Smallest a clamp will shrink a window to, so it never becomes ungrabbable. */
|
|
20
|
+
export declare const MIN_CLAMP_W = 200;
|
|
21
|
+
export declare const MIN_CLAMP_H = 150;
|
|
22
|
+
/**
|
|
23
|
+
* Where a floating window has to move to stay reachable inside a shrinking workspace.
|
|
24
|
+
*
|
|
25
|
+
* Returns the clamped rect, or the **same object** when nothing needed to change. That
|
|
26
|
+
* identity is the point: the caller watches the window list deeply and writes back to it, so
|
|
27
|
+
* writing on "a bound was exceeded" rather than on "a value changed" re-triggers the watcher
|
|
28
|
+
* forever. Both clamps have a floor, so in a workspace narrower than that floor the clamped
|
|
29
|
+
* value still fails its own condition — and the rewrite loop that produced was found by
|
|
30
|
+
* M13's browser tour as "Maximum recursive updates exceeded".
|
|
31
|
+
*
|
|
32
|
+
* Size is clamped for every window. Position is clamped only for a free-floating one: an
|
|
33
|
+
* anchored window is placed entirely by its anchor and reading direction, so its stored x/y
|
|
34
|
+
* do not affect where it appears.
|
|
35
|
+
*/
|
|
36
|
+
export declare function clampFloatingRect(rect: FloatingRect, view: {
|
|
37
|
+
width: number;
|
|
38
|
+
height: number;
|
|
39
|
+
}, anchored: boolean): FloatingRect;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context-menu item shapes.
|
|
3
|
+
*
|
|
4
|
+
* Names and fields are kept identical to react-dockable-desktop's, so menu definitions port
|
|
5
|
+
* across unchanged — a menu is data, and this is the one part of the API where a React
|
|
6
|
+
* app's arrays can be reused verbatim.
|
|
7
|
+
*/
|
|
8
|
+
import type { Component } from 'vue';
|
|
9
|
+
import type { Label } from '../types';
|
|
10
|
+
/** The optional checkbox column on a simple item. */
|
|
11
|
+
export interface ContextMenuCheckbox {
|
|
12
|
+
/** Whether the checkbox column renders at all. @default true */
|
|
13
|
+
active?: boolean;
|
|
14
|
+
/** Whether the item is interactive. Prefer the item's own `disabled`. @default true */
|
|
15
|
+
enabled?: boolean;
|
|
16
|
+
/** Current checked state. */
|
|
17
|
+
value: boolean;
|
|
18
|
+
}
|
|
19
|
+
/** A normal menu item. */
|
|
20
|
+
export interface ContextMenuSimpleItem {
|
|
21
|
+
label: Label;
|
|
22
|
+
icon?: Component;
|
|
23
|
+
/** Tooltip. */
|
|
24
|
+
title?: Label;
|
|
25
|
+
checkbox?: ContextMenuCheckbox;
|
|
26
|
+
action?: () => void;
|
|
27
|
+
/** Test hook, rendered as `data-cy-action`. */
|
|
28
|
+
cyAction?: string;
|
|
29
|
+
disabled?: boolean;
|
|
30
|
+
}
|
|
31
|
+
/** A divider. */
|
|
32
|
+
export interface ContextMenuSeparator {
|
|
33
|
+
separator: true;
|
|
34
|
+
}
|
|
35
|
+
/** An item that opens a submenu. One level deep, as in rdd. */
|
|
36
|
+
export interface ContextMenuSubMenu {
|
|
37
|
+
label: Label;
|
|
38
|
+
title?: Label;
|
|
39
|
+
items?: ContextMenuItem[];
|
|
40
|
+
}
|
|
41
|
+
export type ContextMenuItem = ContextMenuSimpleItem | ContextMenuSeparator | ContextMenuSubMenu;
|
|
42
|
+
/** What to show, and where. */
|
|
43
|
+
export interface ShowContextMenuOptions {
|
|
44
|
+
/** Position from an event. Takes precedence over `x`/`y`. */
|
|
45
|
+
event?: MouseEvent | PointerEvent | TouchEvent;
|
|
46
|
+
x?: number;
|
|
47
|
+
y?: number;
|
|
48
|
+
items: ContextMenuItem[];
|
|
49
|
+
}
|
|
50
|
+
export declare const isSeparator: (item: ContextMenuItem) => item is ContextMenuSeparator;
|
|
51
|
+
export declare const isSubMenu: (item: ContextMenuItem) => item is ContextMenuSubMenu;
|
|
52
|
+
/** Where a menu should open, from whatever the caller supplied. */
|
|
53
|
+
export declare function menuPosition(options: ShowContextMenuOptions): {
|
|
54
|
+
x: number;
|
|
55
|
+
y: number;
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Keep a menu inside the viewport.
|
|
59
|
+
*
|
|
60
|
+
* Pure, so the clamping is testable without layout: the caller measures, this decides.
|
|
61
|
+
*/
|
|
62
|
+
export declare function clampToViewport(position: {
|
|
63
|
+
x: number;
|
|
64
|
+
y: number;
|
|
65
|
+
}, size: {
|
|
66
|
+
width: number;
|
|
67
|
+
height: number;
|
|
68
|
+
}, viewport: {
|
|
69
|
+
width: number;
|
|
70
|
+
height: number;
|
|
71
|
+
}, padding?: number): {
|
|
72
|
+
x: number;
|
|
73
|
+
y: number;
|
|
74
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { Component, ComputedRef } from 'vue';
|
|
2
|
+
import type { SidebarTab } from './sidebarTypes';
|
|
3
|
+
import type { ToolbarItem } from './toolbarTypes';
|
|
4
|
+
/** One named section a panel contributes to the app's sidebar while it is active. */
|
|
5
|
+
export interface PanelSidebarSection {
|
|
6
|
+
id: string;
|
|
7
|
+
label: string;
|
|
8
|
+
icon?: Component;
|
|
9
|
+
/** Rendered as the section's content. */
|
|
10
|
+
component: Component;
|
|
11
|
+
props?: Record<string, unknown>;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* What a panel publishes. Both fields are optional and independent — a panel may contribute
|
|
15
|
+
* only toolbar items, only sections, both, or nothing.
|
|
16
|
+
*
|
|
17
|
+
* The library assigns no meaning to either: what "a toolbar item" is for is the application's
|
|
18
|
+
* decision, and neither `<VddToolbar>` nor `<VddSidebar>` reads this on its own. The shell
|
|
19
|
+
* merges it into its own `items`/`tabs`.
|
|
20
|
+
*/
|
|
21
|
+
export interface PanelContribution {
|
|
22
|
+
toolbarItems?: ToolbarItem[];
|
|
23
|
+
sidebarSections?: PanelSidebarSection[];
|
|
24
|
+
}
|
|
25
|
+
export interface Contributions {
|
|
26
|
+
/**
|
|
27
|
+
* Publish for one panel, replacing anything it published before. The returned function
|
|
28
|
+
* withdraws it — but only if nothing has re-published for that id since, so a republish
|
|
29
|
+
* during a component update cannot be undone by the previous registration's cleanup.
|
|
30
|
+
*/
|
|
31
|
+
publish(panelId: string, contribution: PanelContribution): () => void;
|
|
32
|
+
/** What one panel has published, or `null`. */
|
|
33
|
+
get(panelId: string): PanelContribution | null;
|
|
34
|
+
/** Every panel that has published something. */
|
|
35
|
+
ids(): string[];
|
|
36
|
+
}
|
|
37
|
+
export declare function createContributions(): Contributions;
|
|
38
|
+
/**
|
|
39
|
+
* Turn a contributed section into a sidebar tab.
|
|
40
|
+
*
|
|
41
|
+
* `eagerMount` and `preserveState` are deliberately left unset: a contribution exists only
|
|
42
|
+
* while its panel is mounted *and* active, so neither has anything to mean here.
|
|
43
|
+
*/
|
|
44
|
+
export declare function sectionToTab(section: PanelSidebarSection, fallbackIcon?: Component): SidebarTab;
|
|
45
|
+
/**
|
|
46
|
+
* Append the active panel's contributed items to a static list, behind a separator.
|
|
47
|
+
*
|
|
48
|
+
* A plain function of a `computed`, not a hook — the state is on the workspace, so there is
|
|
49
|
+
* nothing to subscribe to. rdd needed `useMergedToolbarItems()` because reading the store
|
|
50
|
+
* required `useSyncExternalStore`.
|
|
51
|
+
*/
|
|
52
|
+
export declare function mergeToolbarItems(staticItems: ToolbarItem[], contribution: PanelContribution | null): ToolbarItem[];
|
|
53
|
+
/** Append the active panel's contributed sections to a static tab list. */
|
|
54
|
+
export declare function mergeSidebarTabs(staticTabs: SidebarTab[], contribution: PanelContribution | null, fallbackIcon?: Component): SidebarTab[];
|
|
55
|
+
/** @internal — used by the workspace to expose the active contribution as a ref. */
|
|
56
|
+
export declare function activeContributionRef(contributions: Contributions, activePanelId: () => string | null): ComputedRef<PanelContribution | null>;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pointer-drag and resize primitives.
|
|
3
|
+
*
|
|
4
|
+
* Framework-agnostic on purpose: there is no Vue in this file, so the grid split resizer,
|
|
5
|
+
* the sidebar drawer resizer and both floating-window resize implementations share one
|
|
6
|
+
* mechanic that cannot drift apart. In react-dockable-desktop these had already drifted
|
|
7
|
+
* four ways, differing in exactly the kind of detail (an inline style present in one and
|
|
8
|
+
* missing in another) that caused a real user-visible bug.
|
|
9
|
+
*
|
|
10
|
+
* Exported publicly so applications can build their own resizable UI inside a panel with
|
|
11
|
+
* the same behaviour as the library's own.
|
|
12
|
+
*/
|
|
13
|
+
export interface PointerDragConfig<TStart> {
|
|
14
|
+
/** The element to capture the pointer on — normally the handle the user grabbed. */
|
|
15
|
+
element: HTMLElement;
|
|
16
|
+
pointerId: number;
|
|
17
|
+
/** The pointerdown event's clientX/clientY, used as the delta origin. */
|
|
18
|
+
startClientX: number;
|
|
19
|
+
startClientY: number;
|
|
20
|
+
/** Snapshot whatever state the caller needs at drag start (sizes, positions, ...). */
|
|
21
|
+
captureStart: () => TStart;
|
|
22
|
+
/** Called on every pointermove with the delta from the drag's start position. */
|
|
23
|
+
onMove: (dx: number, dy: number, start: TStart) => void;
|
|
24
|
+
/** Called once when the drag ends (pointerup or pointercancel). */
|
|
25
|
+
onEnd?: (start: TStart) => void;
|
|
26
|
+
/** Classes toggled on the given elements for the duration of the drag. */
|
|
27
|
+
activeClasses?: Array<{
|
|
28
|
+
el: HTMLElement;
|
|
29
|
+
classes: string[];
|
|
30
|
+
}>;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Starts a pointer-capture-based drag: captures the pointer on `element`, tracks
|
|
34
|
+
* movement via listeners scoped to that element's own lifetime (not `window`), and
|
|
35
|
+
* cleans up automatically on release or cancel.
|
|
36
|
+
*/
|
|
37
|
+
export declare function startPointerDrag<TStart>(config: PointerDragConfig<TStart>): void;
|
|
38
|
+
export type ResizeDir = 'n' | 'ne' | 'e' | 'se' | 's' | 'sw' | 'w' | 'nw';
|
|
39
|
+
export interface ResizeRect {
|
|
40
|
+
x: number;
|
|
41
|
+
y: number;
|
|
42
|
+
w: number;
|
|
43
|
+
h: number;
|
|
44
|
+
}
|
|
45
|
+
export interface ResizeConstraints {
|
|
46
|
+
minW: number;
|
|
47
|
+
minH: number;
|
|
48
|
+
/** Upper bound on width — only applies to eastward growth (dir includes 'e'). */
|
|
49
|
+
maxW?: number;
|
|
50
|
+
/** Upper bound on height — only applies to southward growth (dir includes 's'). */
|
|
51
|
+
maxH?: number;
|
|
52
|
+
/** Lower bound on the resulting x — only applies to westward growth (dir includes 'w'). */
|
|
53
|
+
minX?: number;
|
|
54
|
+
/** Lower bound on the resulting y — only applies to northward growth (dir includes 'n'). */
|
|
55
|
+
minY?: number;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Pure function computing the new rect for an 8-directional resize handle drag.
|
|
59
|
+
*
|
|
60
|
+
* `maxW`/`maxH` and `minX`/`minY` are independent, direction-scoped constraints
|
|
61
|
+
* rather than one "container bound" — a resize toward the fixed edge (e/s) is
|
|
62
|
+
* naturally bounded by a maximum dimension, while a resize toward the moving edge
|
|
63
|
+
* (w/n) is naturally bounded by a minimum position, and the two calling sites this
|
|
64
|
+
* was extracted from need different subsets of these (a workspace floating window omits all four, so it may grow unbounded and be dragged
|
|
65
|
+
* off-screen; a docked inner widget supplies all four to stay within its panel).
|
|
66
|
+
*/
|
|
67
|
+
export declare function computeResizedRect(dir: ResizeDir, dx: number, dy: number, start: ResizeRect, constraints: ResizeConstraints): ResizeRect;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The inter-panel event bus.
|
|
3
|
+
*
|
|
4
|
+
* Panels are deliberately unaware of each other; this is how they communicate. It is a
|
|
5
|
+
* domain feature, not DOM events, so it stays a workspace method rather than becoming
|
|
6
|
+
* component `emits`.
|
|
7
|
+
*
|
|
8
|
+
* Framework-agnostic: subscriptions made from a component are disposed by the composable
|
|
9
|
+
* that wraps this, not by the bus itself.
|
|
10
|
+
*/
|
|
11
|
+
/** Events the library publishes itself. An application's own events are merged with these. */
|
|
12
|
+
export interface BuiltInEvents {
|
|
13
|
+
'panel:opened': {
|
|
14
|
+
id: string;
|
|
15
|
+
component: string;
|
|
16
|
+
};
|
|
17
|
+
'panel:closed': {
|
|
18
|
+
id: string;
|
|
19
|
+
};
|
|
20
|
+
'panel:minimized': {
|
|
21
|
+
id: string;
|
|
22
|
+
};
|
|
23
|
+
'panel:restored': {
|
|
24
|
+
id: string;
|
|
25
|
+
};
|
|
26
|
+
/** The globally active panel changed. The ordered channel for handing off between panels —
|
|
27
|
+
* a watcher on `usePanel().isActive` sees the same change but not the ordering. */
|
|
28
|
+
'panel:activated': {
|
|
29
|
+
id: string | null;
|
|
30
|
+
previous: string | null;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Anything `saveLayout()` would capture has changed: open, close, minimise, restore, a
|
|
34
|
+
* dedupe redirect, and every placement change (dock, float, reorder, edge-dock, split
|
|
35
|
+
* resize). Coalesced so an autosave needs one subscription.
|
|
36
|
+
*
|
|
37
|
+
* Does **not** fire when an `onSaveState` provider's own return value changes — that is a
|
|
38
|
+
* pull, and nothing can observe it changing. Save on your own trigger too if it matters.
|
|
39
|
+
*/
|
|
40
|
+
'layout:changed': Record<string, never>;
|
|
41
|
+
/**
|
|
42
|
+
* Fired from inside `saveLayout()`, only when that call excluded at least one panel whose
|
|
43
|
+
* current props failed the serialisability check. A passive flag is not enough: nobody may
|
|
44
|
+
* be looking at the moment something silently drops out of a snapshot.
|
|
45
|
+
*/
|
|
46
|
+
'layout:panels-excluded': {
|
|
47
|
+
panels: {
|
|
48
|
+
id: string;
|
|
49
|
+
component: string;
|
|
50
|
+
}[];
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
export declare class EventBus<TEvents extends Record<string, unknown> = Record<string, unknown>> {
|
|
54
|
+
private listeners;
|
|
55
|
+
subscribe<K extends keyof (TEvents & BuiltInEvents) & string>(event: K, callback: (data: (TEvents & BuiltInEvents)[K]) => void): () => void;
|
|
56
|
+
publish<K extends keyof (TEvents & BuiltInEvents) & string>(event: K, data: (TEvents & BuiltInEvents)[K]): void;
|
|
57
|
+
/**
|
|
58
|
+
* Publish one of the library's own events.
|
|
59
|
+
*
|
|
60
|
+
* Separate from `publish` because `TEvents & BuiltInEvents` is not provably satisfied by a
|
|
61
|
+
* built-in payload: an application could declare its own narrower type for a built-in key.
|
|
62
|
+
* This path is typed against `BuiltInEvents` alone, so the library's own call sites are
|
|
63
|
+
* checked properly instead of being cast.
|
|
64
|
+
*
|
|
65
|
+
* @internal
|
|
66
|
+
*/
|
|
67
|
+
emit<K extends keyof BuiltInEvents & string>(event: K, data: BuiltInEvents[K]): void;
|
|
68
|
+
private dispatch;
|
|
69
|
+
/** Listener count for an event — for tests and diagnostics. */
|
|
70
|
+
count(event: string): number;
|
|
71
|
+
/** Drop every listener. Called by `workspace.dispose()`. */
|
|
72
|
+
clear(): void;
|
|
73
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure operations on the layout tree.
|
|
3
|
+
*
|
|
4
|
+
* Every function here takes a tree and returns a new one — no mutation, no reactivity, no
|
|
5
|
+
* Vue. In react-dockable-desktop these lived as closures inside the provider and could
|
|
6
|
+
* only be tested through the rendered component; extracting them means the tree maths is
|
|
7
|
+
* directly testable, which is where the subtle bugs are (see `deriveActivePanelId`).
|
|
8
|
+
*/
|
|
9
|
+
import type { FloatingWindow, LayoutLeafNode, LayoutNode, PanelInfo, SplitDirection } from '../types';
|
|
10
|
+
/** An empty workspace: one leaf, no panels. */
|
|
11
|
+
export declare const EMPTY_LEAF: LayoutLeafNode;
|
|
12
|
+
/** A fresh empty root, for when removing the last panel collapses the tree to nothing. */
|
|
13
|
+
export declare const emptyRoot: () => LayoutLeafNode;
|
|
14
|
+
/**
|
|
15
|
+
* Remove a panel from the tree.
|
|
16
|
+
*
|
|
17
|
+
* Returns `null` when the node itself should disappear — an emptied leaf without
|
|
18
|
+
* `keepOnEmpty`, or a branch left with no children. A branch left with one child collapses
|
|
19
|
+
* to that child, and surviving `sizes` are re-normalised to sum to 1.
|
|
20
|
+
*
|
|
21
|
+
* When the removed panel was its leaf's selected tab, selection moves to the next tab, then
|
|
22
|
+
* the previous, then the first — the order a user expects after closing a tab.
|
|
23
|
+
*/
|
|
24
|
+
export declare function removePanelFromTree(node: LayoutNode, id: string): LayoutNode | null;
|
|
25
|
+
/**
|
|
26
|
+
* Add a panel to a leaf. A panel already in that leaf is not duplicated.
|
|
27
|
+
*
|
|
28
|
+
* `select: false` adds it **without** making it the leaf's selected tab — what "open in the
|
|
29
|
+
* background" has to mean. Selecting it anyway would make the newly added panel the visible
|
|
30
|
+
* tab while some other panel stayed globally active, which is precisely the disagreement
|
|
31
|
+
* between leaf selection and `activePanelId` that divergence D2 is about. (rdd's
|
|
32
|
+
* `focus: false` did exactly that.)
|
|
33
|
+
*/
|
|
34
|
+
export declare function addPanelToLeaf(node: LayoutNode, leafId: string, panelId: string, options?: {
|
|
35
|
+
select?: boolean;
|
|
36
|
+
}): LayoutNode;
|
|
37
|
+
/** Insert a panel into a leaf at a specific tab index, and select it. */
|
|
38
|
+
export declare function insertPanelInLeaf(node: LayoutNode, leafId: string, panelId: string, index: number): LayoutNode;
|
|
39
|
+
/** Default id scheme for leaves created by a split. Any string is valid; this one is legible. */
|
|
40
|
+
export declare const defaultLeafId: (kind: "split" | "edge") => string;
|
|
41
|
+
/**
|
|
42
|
+
* Split a leaf, putting `panelId` in a new leaf on the given side.
|
|
43
|
+
*
|
|
44
|
+
* `newLeafId` is injectable so tests can assert tree shape without a timestamp in it.
|
|
45
|
+
*/
|
|
46
|
+
export declare function splitLeafInTree(node: LayoutNode, leafId: string, panelId: string, position: SplitDirection, splitRatio: number, newLeafId?: string): LayoutNode;
|
|
47
|
+
/** Wrap the whole tree in a branch, docking `panelId` as a full-width/height edge row. */
|
|
48
|
+
export declare function dockToEdge(root: LayoutNode, panelId: string, position: SplitDirection, edgeRatio: number, newLeafId?: string): LayoutNode;
|
|
49
|
+
/** Remove a leaf entirely, honouring its `canClose`. Collapses and re-normalises like removal. */
|
|
50
|
+
export declare function removeLeafFromTree(node: LayoutNode, leafId: string): LayoutNode | null;
|
|
51
|
+
/** Select a panel as its leaf's active tab, wherever it is. */
|
|
52
|
+
export declare function selectPanelInTree(node: LayoutNode, id: string): LayoutNode;
|
|
53
|
+
/** The first leaf in document order, depth-first. */
|
|
54
|
+
export declare function findFirstLeafId(node: LayoutNode): string | null;
|
|
55
|
+
/** The leaf containing a panel, or `null`. */
|
|
56
|
+
export declare function findLeafForPanel(node: LayoutNode, id: string): string | null;
|
|
57
|
+
/** A leaf by id, or `null`. */
|
|
58
|
+
export declare function findLeaf(node: LayoutNode | null, leafId: string): LayoutLeafNode | null;
|
|
59
|
+
/** Whether a leaf exists in the tree. */
|
|
60
|
+
export declare function leafExists(node: LayoutNode, leafId: string): boolean;
|
|
61
|
+
/** Update the sizes of the branch at `path` (a list of child indices from the root). */
|
|
62
|
+
export declare function updateSizesAtPath(node: LayoutNode, path: number[], sizes: number[]): LayoutNode;
|
|
63
|
+
/** The subset of a layout needed to reason about visibility. */
|
|
64
|
+
export interface ActiveTargetScope {
|
|
65
|
+
gridRoot: LayoutNode | null;
|
|
66
|
+
floating: FloatingWindow[];
|
|
67
|
+
panels: Record<string, PanelInfo>;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Whether `id` names a panel the user can actually see, and which may therefore be the
|
|
71
|
+
* globally active one: the selected tab of some leaf, or a floating window.
|
|
72
|
+
*
|
|
73
|
+
* A minimised panel never qualifies. It stays mounted, so leaving it active would keep
|
|
74
|
+
* routing contributed controls to a panel that is not on screen — the defect family that
|
|
75
|
+
* cost react-dockable-desktop two releases.
|
|
76
|
+
*
|
|
77
|
+
* Used both to validate a persisted `activePanelId` on load and to guard the one written on
|
|
78
|
+
* save, so the two directions cannot disagree about what "active" is allowed to mean.
|
|
79
|
+
*/
|
|
80
|
+
export declare function isVisibleActiveTarget(id: string, scope: ActiveTargetScope): boolean;
|
|
81
|
+
/**
|
|
82
|
+
* Derive the globally active panel, in order:
|
|
83
|
+
*
|
|
84
|
+
* 1. the first leaf in document order whose own selected tab is a valid target;
|
|
85
|
+
* 2. otherwise the frontmost (highest `z`) floating window, so a float-only layout does
|
|
86
|
+
* not come back with nothing active;
|
|
87
|
+
* 3. otherwise `null`.
|
|
88
|
+
*
|
|
89
|
+
* **Depth-first, not breadth-first.** For a grid whose first child is itself a split, a
|
|
90
|
+
* level-by-level walk reaches the *second* child's leaf before the first child's own
|
|
91
|
+
* children, and picks the wrong tab. Mirrors `findFirstLeafId`'s traversal for that reason.
|
|
92
|
+
*
|
|
93
|
+
* Never an arbitrary entry in `panels`: that seed is what made a restored workspace come
|
|
94
|
+
* back with one panel visible and a different, invisible one marked active.
|
|
95
|
+
*/
|
|
96
|
+
export declare function deriveActivePanelId(scope: ActiveTargetScope): string | null;
|