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.
Files changed (82) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/LICENSE +21 -0
  3. package/README.md +169 -0
  4. package/dist/components/VddConfirm.vue.d.ts +19 -0
  5. package/dist/components/VddContextMenu.vue.d.ts +35 -0
  6. package/dist/components/VddDesktop.vue.d.ts +28 -0
  7. package/dist/components/VddDragGhost.vue.d.ts +3 -0
  8. package/dist/components/VddDropZones.vue.d.ts +6 -0
  9. package/dist/components/VddEdgeZones.vue.d.ts +3 -0
  10. package/dist/components/VddFloatingWidget.vue.d.ts +51 -0
  11. package/dist/components/VddFloatingWindow.vue.d.ts +12 -0
  12. package/dist/components/VddLeafGroup.vue.d.ts +7 -0
  13. package/dist/components/VddModalHost.vue.d.ts +9 -0
  14. package/dist/components/VddModals.vue.d.ts +3 -0
  15. package/dist/components/VddOverlayFrame.vue.d.ts +28 -0
  16. package/dist/components/VddPanelMount.vue.d.ts +8 -0
  17. package/dist/components/VddPanelOverlay.vue.d.ts +13 -0
  18. package/dist/components/VddPanelSlot.vue.d.ts +9 -0
  19. package/dist/components/VddPanelToolbar.vue.d.ts +26 -0
  20. package/dist/components/VddSecondarySidebar.vue.d.ts +16 -0
  21. package/dist/components/VddSidePanelHost.vue.d.ts +10 -0
  22. package/dist/components/VddSidePanels.vue.d.ts +12 -0
  23. package/dist/components/VddSidebar.vue.d.ts +54 -0
  24. package/dist/components/VddSidebarDrawer.vue.d.ts +51 -0
  25. package/dist/components/VddSidebarRail.vue.d.ts +22 -0
  26. package/dist/components/VddSidebarTabScope.vue.d.ts +22 -0
  27. package/dist/components/VddTaskbar.vue.d.ts +17 -0
  28. package/dist/components/VddTaskbarPreview.vue.d.ts +24 -0
  29. package/dist/components/VddToastIcon.vue.d.ts +8 -0
  30. package/dist/components/VddToastItem.vue.d.ts +18 -0
  31. package/dist/components/VddToasts.vue.d.ts +36 -0
  32. package/dist/components/VddToolbar.vue.d.ts +20 -0
  33. package/dist/components/VddToolbarButton.vue.d.ts +25 -0
  34. package/dist/components/VddToolbarCenter.vue.d.ts +13 -0
  35. package/dist/components/VddToolbarGroupButton.vue.d.ts +8 -0
  36. package/dist/components/VddToolbarItem.vue.d.ts +13 -0
  37. package/dist/components/VddToolbarSearch.vue.d.ts +27 -0
  38. package/dist/components/VddToolbarSeparator.vue.d.ts +3 -0
  39. package/dist/components/VddToolbarSpacer.vue.d.ts +3 -0
  40. package/dist/components/VddToolbarToggle.vue.d.ts +32 -0
  41. package/dist/components/VddWorkspaceGrid.vue.d.ts +8 -0
  42. package/dist/composables/useColorScheme.d.ts +19 -0
  43. package/dist/composables/useContextMenu.d.ts +29 -0
  44. package/dist/composables/useContributions.d.ts +48 -0
  45. package/dist/composables/useDragDock.d.ts +59 -0
  46. package/dist/composables/useOverlayHost.d.ts +30 -0
  47. package/dist/composables/useOverlays.d.ts +51 -0
  48. package/dist/composables/usePanel.d.ts +82 -0
  49. package/dist/composables/usePanelDom.d.ts +7 -0
  50. package/dist/composables/usePanelOverlay.d.ts +35 -0
  51. package/dist/composables/useSidebar.d.ts +15 -0
  52. package/dist/composables/useToolbar.d.ts +9 -0
  53. package/dist/composables/useWorkspace.d.ts +18 -0
  54. package/dist/core/anchorGeometry.d.ts +39 -0
  55. package/dist/core/contextMenu.d.ts +74 -0
  56. package/dist/core/contributions.d.ts +56 -0
  57. package/dist/core/dragResize.d.ts +67 -0
  58. package/dist/core/eventBus.d.ts +73 -0
  59. package/dist/core/layoutTree.d.ts +96 -0
  60. package/dist/core/messages.d.ts +117 -0
  61. package/dist/core/overlayState.d.ts +64 -0
  62. package/dist/core/overlays.d.ts +114 -0
  63. package/dist/core/panelDom.d.ts +79 -0
  64. package/dist/core/panelMenu.d.ts +45 -0
  65. package/dist/core/panelOverlay.d.ts +121 -0
  66. package/dist/core/registry.d.ts +61 -0
  67. package/dist/core/serializable.d.ts +26 -0
  68. package/dist/core/serialize.d.ts +32 -0
  69. package/dist/core/sidebarTypes.d.ts +102 -0
  70. package/dist/core/stretch.d.ts +47 -0
  71. package/dist/core/toast.d.ts +474 -0
  72. package/dist/core/toolbarState.d.ts +13 -0
  73. package/dist/core/toolbarTypes.d.ts +99 -0
  74. package/dist/core/workspace.d.ts +222 -0
  75. package/dist/index.cjs +5 -0
  76. package/dist/index.cjs.map +1 -0
  77. package/dist/index.d.ts +64 -0
  78. package/dist/index.js +4851 -0
  79. package/dist/index.js.map +1 -0
  80. package/dist/styles.css +3944 -0
  81. package/dist/types.d.ts +145 -0
  82. package/package.json +102 -0
@@ -0,0 +1,117 @@
1
+ import type { Label, MessageFormatter } from '../types';
2
+ /**
3
+ * The library's own UI strings.
4
+ *
5
+ * Each `id` is the key an application defines in its i18n message table; `defaultMessage`
6
+ * is the fallback when no formatter is supplied. Override any subset via
7
+ * `createWorkspace({ messages })` — the object is merged, so partial overrides work.
8
+ */
9
+ export declare const defaultMessages: {
10
+ readonly floatWindow: {
11
+ readonly id: "vdd.floatWindow";
12
+ readonly defaultMessage: "Float Window";
13
+ };
14
+ readonly minimizePanel: {
15
+ readonly id: "vdd.minimizePanel";
16
+ readonly defaultMessage: "Minimize Panel";
17
+ };
18
+ readonly closeTab: {
19
+ readonly id: "vdd.closeTab";
20
+ readonly defaultMessage: "Close Tab";
21
+ };
22
+ readonly restorePanel: {
23
+ readonly id: "vdd.restorePanel";
24
+ readonly defaultMessage: "Restore Panel";
25
+ };
26
+ readonly maximizePanel: {
27
+ readonly id: "vdd.maximizePanel";
28
+ readonly defaultMessage: "Maximize Panel";
29
+ };
30
+ readonly closePanel: {
31
+ readonly id: "vdd.closePanel";
32
+ readonly defaultMessage: "Close Panel";
33
+ };
34
+ readonly dockWindow: {
35
+ readonly id: "vdd.dockWindow";
36
+ readonly defaultMessage: "Dock Window";
37
+ };
38
+ readonly minimize: {
39
+ readonly id: "vdd.minimize";
40
+ readonly defaultMessage: "Minimize";
41
+ };
42
+ readonly maximize: {
43
+ readonly id: "vdd.maximize";
44
+ readonly defaultMessage: "Maximize";
45
+ };
46
+ readonly restoreSize: {
47
+ readonly id: "vdd.restoreSize";
48
+ readonly defaultMessage: "Restore Size";
49
+ };
50
+ readonly close: {
51
+ readonly id: "vdd.close";
52
+ readonly defaultMessage: "Close";
53
+ };
54
+ readonly closeEmptyGroup: {
55
+ readonly id: "vdd.closeEmptyGroup";
56
+ readonly defaultMessage: "Close empty split group";
57
+ };
58
+ readonly emptyGroup: {
59
+ readonly id: "vdd.emptyGroup";
60
+ readonly defaultMessage: "Empty workspace section";
61
+ };
62
+ readonly unsavedChangesTitle: {
63
+ readonly id: "vdd.unsavedChangesTitle";
64
+ readonly defaultMessage: "Unsaved Changes";
65
+ };
66
+ readonly unsavedChangesMessage: {
67
+ readonly id: "vdd.unsavedChangesMessage";
68
+ readonly defaultMessage: "\"{title}\" has unsaved changes. Do you want to discard your changes and close?";
69
+ };
70
+ readonly discardChanges: {
71
+ readonly id: "vdd.discardChanges";
72
+ readonly defaultMessage: "Discard Changes";
73
+ };
74
+ readonly cancel: {
75
+ readonly id: "vdd.cancel";
76
+ readonly defaultMessage: "Cancel";
77
+ };
78
+ readonly yes: {
79
+ readonly id: "vdd.yes";
80
+ readonly defaultMessage: "Yes";
81
+ };
82
+ readonly no: {
83
+ readonly id: "vdd.no";
84
+ readonly defaultMessage: "No";
85
+ };
86
+ readonly ok: {
87
+ readonly id: "vdd.ok";
88
+ readonly defaultMessage: "OK";
89
+ };
90
+ readonly closeTooltip: {
91
+ readonly id: "vdd.closeTooltip";
92
+ readonly defaultMessage: "Close";
93
+ };
94
+ readonly scrollTabsLeft: {
95
+ readonly id: "vdd.scrollTabsLeft";
96
+ readonly defaultMessage: "Scroll tabs left";
97
+ };
98
+ readonly scrollTabsRight: {
99
+ readonly id: "vdd.scrollTabsRight";
100
+ readonly defaultMessage: "Scroll tabs right";
101
+ };
102
+ readonly moreActions: {
103
+ readonly id: "vdd.moreActions";
104
+ readonly defaultMessage: "More actions";
105
+ };
106
+ readonly search: {
107
+ readonly id: "vdd.search";
108
+ readonly defaultMessage: "Search";
109
+ };
110
+ };
111
+ /**
112
+ * Every message key. Import it in your own message table to get a compile-time guarantee
113
+ * that all keys are present and none are misspelled.
114
+ */
115
+ export type MessageKey = keyof typeof defaultMessages;
116
+ /** Resolve a label with a formatter, falling back to its `defaultMessage` then its `id`. */
117
+ export declare function formatLabel(label: Label | undefined, format?: MessageFormatter): string;
@@ -0,0 +1,64 @@
1
+ import type { Component, InjectionKey, Ref } from 'vue';
2
+ import type { FloatAnchor, Label } from '../types';
3
+ import type { Stretch, PanelFloatPlacement } from './stretch';
4
+ import type { ToolbarInsets, ToolbarPosition } from './panelOverlay';
5
+ /** A widget opened through `useFloatingWidgets()` rather than placed in the template. */
6
+ export interface ManagedWidget {
7
+ /**
8
+ * The widget's header text: plain, or a localisable descriptor.
9
+ *
10
+ * A descriptor matters most here of all the title surfaces, because this object is *stored*:
11
+ * a resolved string handed to `openManaged` is frozen at the language that was current when
12
+ * the widget opened, while a descriptor is resolved on every render and so follows a locale
13
+ * change with no reopen.
14
+ */
15
+ title: Label;
16
+ icon?: Component;
17
+ /** Rendered as the widget's content. */
18
+ component: Component;
19
+ props?: Record<string, unknown>;
20
+ anchor?: FloatAnchor;
21
+ width?: number;
22
+ height?: number;
23
+ stretch?: Stretch | null;
24
+ }
25
+ export interface OverlayStacks {
26
+ 'top-left': string[];
27
+ 'top-right': string[];
28
+ 'bottom-left': string[];
29
+ 'bottom-right': string[];
30
+ }
31
+ export interface PanelOverlayStore {
32
+ /** The overlay root element, for measuring drop zones. */
33
+ container: Ref<HTMLElement | null>;
34
+ /** Space each toolbar edge claims. */
35
+ insets: ToolbarInsets;
36
+ /** The widget on top, or `null`. */
37
+ topId: Ref<string | null>;
38
+ zOrders: Record<string, number>;
39
+ /** Which widgets are stacked in each corner bucket. */
40
+ stacks: OverlayStacks;
41
+ /** Each docked widget's block size, so its stack peers can offset past it. */
42
+ dockedSizes: Record<string, number>;
43
+ /** The widget being dragged, or `null`. */
44
+ draggingId: Ref<string | null>;
45
+ /** The corner the drag is currently over, or `null`. */
46
+ hovered: Ref<FloatAnchor | null>;
47
+ /** Widgets opened through `useFloatingWidgets()`. */
48
+ managed: Map<string, ManagedWidget>;
49
+ /** Bumped whenever `managed` changes, since a `Map` is not reactive by itself. */
50
+ managedVersion: Ref<number>;
51
+ registerToolbar(position: ToolbarPosition, size: number): void;
52
+ unregisterToolbar(position: ToolbarPosition): void;
53
+ focus(id: string): void;
54
+ dock(id: string, anchor: FloatAnchor, stretch: Stretch | null): void;
55
+ undock(id: string): void;
56
+ reportSize(id: string, size: number): void;
57
+ openManaged(id: string, widget: ManagedWidget): void;
58
+ closeManaged(id: string): void;
59
+ closeAllManaged(): void;
60
+ managedIds(): string[];
61
+ }
62
+ export declare const PANEL_OVERLAY_KEY: InjectionKey<PanelOverlayStore>;
63
+ export declare function createPanelOverlayStore(): PanelOverlayStore;
64
+ export type { PanelFloatPlacement };
@@ -0,0 +1,114 @@
1
+ import type { Component } from 'vue';
2
+ import type { DirtyStateOptions, Label } from '../types';
3
+ /** Which of the three overlay slots an instance occupies. */
4
+ export type OverlayKind = 'left-panel' | 'right-panel' | 'modal';
5
+ /** Options for `openLeftPanel` / `openRightPanel`. */
6
+ export interface SidePanelOptions {
7
+ title?: Label;
8
+ /** Rendered in the header, before the title. */
9
+ icon?: Component;
10
+ /** A number is pixels; a string is used as-is (`'40vw'`). @default 400 */
11
+ width?: number | string;
12
+ /**
13
+ * Padding for the body. A number is pixels; a string is any CSS value or shorthand.
14
+ * Nothing is set at all by default, so the stylesheet decides and content can go
15
+ * edge-to-edge.
16
+ */
17
+ bodyPadding?: number | string;
18
+ }
19
+ /** Options for `openModal`. */
20
+ export interface ModalOptions {
21
+ title?: Label;
22
+ icon?: Component;
23
+ /** Maps to a `max-width` rule. @default 'auto' */
24
+ size?: 'small' | 'medium' | 'large' | 'fullscreen' | 'auto';
25
+ /** `false` removes the close button *and* dismissal by backdrop click or Escape. @default true */
26
+ closable?: boolean;
27
+ /** As `SidePanelOptions.bodyPadding`. */
28
+ bodyPadding?: number | string;
29
+ }
30
+ /** One open side panel or modal. */
31
+ export interface OverlayInstance {
32
+ id: string;
33
+ /** Kept raw: a component definition is not reactive data, and proxying one is wasteful. */
34
+ component: Component;
35
+ props: Record<string, unknown>;
36
+ kind: OverlayKind;
37
+ options: SidePanelOptions & ModalOptions;
38
+ dirty: boolean;
39
+ dirtyOptions?: DirtyStateOptions;
40
+ }
41
+ export interface OverlayState {
42
+ leftPanel: OverlayInstance | null;
43
+ rightPanel: OverlayInstance | null;
44
+ /** Bottom to top: the last entry is the topmost modal. */
45
+ modals: OverlayInstance[];
46
+ }
47
+ /**
48
+ * What the built-in unsaved-changes question needs to know.
49
+ *
50
+ * Deliberately not an `OverlayInstance` or a `PanelInfo`: a docked tab, a floating window, a
51
+ * drawer and a modal all ask the same question, and narrowing it to these two fields is what
52
+ * lets one implementation serve all four.
53
+ */
54
+ export interface DiscardRequest {
55
+ /** The thing being closed, already formatted — it appears in the message. */
56
+ title: string;
57
+ dirtyOptions?: DirtyStateOptions;
58
+ }
59
+ /** Ask the user whether to discard unsaved changes. Resolves `false` for every refusal. */
60
+ export type ConfirmDiscard = (request: DiscardRequest) => Promise<boolean>;
61
+ export interface Overlays {
62
+ readonly state: OverlayState;
63
+ /**
64
+ * Open a drawer on the left. Resolves to the new instance's id, or `null` when a panel was
65
+ * already open there and its close guard refused — a drawer is a single slot, so opening
66
+ * one is also closing the other.
67
+ */
68
+ openLeftPanel(component: Component, props?: Record<string, unknown>, options?: SidePanelOptions): Promise<string | null>;
69
+ openRightPanel(component: Component, props?: Record<string, unknown>, options?: SidePanelOptions): Promise<string | null>;
70
+ /** Push a modal onto the stack. Synchronous: a stack has room for another. */
71
+ openModal(component: Component, props?: Record<string, unknown>, options?: ModalOptions): string;
72
+ /** Remove an instance immediately, with no guard and no dirty check. */
73
+ close(id: string): void;
74
+ closeAll(): void;
75
+ closeAllModals(): void;
76
+ getInstance(id: string): OverlayInstance | undefined;
77
+ updateInstance(id: string, updates: Partial<Pick<OverlayInstance, 'props' | 'options' | 'dirty' | 'dirtyOptions'>>): void;
78
+ setDirty(id: string, dirty: boolean, options?: DirtyStateOptions): void;
79
+ /**
80
+ * Close, honouring this instance's close guard and its dirty state.
81
+ *
82
+ * `force` skips both. Without a `confirm`, a dirty instance stays open rather than
83
+ * discarding the user's edits silently — the same refusal `requestClosePanel` makes.
84
+ */
85
+ requestClose(id: string, options?: {
86
+ force?: boolean;
87
+ confirm?: (instance: OverlayInstance) => Promise<boolean>;
88
+ }): Promise<void>;
89
+ /** Veto an instance's close. Returning `false` blocks it. */
90
+ registerCloseGuard(id: string, guard: () => boolean | Promise<boolean>): () => void;
91
+ /** The topmost modal, or `null`. */
92
+ topmostModal(): OverlayInstance | null;
93
+ /**
94
+ * Ask the user whether to discard unsaved changes, as a modal on top of the stack.
95
+ *
96
+ * Resolves `false` when nothing can render the question — which is why a dirty close with
97
+ * no `<VddModals>` mounted refuses instead of discarding. Every close path in the library
98
+ * routes through this one function: a tab's ×, a floating window's ×, a drawer, a modal,
99
+ * and `usePanel().close()`. rdd wired an equivalent separately into each container, and
100
+ * the docked-panel path was wired to nothing at all.
101
+ */
102
+ confirmDiscard: ConfirmDiscard;
103
+ /**
104
+ * Register the component that renders that question, and the formatter for its message.
105
+ * Called by `<VddModals>` on mount: asking requires somewhere to render.
106
+ * @internal
107
+ */
108
+ setConfirmRenderer(renderer: ConfirmRenderer | null): void;
109
+ /** @internal */
110
+ dispose(): void;
111
+ }
112
+ /** Supplied by `<VddModals>`: opens the question and resolves the user's answer. @internal */
113
+ export type ConfirmRenderer = (request: DiscardRequest) => Promise<boolean>;
114
+ export declare function createOverlays(): Overlays;
@@ -0,0 +1,79 @@
1
+ /**
2
+ * The persistence port: the mechanism that makes a panel survive being moved.
3
+ *
4
+ * Each panel gets **one** element for its whole lifetime. Panel content is teleported into
5
+ * that element once; moving the panel between the grid, a floating window, a hover preview
6
+ * and the off-screen store moves *the element*, which Vue never observes. That is what keeps
7
+ * a WebGL context, a Monaco model, a playing video and an open socket alive across every
8
+ * layout change.
9
+ *
10
+ * Proven in the M0 spike, including the choice of this strategy over teleporting straight to
11
+ * the host — see docs/decisions/0002-zero-unmount-via-teleport.md. One instance per
12
+ * `<VddDesktop>`, never a module-level singleton: rdd's module-level `domCache` meant two
13
+ * workspaces on a page shared panel DOM.
14
+ */
15
+ export declare class PanelDomCache {
16
+ private readonly doc;
17
+ private elements;
18
+ /**
19
+ * Preserved state, **owned per panel and outliving the hidden period**.
20
+ *
21
+ * Capturing at each move is not enough: while a panel sits in the hidden store it has no
22
+ * layout, so every scroller reports `scrollHeight === clientHeight === 0` and there is
23
+ * nothing to read. So the record is only refreshed while the panel is laid out, and always
24
+ * applied when it becomes laid out again — which is what makes minimise → restore work.
25
+ * M0 verified this failing, then fixed.
26
+ */
27
+ private preserved;
28
+ /**
29
+ * The last size each panel was laid out at.
30
+ *
31
+ * Kept here because it outlives any one host: the taskbar's hover preview needs to know how
32
+ * big a panel *was* in order to scale a thumbnail of it, and by then the panel is minimised
33
+ * and has no size of its own.
34
+ */
35
+ private sizes;
36
+ private hidden;
37
+ constructor(doc?: Document);
38
+ /** The off-screen store. Panels live here while minimised, and before their first host. */
39
+ hiddenStore(): HTMLElement;
40
+ /**
41
+ * This panel's element, created on first ask.
42
+ *
43
+ * Created **already in the document**, inside the hidden store: a `<Teleport>` target must
44
+ * exist when the teleport mounts, or Vue warns and renders nothing.
45
+ */
46
+ elementFor(id: string): HTMLDivElement;
47
+ /** Whether this panel has an element yet. */
48
+ has(id: string): boolean;
49
+ /** Record the size a panel was last laid out at. */
50
+ reportSize(id: string, size: {
51
+ width: number;
52
+ height: number;
53
+ }): void;
54
+ /** The size a panel was last laid out at, or a sensible default for a thumbnail. */
55
+ sizeOf(id: string): {
56
+ width: number;
57
+ height: number;
58
+ };
59
+ /** The host this panel's element is currently inside. */
60
+ hostOf(id: string): HTMLElement | null;
61
+ /**
62
+ * Move a panel's element into `host` (or the hidden store when `null`), preserving scroll
63
+ * and focus around the move.
64
+ *
65
+ * @param refocus restore focus as well. Only true when the panel is becoming the active
66
+ * one — restoring focus into a background panel would steal the caret from wherever the
67
+ * user is actually typing.
68
+ */
69
+ moveTo(id: string, host: HTMLElement | null, options?: {
70
+ refocus?: boolean;
71
+ preserveScroll?: boolean;
72
+ }): void;
73
+ /** Forget a panel entirely. Called when the panel closes. */
74
+ release(id: string): void;
75
+ /** Drop every element and the hidden store. */
76
+ dispose(): void;
77
+ private remember;
78
+ private apply;
79
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * The standard menus the library offers for a panel.
3
+ *
4
+ * Built here rather than in a component so the same menu is offered wherever a panel is
5
+ * right-clicked — a tab, a floating window's title bar, a taskbar icon — and so the item
6
+ * set is testable without rendering anything.
7
+ */
8
+ import type { ContextMenuItem } from './contextMenu';
9
+ import type { PanelDefaultOptions } from './registry';
10
+ import type { Label, MessageDescriptor } from '../types';
11
+ /**
12
+ * Just enough of the workspace to build a menu, so this module does not depend on the store.
13
+ *
14
+ * `format` takes `Label | undefined`, matching the workspace exactly: widening it to
15
+ * `unknown` looks harmless but breaks assignability, since a function accepting a narrow
16
+ * type cannot stand in for one accepting anything.
17
+ */
18
+ interface MenuHost {
19
+ format: (label: Label | undefined) => string;
20
+ messages: Record<string, MessageDescriptor>;
21
+ panelMenuItems: (id: string) => ContextMenuItem[];
22
+ }
23
+ export interface PanelMenuActions {
24
+ float: () => void;
25
+ minimize: () => void;
26
+ close: () => void;
27
+ restore: () => void;
28
+ maximize: () => void;
29
+ }
30
+ /**
31
+ * The menu for a docked tab or a floating window.
32
+ *
33
+ * Entries the panel forbids are absent rather than disabled: an action a panel has opted out
34
+ * of is not a temporarily unavailable action, it is not an action at all.
35
+ */
36
+ export declare function buildPanelMenu(host: MenuHost, panelId: string, options: PanelDefaultOptions, actions: PanelMenuActions): ContextMenuItem[];
37
+ /**
38
+ * The menu for a minimised panel's taskbar icon.
39
+ *
40
+ * "Maximize" works here. rdd offered the same item but its `maximizePanel` only mapped over
41
+ * floating windows, where a minimised panel is not — so the item did nothing at all
42
+ * (divergence D1). Restoring first is what makes it mean something.
43
+ */
44
+ export declare function buildTaskbarMenu(host: MenuHost, panelId: string, options: PanelDefaultOptions, actions: PanelMenuActions): ContextMenuItem[];
45
+ export {};
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Geometry and rules for the panel overlay — the toolbars and floating widgets that live
3
+ * *inside* one panel, over its content.
4
+ *
5
+ * Everything here is pure, so the parts that were hardest to get right in rdd — which resize
6
+ * handles a placement offers, and where a docked widget is allowed to grow to — are testable
7
+ * without a DOM. The stretch algebra they build on is in `core/stretch.ts`.
8
+ */
9
+ import type { FloatAnchor } from '../types';
10
+ import type { ResizeDir } from './dragResize';
11
+ import type { Stretch } from './stretch';
12
+ /** Which edge of the panel a toolbar attaches to. */
13
+ export type ToolbarPosition = 'top' | 'bottom' | 'left' | 'right';
14
+ /** Background treatment of a toolbar strip. */
15
+ export type ToolbarVariant = 'transparent' | 'frosted' | 'solid';
16
+ /** Button treatment, inherited by the toolbar's own buttons. */
17
+ export type ButtonVariant = 'ghost' | 'soft' | 'outlined' | 'filled';
18
+ /** Smallest a widget may be resized to. */
19
+ export declare const MIN_W = 120;
20
+ export declare const MIN_H = 60;
21
+ /** Gutter between a docked widget and the panel's inline edge. */
22
+ export declare const DOCK_INSET = 8;
23
+ /** Gap between widgets stacked in the same corner. */
24
+ export declare const DOCK_GAP = 8;
25
+ /** Side of the corner drop zones, in pixels. */
26
+ export declare const DROP_ZONE_SIZE = 80;
27
+ /** How far the header must move before a click becomes a drag. */
28
+ export declare const DRAG_THRESHOLD = 4;
29
+ /**
30
+ * Resize-to-stretch snapping thresholds, asymmetric on purpose.
31
+ *
32
+ * A drag arms within `SNAP_IN` of the full extent but only disarms once it pulls back past the
33
+ * wider `SNAP_OUT`. Without that hysteresis, releasing a stretched axis by dragging a few
34
+ * pixels inward immediately re-arms and snaps straight back on release, which makes the
35
+ * gesture feel broken.
36
+ */
37
+ export declare const SNAP_IN = 16;
38
+ export declare const SNAP_OUT = 40;
39
+ /** Space claimed by toolbars. Block values are physical; inline values are logical. */
40
+ export interface ToolbarInsets {
41
+ top: number;
42
+ bottom: number;
43
+ /** Inline-start, so `left` under LTR and `right` under RTL. */
44
+ inlineStart: number;
45
+ inlineEnd: number;
46
+ }
47
+ export declare const NO_INSETS: ToolbarInsets;
48
+ /** The four corners. */
49
+ export declare const ANCHORS: readonly FloatAnchor[];
50
+ /** Which corner zone a pointer is over, or `null` for the middle. */
51
+ export declare function hoveredZone(rect: {
52
+ left: number;
53
+ top: number;
54
+ width: number;
55
+ height: number;
56
+ }, clientX: number, clientY: number): FloatAnchor | null;
57
+ /** A drop zone is named by its physical corner, so under RTL the inline half mirrors. */
58
+ export declare const flipZoneHorizontal: (zone: FloatAnchor) => FloatAnchor;
59
+ /**
60
+ * The band a docked widget may occupy, in **physical** pixels from the container's edges.
61
+ *
62
+ * The block axis keeps clear of top/bottom toolbars only. The inline axis also adds the
63
+ * `DOCK_INSET` gutter, matching how a docked widget is positioned in the first place. rdd
64
+ * fixed docked widgets *positioning* themselves over a toolbar in 5.x and never gave the
65
+ * resize path the same treatment, so a docked widget could still be resized straight over
66
+ * the toolbar on the far side.
67
+ *
68
+ * Inline is converted from logical to physical here because handle directions and measured
69
+ * rects are physical, while a toolbar claims its space logically.
70
+ */
71
+ export declare function dockedBand(insets: ToolbarInsets, isRtl: boolean): {
72
+ left: number;
73
+ right: number;
74
+ top: number;
75
+ bottom: number;
76
+ };
77
+ /**
78
+ * Which resize handles a placement offers.
79
+ *
80
+ * **Free-floating:** all eight. Nothing is pinned.
81
+ *
82
+ * **Docked:** only the edges that can actually move. A docked widget pins one edge per axis,
83
+ * so a handle on a pinned side would move the *opposite* edge instead of the one under the
84
+ * cursor — an inert stub with a resize cursor on it. rdd hardcoded the five non-northern
85
+ * directions regardless of anchor, which looked harmless for top anchors but left every
86
+ * bottom-anchored widget with no working vertical resize at all: `n` was not rendered, and
87
+ * `s` was the stub.
88
+ *
89
+ * **Stretched:** a stretched axis has both ends pinned, but both are *releasable* — dragging
90
+ * either moves that edge and pins the opposite one. So it offers handles on both ends, which
91
+ * is also what keeps the fully-stretched state from being a dead end with nothing to grab.
92
+ * The corner belongs only to the all-pinned state; in a stretched state it would mix a
93
+ * resize and a release into one gesture.
94
+ *
95
+ * The block axis is direction-agnostic; the inline axis is not. The pin is a logical property
96
+ * (`inset-inline-end`) while the handle classes are physical (`.vdd-resize-e { right: 0 }`),
97
+ * so which *physical* side is pinned depends on the reading direction.
98
+ */
99
+ export declare function handleDirs(mode: 'docked' | 'free', anchor: FloatAnchor, stretch: Stretch | null, isRtl: boolean): ResizeDir[];
100
+ /**
101
+ * Where a released axis re-pins.
102
+ *
103
+ * The edge under the pointer becomes the moving one and the opposite end becomes the new pin,
104
+ * so the gesture reads exactly like an ordinary resize. Handle directions are physical and
105
+ * anchors are logical, hence the direction term on the inline half.
106
+ */
107
+ export declare function anchorAfterRelease(anchor: FloatAnchor, dir: ResizeDir, axes: {
108
+ inline: boolean;
109
+ block: boolean;
110
+ }, isRtl: boolean): FloatAnchor;
111
+ /**
112
+ * How far down its edge a docked widget sits, given what it is stacked behind.
113
+ *
114
+ * The largest offset across every bucket the widget occupies, so a strip spanning an edge
115
+ * clears whatever is stacked in *both* of that edge's corners rather than only the one it is
116
+ * anchored to.
117
+ */
118
+ export declare function stackOffset(id: string, buckets: FloatAnchor[], stacks: Record<FloatAnchor, string[]>, sizes: Record<string, number>, fallbackHeight: number): {
119
+ offset: number;
120
+ registered: boolean;
121
+ };
@@ -0,0 +1,61 @@
1
+ import type { Component } from 'vue';
2
+ import type { FloatAnchor, Label } from '../types';
3
+ /** Defaults applied to every instance of a registered panel. All optional. */
4
+ export interface PanelDefaultOptions {
5
+ /** Tab and window title. */
6
+ title?: Label;
7
+ /** Icon shown in the tab, title bar and taskbar. */
8
+ icon?: Component;
9
+ /** Where the panel goes when first opened. @default 'docked' */
10
+ initialTarget?: 'floating' | 'docked' | 'tabbed';
11
+ /** Bounds used the first time the panel is floated. Numbers are px; strings any CSS length. */
12
+ favoritePosition?: {
13
+ x: number | string;
14
+ y: number | string;
15
+ width: number | string;
16
+ height: number | string;
17
+ };
18
+ /** Corner to pin newly-floated windows to. */
19
+ defaultAnchor?: FloatAnchor;
20
+ /** Show the close button. @default true */
21
+ canClose?: boolean;
22
+ /** Show the minimise button. @default true */
23
+ canMinimize?: boolean;
24
+ /** Allow dragging the tab, which is also what allows floating by drag. @default true */
25
+ canDrag?: boolean;
26
+ /** Show a letter tile instead of a live thumbnail in the taskbar hover preview. @default false */
27
+ disableLivePreview?: boolean;
28
+ /**
29
+ * Restore scroll offsets after the panel is re-parented. Turn off for a panel that
30
+ * manages virtualised scrolling itself and would rather react to `isMinimized`.
31
+ * @default true
32
+ * @see docs/decisions/0014-preserve-scroll-and-focus.md
33
+ */
34
+ preserveScroll?: boolean;
35
+ }
36
+ /** A registered panel kind. */
37
+ export interface PanelRegistryEntry {
38
+ component: Component;
39
+ defaultOptions?: PanelDefaultOptions;
40
+ }
41
+ /**
42
+ * The panel catalogue: component keys → components.
43
+ *
44
+ * One instance per workspace, never a module-level singleton, so two workspaces on a page
45
+ * cannot see each other's panels (docs/decisions/0004-store-outside-components.md).
46
+ */
47
+ export declare class PanelRegistry {
48
+ private entries;
49
+ /**
50
+ * Register a panel kind. `markRaw` is applied to the component: a component placed in
51
+ * reactive state would otherwise be deep-proxied by Vue, which both warns and is
52
+ * pointless — a component definition is never reactive data.
53
+ */
54
+ register(id: string, component: Component, defaultOptions?: PanelDefaultOptions): void;
55
+ /** Look up a panel kind, or `undefined` if the key was never registered. */
56
+ get(id: string): PanelRegistryEntry | undefined;
57
+ /** Whether a key is registered. */
58
+ has(id: string): boolean;
59
+ /** Every registered key. */
60
+ keys(): string[];
61
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Whether a value can round-trip through `JSON.stringify`/`JSON.parse` without silently
3
+ * losing information.
4
+ *
5
+ * Deliberately **not** a `JSON.stringify` try/catch: that does not throw for the failure
6
+ * case this guards against — a function-valued property is quietly dropped, not rejected.
7
+ * This walks the value tree instead, returning `false` as soon as it finds a function,
8
+ * symbol, `undefined`, VNode, or any non-plain object (a class instance, `Map`, `Set`,
9
+ * `RegExp`, …).
10
+ *
11
+ * A Vue **component** needs no special case: an options object or SFC carries `setup`,
12
+ * `render` or similar function properties, and the recursive walk rejects any object
13
+ * containing a function. An async component *is* a function and is rejected directly.
14
+ *
15
+ * `Date` is an explicit exception — serialisable-enough, matching `JSON.stringify`'s own
16
+ * behaviour — even though it comes back as an ISO string rather than a `Date`. That is a
17
+ * smaller, documentable gotcha than a silently vanishing function.
18
+ *
19
+ * This classification is a **compatibility surface**: it must agree with
20
+ * react-dockable-desktop's, or the same workspace would prune different panels in each
21
+ * library. See docs/decisions/0009-layout-json-compatibility.md.
22
+ *
23
+ * Pass raw values. A reactive proxy of plain data does pass, but prefer `toRaw()` at the
24
+ * call site so the answer is about the data and not about Vue's wrapper.
25
+ */
26
+ export declare function isSerializable(value: unknown): boolean;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Reading a saved layout.
3
+ *
4
+ * The format is byte-compatible with react-dockable-desktop 6.2.0 in both directions at
5
+ * `version: 2` — a hard requirement, see docs/decisions/0009-layout-json-compatibility.md.
6
+ * Anything in this file that changes what is read or written is a compatibility change.
7
+ */
8
+ import type { FloatingWindow, LayoutNode, PanelInfo } from '../types';
9
+ /** A validated payload, with `activePanelId` already resolved. */
10
+ export interface ParsedLayout {
11
+ gridRoot: LayoutNode;
12
+ floating: FloatingWindow[];
13
+ minimized: {
14
+ id: string;
15
+ title: PanelInfo['title'];
16
+ component: string;
17
+ }[];
18
+ panels: Record<string, PanelInfo>;
19
+ activePanelId: string | null;
20
+ }
21
+ /**
22
+ * Validate and normalise a parsed JSON payload, or return `null` if it is not a layout.
23
+ *
24
+ * Both entry points — a workspace's `initialState` and `loadLayout()` — go through here, so
25
+ * the shape check, the migration and the `activePanelId` resolution cannot drift apart
26
+ * between them. In rdd they once did: only one of the two ran the migration.
27
+ */
28
+ export declare function parseLayoutPayload(parsed: unknown, onWarn?: (msg: string) => void): ParsedLayout | null;
29
+ /** Parse a layout string, falling back to an empty workspace. Never throws. */
30
+ export declare function parseInitialState(json: string | null | undefined, onWarn?: (msg: string) => void): ParsedLayout;
31
+ /** The schema version this library writes. Changing it is a breaking change for both libraries. */
32
+ export declare const LAYOUT_VERSION = 2;