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 @@
1
+ {"version":3,"file":"index.cjs","names":["$slots","$slots","$attrs","$slots"],"sources":["../src/core/layoutTree.ts","../src/core/serialize.ts","../src/core/serializable.ts","../src/core/registry.ts","../src/core/eventBus.ts","../src/core/contextMenu.ts","../src/core/messages.ts","../src/core/toolbarState.ts","../src/core/overlays.ts","../src/core/contributions.ts","../src/core/workspace.ts","../src/core/anchorGeometry.ts","../src/core/panelDom.ts","../src/composables/usePanelDom.ts","../src/composables/useDragDock.ts","../src/core/panelMenu.ts","../src/composables/useWorkspace.ts","../src/core/dragResize.ts","../src/components/VddPanelSlot.vue","../src/components/VddPanelSlot.vue","../src/components/VddDropZones.vue","../src/components/VddDropZones.vue","../src/components/VddLeafGroup.vue","../src/components/VddLeafGroup.vue","../src/components/VddWorkspaceGrid.vue","../src/components/VddWorkspaceGrid.vue","../src/composables/usePanel.ts","../src/components/VddPanelMount.vue","../src/components/VddPanelMount.vue","../src/components/VddFloatingWindow.vue","../src/components/VddFloatingWindow.vue","../src/components/VddEdgeZones.vue","../src/components/VddEdgeZones.vue","../src/components/VddDragGhost.vue","../src/components/VddDragGhost.vue","../src/components/VddTaskbarPreview.vue","../src/components/VddTaskbarPreview.vue","../src/components/VddTaskbar.vue","../src/components/VddTaskbar.vue","../src/components/VddDesktop.vue","../src/components/VddDesktop.vue","../src/components/VddContextMenu.vue","../src/components/VddContextMenu.vue","../src/core/sidebarTypes.ts","../src/components/VddSidebarRail.vue","../src/components/VddSidebarRail.vue","../src/components/VddSidebarTabScope.vue","../src/components/VddSidebarTabScope.vue","../src/components/VddSidebarDrawer.vue","../src/components/VddSidebarDrawer.vue","../src/components/VddSidebar.vue","../src/components/VddSidebar.vue","../src/components/VddSecondarySidebar.vue","../src/components/VddSecondarySidebar.vue","../src/core/toolbarTypes.ts","../src/components/VddToolbarGroupButton.vue","../src/components/VddToolbarGroupButton.vue","../src/components/VddToolbar.vue","../src/components/VddToolbar.vue","../src/composables/useOverlayHost.ts","../src/components/VddOverlayFrame.vue","../src/components/VddOverlayFrame.vue","../src/components/VddSidePanelHost.vue","../src/components/VddSidePanelHost.vue","../src/components/VddSidePanels.vue","../src/components/VddSidePanels.vue","../src/components/VddModalHost.vue","../src/components/VddModalHost.vue","../src/components/VddConfirm.vue","../src/components/VddConfirm.vue","../src/components/VddModals.vue","../src/components/VddModals.vue","../src/core/toast.ts","../src/components/VddToastIcon.vue","../src/components/VddToastIcon.vue","../src/components/VddToastItem.vue","../src/components/VddToastItem.vue","../src/components/VddToasts.vue","../src/components/VddToasts.vue","../src/core/stretch.ts","../src/core/panelOverlay.ts","../src/core/overlayState.ts","../src/composables/usePanelOverlay.ts","../src/components/VddFloatingWidget.vue","../src/components/VddFloatingWidget.vue","../src/components/VddPanelOverlay.vue","../src/components/VddPanelOverlay.vue","../src/components/VddPanelToolbar.vue","../src/components/VddPanelToolbar.vue","../src/components/VddToolbarButton.vue","../src/components/VddToolbarButton.vue","../src/components/VddToolbarToggle.vue","../src/components/VddToolbarToggle.vue","../src/components/VddToolbarSeparator.vue","../src/components/VddToolbarSeparator.vue","../src/components/VddToolbarSpacer.vue","../src/components/VddToolbarSpacer.vue","../src/components/VddToolbarItem.vue","../src/components/VddToolbarItem.vue","../src/components/VddToolbarCenter.vue","../src/components/VddToolbarCenter.vue","../src/components/VddToolbarSearch.vue","../src/components/VddToolbarSearch.vue","../src/composables/useContextMenu.ts","../src/composables/useSidebar.ts","../src/composables/useToolbar.ts","../src/composables/useOverlays.ts","../src/composables/useContributions.ts","../src/composables/useColorScheme.ts","../src/index.ts"],"sourcesContent":["/**\n * Pure operations on the layout tree.\n *\n * Every function here takes a tree and returns a new one — no mutation, no reactivity, no\n * Vue. In react-dockable-desktop these lived as closures inside the provider and could\n * only be tested through the rendered component; extracting them means the tree maths is\n * directly testable, which is where the subtle bugs are (see `deriveActivePanelId`).\n */\nimport type { FloatingWindow, LayoutLeafNode, LayoutNode, PanelInfo, SplitDirection } from '../types'\n\n/** An empty workspace: one leaf, no panels. */\nexport const EMPTY_LEAF: LayoutLeafNode = {\n type: 'leaf',\n id: 'group-default',\n panels: [],\n activePanelId: null,\n}\n\n/** A fresh empty root, for when removing the last panel collapses the tree to nothing. */\nexport const emptyRoot = (): LayoutLeafNode => ({ ...EMPTY_LEAF, panels: [] })\n\n/**\n * Remove a panel from the tree.\n *\n * Returns `null` when the node itself should disappear — an emptied leaf without\n * `keepOnEmpty`, or a branch left with no children. A branch left with one child collapses\n * to that child, and surviving `sizes` are re-normalised to sum to 1.\n *\n * When the removed panel was its leaf's selected tab, selection moves to the next tab, then\n * the previous, then the first — the order a user expects after closing a tab.\n */\nexport function removePanelFromTree(node: LayoutNode, id: string): LayoutNode | null {\n if (node.type === 'leaf') {\n const idx = node.panels.indexOf(id)\n if (idx === -1) return node\n const panels = node.panels.filter(p => p !== id)\n const activePanelId = node.activePanelId === id\n ? (panels[idx] ?? panels[idx - 1] ?? panels[0] ?? null)\n : node.activePanelId\n if (panels.length === 0 && !node.keepOnEmpty) return null\n return { ...node, panels, activePanelId }\n }\n\n const children = node.children\n .map(c => removePanelFromTree(c, id))\n .filter((c): c is LayoutNode => c !== null)\n\n if (children.length === 0) return null\n if (children.length === 1) return children[0]!\n\n const sizes = node.sizes.slice(0, children.length)\n const sum = sizes.reduce((a, b) => a + b, 0)\n return { ...node, children, sizes: sizes.map(s => s / sum) }\n}\n\n/**\n * Add a panel to a leaf. A panel already in that leaf is not duplicated.\n *\n * `select: false` adds it **without** making it the leaf's selected tab — what \"open in the\n * background\" has to mean. Selecting it anyway would make the newly added panel the visible\n * tab while some other panel stayed globally active, which is precisely the disagreement\n * between leaf selection and `activePanelId` that divergence D2 is about. (rdd's\n * `focus: false` did exactly that.)\n */\nexport function addPanelToLeaf(\n node: LayoutNode, leafId: string, panelId: string, options?: { select?: boolean },\n): LayoutNode {\n const select = options?.select !== false\n if (node.type === 'leaf') {\n if (node.id !== leafId) return node\n const panels = node.panels.includes(panelId) ? node.panels : [...node.panels, panelId]\n const activePanelId = select ? panelId : (node.activePanelId ?? panelId)\n return { ...node, panels, activePanelId }\n }\n return { ...node, children: node.children.map(c => addPanelToLeaf(c, leafId, panelId, options)) }\n}\n\n/** Insert a panel into a leaf at a specific tab index, and select it. */\nexport function insertPanelInLeaf(\n node: LayoutNode, leafId: string, panelId: string, index: number,\n): LayoutNode {\n if (node.type === 'leaf') {\n if (node.id !== leafId) return node\n const remaining = node.panels.filter(p => p !== panelId)\n const at = Math.max(0, Math.min(index, remaining.length))\n const panels = [...remaining]\n panels.splice(at, 0, panelId)\n return { ...node, panels, activePanelId: panelId }\n }\n return { ...node, children: node.children.map(c => insertPanelInLeaf(c, leafId, panelId, index)) }\n}\n\n/** Default id scheme for leaves created by a split. Any string is valid; this one is legible. */\nexport const defaultLeafId = (kind: 'split' | 'edge'): string =>\n `group-${kind}-${Date.now()}-${Math.floor(Math.random() * 1000)}`\n\n/**\n * Split a leaf, putting `panelId` in a new leaf on the given side.\n *\n * `newLeafId` is injectable so tests can assert tree shape without a timestamp in it.\n */\nexport function splitLeafInTree(\n node: LayoutNode,\n leafId: string,\n panelId: string,\n position: SplitDirection,\n splitRatio: number,\n newLeafId: string = defaultLeafId('split'),\n): LayoutNode {\n if (node.type === 'leaf') {\n if (node.id !== leafId) return node\n const newLeaf: LayoutLeafNode = { type: 'leaf', id: newLeafId, panels: [panelId], activePanelId: panelId }\n const first = position === 'left' || position === 'top'\n return {\n type: 'branch',\n orientation: position === 'left' || position === 'right' ? 'horizontal' : 'vertical',\n sizes: first ? [splitRatio, 1 - splitRatio] : [1 - splitRatio, splitRatio],\n children: first ? [newLeaf, node] : [node, newLeaf],\n }\n }\n return {\n ...node,\n children: node.children.map(c => splitLeafInTree(c, leafId, panelId, position, splitRatio, newLeafId)),\n }\n}\n\n/** Wrap the whole tree in a branch, docking `panelId` as a full-width/height edge row. */\nexport function dockToEdge(\n root: LayoutNode,\n panelId: string,\n position: SplitDirection,\n edgeRatio: number,\n newLeafId: string = defaultLeafId('edge'),\n): LayoutNode {\n const newLeaf: LayoutLeafNode = { type: 'leaf', id: newLeafId, panels: [panelId], activePanelId: panelId }\n const first = position === 'left' || position === 'top'\n return {\n type: 'branch',\n orientation: position === 'left' || position === 'right' ? 'horizontal' : 'vertical',\n sizes: first ? [edgeRatio, 1 - edgeRatio] : [1 - edgeRatio, edgeRatio],\n children: first ? [newLeaf, root] : [root, newLeaf],\n }\n}\n\n/** Remove a leaf entirely, honouring its `canClose`. Collapses and re-normalises like removal. */\nexport function removeLeafFromTree(node: LayoutNode, leafId: string): LayoutNode | null {\n if (node.type === 'leaf') return node.id === leafId && node.canClose !== false ? null : node\n const children = node.children\n .map(c => removeLeafFromTree(c, leafId))\n .filter((c): c is LayoutNode => c !== null)\n if (children.length === 0) return null\n if (children.length === 1) return children[0]!\n const sizes = node.sizes.slice(0, children.length)\n const sum = sizes.reduce((a, b) => a + b, 0)\n return { ...node, children, sizes: sizes.map(s => s / sum) }\n}\n\n/** Select a panel as its leaf's active tab, wherever it is. */\nexport function selectPanelInTree(node: LayoutNode, id: string): LayoutNode {\n if (node.type === 'leaf') return node.panels.includes(id) ? { ...node, activePanelId: id } : node\n return { ...node, children: node.children.map(c => selectPanelInTree(c, id)) }\n}\n\n/** The first leaf in document order, depth-first. */\nexport function findFirstLeafId(node: LayoutNode): string | null {\n if (node.type === 'leaf') return node.id\n for (const child of node.children) {\n const id = findFirstLeafId(child)\n if (id) return id\n }\n return null\n}\n\n/** The leaf containing a panel, or `null`. */\nexport function findLeafForPanel(node: LayoutNode, id: string): string | null {\n if (node.type === 'leaf') return node.panels.includes(id) ? node.id : null\n for (const child of node.children) {\n const found = findLeafForPanel(child, id)\n if (found) return found\n }\n return null\n}\n\n/** A leaf by id, or `null`. */\nexport function findLeaf(node: LayoutNode | null, leafId: string): LayoutLeafNode | null {\n if (!node) return null\n if (node.type === 'leaf') return node.id === leafId ? node : null\n for (const child of node.children) {\n const found = findLeaf(child, leafId)\n if (found) return found\n }\n return null\n}\n\n/** Whether a leaf exists in the tree. */\nexport function leafExists(node: LayoutNode, leafId: string): boolean {\n return findLeaf(node, leafId) !== null\n}\n\n/** Update the sizes of the branch at `path` (a list of child indices from the root). */\nexport function updateSizesAtPath(node: LayoutNode, path: number[], sizes: number[]): LayoutNode {\n const walk = (n: LayoutNode, depth: number): LayoutNode => {\n if (n.type === 'leaf') return n\n if (depth === path.length) return { ...n, sizes }\n const idx = path[depth]!\n return { ...n, children: n.children.map((c, i) => (i === idx ? walk(c, depth + 1) : c)) }\n }\n return walk(node, 0)\n}\n\n// ── Which panel is active ────────────────────────────────────────────────────\n\n/** The subset of a layout needed to reason about visibility. */\nexport interface ActiveTargetScope {\n gridRoot: LayoutNode | null\n floating: FloatingWindow[]\n panels: Record<string, PanelInfo>\n}\n\n/**\n * Whether `id` names a panel the user can actually see, and which may therefore be the\n * globally active one: the selected tab of some leaf, or a floating window.\n *\n * A minimised panel never qualifies. It stays mounted, so leaving it active would keep\n * routing contributed controls to a panel that is not on screen — the defect family that\n * cost react-dockable-desktop two releases.\n *\n * Used both to validate a persisted `activePanelId` on load and to guard the one written on\n * save, so the two directions cannot disagree about what \"active\" is allowed to mean.\n */\nexport function isVisibleActiveTarget(id: string, scope: ActiveTargetScope): boolean {\n const info = scope.panels[id]\n if (!info || info.state === 'minimized') return false\n if (scope.floating.some(w => w.id === id)) return true\n const isLeafSelection = (node: LayoutNode): boolean =>\n node.type === 'leaf' ? node.activePanelId === id : node.children.some(isLeafSelection)\n return scope.gridRoot ? isLeafSelection(scope.gridRoot) : false\n}\n\n/**\n * Derive the globally active panel, in order:\n *\n * 1. the first leaf in document order whose own selected tab is a valid target;\n * 2. otherwise the frontmost (highest `z`) floating window, so a float-only layout does\n * not come back with nothing active;\n * 3. otherwise `null`.\n *\n * **Depth-first, not breadth-first.** For a grid whose first child is itself a split, a\n * level-by-level walk reaches the *second* child's leaf before the first child's own\n * children, and picks the wrong tab. Mirrors `findFirstLeafId`'s traversal for that reason.\n *\n * Never an arbitrary entry in `panels`: that seed is what made a restored workspace come\n * back with one panel visible and a different, invisible one marked active.\n */\nexport function deriveActivePanelId(scope: ActiveTargetScope): string | null {\n const isCandidate = (id: string | null): boolean =>\n id !== null && !!scope.panels[id] && scope.panels[id]!.state !== 'minimized'\n\n const fromLeaves = (node: LayoutNode): string | null => {\n if (node.type === 'leaf') return isCandidate(node.activePanelId) ? node.activePanelId : null\n for (const child of node.children) {\n const found = fromLeaves(child)\n if (found) return found\n }\n return null\n }\n\n const selected = scope.gridRoot ? fromLeaves(scope.gridRoot) : null\n if (selected) return selected\n\n let frontmost: FloatingWindow | null = null\n for (const w of scope.floating) {\n if (!isCandidate(w.id)) continue\n if (!frontmost || w.z > frontmost.z) frontmost = w\n }\n return frontmost?.id ?? null\n}\n","/**\n * Reading a saved layout.\n *\n * The format is byte-compatible with react-dockable-desktop 6.2.0 in both directions at\n * `version: 2` — a hard requirement, see docs/decisions/0009-layout-json-compatibility.md.\n * Anything in this file that changes what is read or written is a compatibility change.\n */\nimport type { FloatAnchor, FloatingWindow, LayoutNode, PanelInfo, SerializedLayout } from '../types'\nimport { deriveActivePanelId, isVisibleActiveTarget, EMPTY_LEAF } from './layoutTree'\nimport type { ActiveTargetScope } from './layoutTree'\n\n/** A validated payload, with `activePanelId` already resolved. */\nexport interface ParsedLayout {\n gridRoot: LayoutNode\n floating: FloatingWindow[]\n minimized: { id: string; title: PanelInfo['title']; component: string }[]\n panels: Record<string, PanelInfo>\n activePanelId: string | null\n}\n\n/**\n * Pre-`anchor` layouts stored two booleans instead of a corner. Carried over verbatim from\n * rdd, including its quirk that `stickyBottom` alone maps to `'bottom-left'`.\n */\nfunction migrateFloating(floating: unknown[]): FloatingWindow[] {\n return floating.map((raw) => {\n const fw = raw as Record<string, unknown>\n if (!('stickyRight' in fw) && !('stickyBottom' in fw)) return fw as unknown as FloatingWindow\n const anchor: FloatAnchor | null =\n fw.stickyRight && fw.stickyBottom ? 'bottom-right'\n : fw.stickyRight ? 'top-right'\n : fw.stickyBottom ? 'bottom-left'\n : null\n const { stickyRight: _sr, stickyBottom: _sb, ...rest } = fw\n return { ...rest, anchor } as unknown as FloatingWindow\n })\n}\n\n/**\n * Validate and normalise a parsed JSON payload, or return `null` if it is not a layout.\n *\n * Both entry points — a workspace's `initialState` and `loadLayout()` — go through here, so\n * the shape check, the migration and the `activePanelId` resolution cannot drift apart\n * between them. In rdd they once did: only one of the two ran the migration.\n */\nexport function parseLayoutPayload(parsed: unknown, onWarn?: (msg: string) => void): ParsedLayout | null {\n if (!parsed || typeof parsed !== 'object') return null\n const p = parsed as Record<string, unknown>\n if (!p.gridRoot || !Array.isArray(p.floating) || !Array.isArray(p.minimized) || !p.panels) return null\n\n const floating = migrateFloating(p.floating)\n const gridRoot = p.gridRoot as LayoutNode\n const panels = p.panels as Record<string, PanelInfo>\n const scope: ActiveTargetScope = { gridRoot, floating, panels }\n\n // A persisted value wins while it still names a visible panel. Anything stale — the panel\n // was closed, minimised, or pruned from this snapshot — falls back to deriving from the\n // grid, which is also the path every pre-`activePanelId` layout takes.\n const persisted = typeof p.activePanelId === 'string' ? p.activePanelId : null\n let activePanelId: string | null = null\n if (persisted !== null) {\n if (isVisibleActiveTarget(persisted, scope)) activePanelId = persisted\n else onWarn?.(\n `Ignoring the saved layout's activePanelId (\"${persisted}\") — it does not name a ` +\n `currently visible panel (it may have been closed, minimised, or excluded from the ` +\n `snapshot as non-serialisable). Falling back to the selected tab of the first leaf.`,\n )\n }\n if (activePanelId === null) activePanelId = deriveActivePanelId(scope)\n\n return {\n gridRoot,\n floating,\n minimized: p.minimized as ParsedLayout['minimized'],\n panels,\n activePanelId,\n }\n}\n\n/** Parse a layout string, falling back to an empty workspace. Never throws. */\nexport function parseInitialState(json: string | null | undefined, onWarn?: (msg: string) => void): ParsedLayout {\n if (json) {\n try {\n const payload = parseLayoutPayload(JSON.parse(json), onWarn)\n if (payload) return payload\n } catch {\n onWarn?.('initialState is not valid JSON; starting from an empty workspace.')\n }\n }\n return { gridRoot: { ...EMPTY_LEAF, panels: [] }, floating: [], minimized: [], panels: {}, activePanelId: null }\n}\n\n/** The schema version this library writes. Changing it is a breaking change for both libraries. */\nexport const LAYOUT_VERSION = 2 satisfies NonNullable<SerializedLayout['version']>\n","import { isVNode } from 'vue'\n\n/**\n * Whether a value can round-trip through `JSON.stringify`/`JSON.parse` without silently\n * losing information.\n *\n * Deliberately **not** a `JSON.stringify` try/catch: that does not throw for the failure\n * case this guards against — a function-valued property is quietly dropped, not rejected.\n * This walks the value tree instead, returning `false` as soon as it finds a function,\n * symbol, `undefined`, VNode, or any non-plain object (a class instance, `Map`, `Set`,\n * `RegExp`, …).\n *\n * A Vue **component** needs no special case: an options object or SFC carries `setup`,\n * `render` or similar function properties, and the recursive walk rejects any object\n * containing a function. An async component *is* a function and is rejected directly.\n *\n * `Date` is an explicit exception — serialisable-enough, matching `JSON.stringify`'s own\n * behaviour — even though it comes back as an ISO string rather than a `Date`. That is a\n * smaller, documentable gotcha than a silently vanishing function.\n *\n * This classification is a **compatibility surface**: it must agree with\n * react-dockable-desktop's, or the same workspace would prune different panels in each\n * library. See docs/decisions/0009-layout-json-compatibility.md.\n *\n * Pass raw values. A reactive proxy of plain data does pass, but prefer `toRaw()` at the\n * call site so the answer is about the data and not about Vue's wrapper.\n */\nexport function isSerializable(value: unknown): boolean {\n if (value === null) return true\n if (value === undefined) return false\n\n const type = typeof value\n if (type === 'string' || type === 'number' || type === 'boolean') return true\n if (type === 'function' || type === 'symbol' || type === 'bigint') return false\n\n // type === 'object' from here on.\n if (value instanceof Date) return true\n if (isVNode(value)) return false\n if (Array.isArray(value)) return value.every(isSerializable)\n\n const proto = Object.getPrototypeOf(value)\n if (proto !== Object.prototype && proto !== null) return false // class instance, Map, Set, RegExp, Error, …\n\n return Object.values(value as Record<string, unknown>).every(isSerializable)\n}\n","import { markRaw } from 'vue'\nimport type { Component } from 'vue'\nimport type { FloatAnchor, Label } from '../types'\n\n/** Defaults applied to every instance of a registered panel. All optional. */\nexport interface PanelDefaultOptions {\n /** Tab and window title. */\n title?: Label\n /** Icon shown in the tab, title bar and taskbar. */\n icon?: Component\n /** Where the panel goes when first opened. @default 'docked' */\n initialTarget?: 'floating' | 'docked' | 'tabbed'\n /** Bounds used the first time the panel is floated. Numbers are px; strings any CSS length. */\n favoritePosition?: { x: number | string; y: number | string; width: number | string; height: number | string }\n /** Corner to pin newly-floated windows to. */\n defaultAnchor?: FloatAnchor\n /** Show the close button. @default true */\n canClose?: boolean\n /** Show the minimise button. @default true */\n canMinimize?: boolean\n /** Allow dragging the tab, which is also what allows floating by drag. @default true */\n canDrag?: boolean\n /** Show a letter tile instead of a live thumbnail in the taskbar hover preview. @default false */\n disableLivePreview?: boolean\n /**\n * Restore scroll offsets after the panel is re-parented. Turn off for a panel that\n * manages virtualised scrolling itself and would rather react to `isMinimized`.\n * @default true\n * @see docs/decisions/0014-preserve-scroll-and-focus.md\n */\n preserveScroll?: boolean\n}\n\n/** A registered panel kind. */\nexport interface PanelRegistryEntry {\n component: Component\n defaultOptions?: PanelDefaultOptions\n}\n\n/**\n * The panel catalogue: component keys → components.\n *\n * One instance per workspace, never a module-level singleton, so two workspaces on a page\n * cannot see each other's panels (docs/decisions/0004-store-outside-components.md).\n */\nexport class PanelRegistry {\n private entries = new Map<string, PanelRegistryEntry>()\n\n /**\n * Register a panel kind. `markRaw` is applied to the component: a component placed in\n * reactive state would otherwise be deep-proxied by Vue, which both warns and is\n * pointless — a component definition is never reactive data.\n */\n register(id: string, component: Component, defaultOptions?: PanelDefaultOptions): void {\n this.entries.set(id, {\n component: markRaw(component),\n ...(defaultOptions ? { defaultOptions: { ...defaultOptions, ...(defaultOptions.icon ? { icon: markRaw(defaultOptions.icon) } : {}) } } : {}),\n })\n }\n\n /** Look up a panel kind, or `undefined` if the key was never registered. */\n get(id: string): PanelRegistryEntry | undefined {\n return this.entries.get(id)\n }\n\n /** Whether a key is registered. */\n has(id: string): boolean {\n return this.entries.has(id)\n }\n\n /** Every registered key. */\n keys(): string[] {\n return Array.from(this.entries.keys())\n }\n}\n","/**\n * The inter-panel event bus.\n *\n * Panels are deliberately unaware of each other; this is how they communicate. It is a\n * domain feature, not DOM events, so it stays a workspace method rather than becoming\n * component `emits`.\n *\n * Framework-agnostic: subscriptions made from a component are disposed by the composable\n * that wraps this, not by the bus itself.\n */\n\n/** Events the library publishes itself. An application's own events are merged with these. */\nexport interface BuiltInEvents {\n 'panel:opened': { id: string; component: string }\n 'panel:closed': { id: string }\n 'panel:minimized': { id: string }\n 'panel:restored': { id: string }\n /** The globally active panel changed. The ordered channel for handing off between panels —\n * a watcher on `usePanel().isActive` sees the same change but not the ordering. */\n 'panel:activated': { id: string | null; previous: string | null }\n /**\n * Anything `saveLayout()` would capture has changed: open, close, minimise, restore, a\n * dedupe redirect, and every placement change (dock, float, reorder, edge-dock, split\n * resize). Coalesced so an autosave needs one subscription.\n *\n * Does **not** fire when an `onSaveState` provider's own return value changes — that is a\n * pull, and nothing can observe it changing. Save on your own trigger too if it matters.\n */\n 'layout:changed': Record<string, never>\n /**\n * Fired from inside `saveLayout()`, only when that call excluded at least one panel whose\n * current props failed the serialisability check. A passive flag is not enough: nobody may\n * be looking at the moment something silently drops out of a snapshot.\n */\n 'layout:panels-excluded': { panels: { id: string; component: string }[] }\n}\n\ntype Listener = (data: unknown) => void\n\nexport class EventBus<TEvents extends Record<string, unknown> = Record<string, unknown>> {\n private listeners = new Map<string, Set<Listener>>()\n\n subscribe<K extends keyof (TEvents & BuiltInEvents) & string>(\n event: K,\n callback: (data: (TEvents & BuiltInEvents)[K]) => void,\n ): () => void {\n let set = this.listeners.get(event)\n if (!set) { set = new Set(); this.listeners.set(event, set) }\n const listener = callback as Listener\n set.add(listener)\n return () => { set!.delete(listener) }\n }\n\n publish<K extends keyof (TEvents & BuiltInEvents) & string>(\n event: K,\n data: (TEvents & BuiltInEvents)[K],\n ): void {\n this.dispatch(event, data)\n }\n\n /**\n * Publish one of the library's own events.\n *\n * Separate from `publish` because `TEvents & BuiltInEvents` is not provably satisfied by a\n * built-in payload: an application could declare its own narrower type for a built-in key.\n * This path is typed against `BuiltInEvents` alone, so the library's own call sites are\n * checked properly instead of being cast.\n *\n * @internal\n */\n emit<K extends keyof BuiltInEvents & string>(event: K, data: BuiltInEvents[K]): void {\n this.dispatch(event, data)\n }\n\n private dispatch(event: string, data: unknown): void {\n const set = this.listeners.get(event)\n if (!set) return\n // Iterate a copy: a listener that unsubscribes itself (or another) during dispatch must\n // not change the set being walked.\n for (const listener of Array.from(set)) listener(data)\n }\n\n /** Listener count for an event — for tests and diagnostics. */\n count(event: string): number {\n return this.listeners.get(event)?.size ?? 0\n }\n\n /** Drop every listener. Called by `workspace.dispose()`. */\n clear(): void {\n this.listeners.clear()\n }\n}\n","/**\n * Context-menu item shapes.\n *\n * Names and fields are kept identical to react-dockable-desktop's, so menu definitions port\n * across unchanged — a menu is data, and this is the one part of the API where a React\n * app's arrays can be reused verbatim.\n */\nimport type { Component } from 'vue'\nimport type { Label } from '../types'\n\n/** The optional checkbox column on a simple item. */\nexport interface ContextMenuCheckbox {\n /** Whether the checkbox column renders at all. @default true */\n active?: boolean\n /** Whether the item is interactive. Prefer the item's own `disabled`. @default true */\n enabled?: boolean\n /** Current checked state. */\n value: boolean\n}\n\n/** A normal menu item. */\nexport interface ContextMenuSimpleItem {\n label: Label\n icon?: Component\n /** Tooltip. */\n title?: Label\n checkbox?: ContextMenuCheckbox\n action?: () => void\n /** Test hook, rendered as `data-cy-action`. */\n cyAction?: string\n disabled?: boolean\n}\n\n/** A divider. */\nexport interface ContextMenuSeparator {\n separator: true\n}\n\n/** An item that opens a submenu. One level deep, as in rdd. */\nexport interface ContextMenuSubMenu {\n label: Label\n title?: Label\n items?: ContextMenuItem[]\n}\n\nexport type ContextMenuItem = ContextMenuSimpleItem | ContextMenuSeparator | ContextMenuSubMenu\n\n/** What to show, and where. */\nexport interface ShowContextMenuOptions {\n /** Position from an event. Takes precedence over `x`/`y`. */\n event?: MouseEvent | PointerEvent | TouchEvent\n x?: number\n y?: number\n items: ContextMenuItem[]\n}\n\nexport const isSeparator = (item: ContextMenuItem): item is ContextMenuSeparator =>\n 'separator' in item\n\nexport const isSubMenu = (item: ContextMenuItem): item is ContextMenuSubMenu =>\n !isSeparator(item) && 'items' in item\n\n/** Where a menu should open, from whatever the caller supplied. */\nexport function menuPosition(options: ShowContextMenuOptions): { x: number; y: number } {\n const event = options.event\n if (!event) return { x: options.x ?? 0, y: options.y ?? 0 }\n if ('touches' in event && event.touches.length > 0) {\n const touch = event.touches[0]!\n return { x: touch.clientX, y: touch.clientY }\n }\n const mouse = event as MouseEvent\n return { x: mouse.clientX, y: mouse.clientY }\n}\n\n/**\n * Keep a menu inside the viewport.\n *\n * Pure, so the clamping is testable without layout: the caller measures, this decides.\n */\nexport function clampToViewport(\n position: { x: number; y: number },\n size: { width: number; height: number },\n viewport: { width: number; height: number },\n padding = 8,\n): { x: number; y: number } {\n let { x, y } = position\n if (x + size.width > viewport.width - padding) x = Math.max(padding, viewport.width - size.width - padding)\n if (y + size.height > viewport.height - padding) y = Math.max(padding, viewport.height - size.height - padding)\n return { x: Math.max(padding, x), y: Math.max(padding, y) }\n}\n","import type { Label, MessageDescriptor, MessageFormatter } from '../types'\n\n/**\n * The library's own UI strings.\n *\n * Each `id` is the key an application defines in its i18n message table; `defaultMessage`\n * is the fallback when no formatter is supplied. Override any subset via\n * `createWorkspace({ messages })` — the object is merged, so partial overrides work.\n */\nexport const defaultMessages = {\n floatWindow: { id: 'vdd.floatWindow', defaultMessage: 'Float Window' },\n minimizePanel: { id: 'vdd.minimizePanel', defaultMessage: 'Minimize Panel' },\n closeTab: { id: 'vdd.closeTab', defaultMessage: 'Close Tab' },\n restorePanel: { id: 'vdd.restorePanel', defaultMessage: 'Restore Panel' },\n maximizePanel: { id: 'vdd.maximizePanel', defaultMessage: 'Maximize Panel' },\n closePanel: { id: 'vdd.closePanel', defaultMessage: 'Close Panel' },\n dockWindow: { id: 'vdd.dockWindow', defaultMessage: 'Dock Window' },\n minimize: { id: 'vdd.minimize', defaultMessage: 'Minimize' },\n maximize: { id: 'vdd.maximize', defaultMessage: 'Maximize' },\n restoreSize: { id: 'vdd.restoreSize', defaultMessage: 'Restore Size' },\n close: { id: 'vdd.close', defaultMessage: 'Close' },\n closeEmptyGroup: { id: 'vdd.closeEmptyGroup', defaultMessage: 'Close empty split group' },\n emptyGroup: { id: 'vdd.emptyGroup', defaultMessage: 'Empty workspace section' },\n unsavedChangesTitle: { id: 'vdd.unsavedChangesTitle', defaultMessage: 'Unsaved Changes' },\n unsavedChangesMessage: {\n id: 'vdd.unsavedChangesMessage',\n defaultMessage: '\"{title}\" has unsaved changes. Do you want to discard your changes and close?',\n },\n discardChanges: { id: 'vdd.discardChanges', defaultMessage: 'Discard Changes' },\n cancel: { id: 'vdd.cancel', defaultMessage: 'Cancel' },\n yes: { id: 'vdd.yes', defaultMessage: 'Yes' },\n no: { id: 'vdd.no', defaultMessage: 'No' },\n ok: { id: 'vdd.ok', defaultMessage: 'OK' },\n closeTooltip: { id: 'vdd.closeTooltip', defaultMessage: 'Close' },\n scrollTabsLeft: { id: 'vdd.scrollTabsLeft', defaultMessage: 'Scroll tabs left' },\n scrollTabsRight: { id: 'vdd.scrollTabsRight', defaultMessage: 'Scroll tabs right' },\n moreActions: { id: 'vdd.moreActions', defaultMessage: 'More actions' },\n search: { id: 'vdd.search', defaultMessage: 'Search' },\n} as const satisfies Record<string, MessageDescriptor>\n\n/**\n * Every message key. Import it in your own message table to get a compile-time guarantee\n * that all keys are present and none are misspelled.\n */\nexport type MessageKey = keyof typeof defaultMessages\n\n/** Resolve a label with a formatter, falling back to its `defaultMessage` then its `id`. */\nexport function formatLabel(\n label: Label | undefined,\n format?: MessageFormatter,\n): string {\n if (label === undefined || label === null) return ''\n if (typeof label === 'string') return label\n if (format) return format(label)\n let text = label.defaultMessage ?? label.id\n if (label.values) {\n for (const [key, value] of Object.entries(label.values)) {\n text = text.replace(`{${key}}`, String(value))\n }\n }\n return text\n}\n","/**\n * Toolbar selection state: which item is active in each radio group, and which toggles are on.\n *\n * Lives on the workspace so it needs no provider of its own and is reachable from anywhere,\n * including outside components. Every item can also be *controlled* instead — see\n * `VddToolbar` — in which case none of this is consulted for that item.\n */\nimport { reactive } from 'vue'\n\nexport interface ToolbarState {\n /** The active item in a radio group, or `null`. */\n activeInGroup: (group: string) => string | null\n /** Set the active item in a group. `null` clears it. */\n setActiveInGroup: (group: string, id: string | null) => void\n /** Whether a toggle is on. */\n isToggled: (id: string) => boolean\n /** Set a toggle explicitly. */\n setToggled: (id: string, on: boolean) => void\n /** Flip a toggle. */\n toggle: (id: string) => void\n}\n\nexport function createToolbarState(): ToolbarState {\n const groups = reactive<Record<string, string | null>>({})\n const toggles = reactive<Record<string, boolean>>({})\n\n return {\n activeInGroup: (group) => groups[group] ?? null,\n setActiveInGroup: (group, id) => { groups[group] = id },\n isToggled: (id) => toggles[id] === true,\n setToggled: (id, on) => { toggles[id] = on },\n toggle: (id) => { toggles[id] = !toggles[id] },\n }\n}\n","/**\n * Side panels and modals: two drawers and a modal stack, all rendered outside the workspace.\n *\n * On the workspace store rather than behind a provider of its own\n * (docs/decisions/0004-store-outside-components.md), so `openModal()` works from a service,\n * a router guard or an event handler — none of which are components. rdd needed a\n * `<PanelProvider>` wrapping the app for this, and a second provider inside each rendered\n * instance to hand it the close contract.\n *\n * `requestClose` here is the *only* implementation of the close-with-guards-and-dirty-check\n * sequence. rdd had it twice, thirty-five identical lines in `SidePanelRenderer` and again in\n * `ModalStackRenderer` — which is how two containers end up disagreeing about what a dirty\n * panel does.\n */\nimport { markRaw, reactive, shallowReactive } from 'vue'\nimport type { Component } from 'vue'\nimport type { DirtyStateOptions, Label } from '../types'\n\n/** Which of the three overlay slots an instance occupies. */\nexport type OverlayKind = 'left-panel' | 'right-panel' | 'modal'\n\n/** Options for `openLeftPanel` / `openRightPanel`. */\nexport interface SidePanelOptions {\n title?: Label\n /** Rendered in the header, before the title. */\n icon?: Component\n /** A number is pixels; a string is used as-is (`'40vw'`). @default 400 */\n width?: number | string\n /**\n * Padding for the body. A number is pixels; a string is any CSS value or shorthand.\n * Nothing is set at all by default, so the stylesheet decides and content can go\n * edge-to-edge.\n */\n bodyPadding?: number | string\n}\n\n/** Options for `openModal`. */\nexport interface ModalOptions {\n title?: Label\n icon?: Component\n /** Maps to a `max-width` rule. @default 'auto' */\n size?: 'small' | 'medium' | 'large' | 'fullscreen' | 'auto'\n /** `false` removes the close button *and* dismissal by backdrop click or Escape. @default true */\n closable?: boolean\n /** As `SidePanelOptions.bodyPadding`. */\n bodyPadding?: number | string\n}\n\n/** One open side panel or modal. */\nexport interface OverlayInstance {\n id: string\n /** Kept raw: a component definition is not reactive data, and proxying one is wasteful. */\n component: Component\n props: Record<string, unknown>\n kind: OverlayKind\n options: SidePanelOptions & ModalOptions\n dirty: boolean\n dirtyOptions?: DirtyStateOptions\n}\n\nexport interface OverlayState {\n leftPanel: OverlayInstance | null\n rightPanel: OverlayInstance | null\n /** Bottom to top: the last entry is the topmost modal. */\n modals: OverlayInstance[]\n}\n\n/**\n * What the built-in unsaved-changes question needs to know.\n *\n * Deliberately not an `OverlayInstance` or a `PanelInfo`: a docked tab, a floating window, a\n * drawer and a modal all ask the same question, and narrowing it to these two fields is what\n * lets one implementation serve all four.\n */\nexport interface DiscardRequest {\n /** The thing being closed, already formatted — it appears in the message. */\n title: string\n dirtyOptions?: DirtyStateOptions\n}\n\n/** Ask the user whether to discard unsaved changes. Resolves `false` for every refusal. */\nexport type ConfirmDiscard = (request: DiscardRequest) => Promise<boolean>\n\nexport interface Overlays {\n readonly state: OverlayState\n /**\n * Open a drawer on the left. Resolves to the new instance's id, or `null` when a panel was\n * already open there and its close guard refused — a drawer is a single slot, so opening\n * one is also closing the other.\n */\n openLeftPanel(component: Component, props?: Record<string, unknown>, options?: SidePanelOptions): Promise<string | null>\n openRightPanel(component: Component, props?: Record<string, unknown>, options?: SidePanelOptions): Promise<string | null>\n /** Push a modal onto the stack. Synchronous: a stack has room for another. */\n openModal(component: Component, props?: Record<string, unknown>, options?: ModalOptions): string\n /** Remove an instance immediately, with no guard and no dirty check. */\n close(id: string): void\n closeAll(): void\n closeAllModals(): void\n getInstance(id: string): OverlayInstance | undefined\n updateInstance(id: string, updates: Partial<Pick<OverlayInstance, 'props' | 'options' | 'dirty' | 'dirtyOptions'>>): void\n setDirty(id: string, dirty: boolean, options?: DirtyStateOptions): void\n /**\n * Close, honouring this instance's close guard and its dirty state.\n *\n * `force` skips both. Without a `confirm`, a dirty instance stays open rather than\n * discarding the user's edits silently — the same refusal `requestClosePanel` makes.\n */\n requestClose(id: string, options?: { force?: boolean; confirm?: (instance: OverlayInstance) => Promise<boolean> }): Promise<void>\n /** Veto an instance's close. Returning `false` blocks it. */\n registerCloseGuard(id: string, guard: () => boolean | Promise<boolean>): () => void\n /** The topmost modal, or `null`. */\n topmostModal(): OverlayInstance | null\n /**\n * Ask the user whether to discard unsaved changes, as a modal on top of the stack.\n *\n * Resolves `false` when nothing can render the question — which is why a dirty close with\n * no `<VddModals>` mounted refuses instead of discarding. Every close path in the library\n * routes through this one function: a tab's ×, a floating window's ×, a drawer, a modal,\n * and `usePanel().close()`. rdd wired an equivalent separately into each container, and\n * the docked-panel path was wired to nothing at all.\n */\n confirmDiscard: ConfirmDiscard\n /**\n * Register the component that renders that question, and the formatter for its message.\n * Called by `<VddModals>` on mount: asking requires somewhere to render.\n * @internal\n */\n setConfirmRenderer(renderer: ConfirmRenderer | null): void\n /** @internal */\n dispose(): void\n}\n\n/** Supplied by `<VddModals>`: opens the question and resolves the user's answer. @internal */\nexport type ConfirmRenderer = (request: DiscardRequest) => Promise<boolean>\n\nlet counter = 0\n/** Ids are unique per process, and carry the kind so a stray id is readable in devtools. */\nconst nextId = (kind: OverlayKind): string => `vdd-${kind}-${++counter}`\n\nexport function createOverlays(): Overlays {\n // `shallowReactive` on the instance list: the instances hold component definitions and\n // caller props, and deep-proxying those would reach into application objects the library\n // has no business rewriting. Replacement, not mutation, is how an instance changes.\n const state = reactive<OverlayState>({\n leftPanel: null,\n rightPanel: null,\n modals: shallowReactive([]),\n }) as OverlayState\n\n const guards = new Map<string, () => boolean | Promise<boolean>>()\n let confirmRenderer: ConfirmRenderer | null = null\n\n const instances = (): OverlayInstance[] =>\n [state.leftPanel, state.rightPanel, ...state.modals].filter((i): i is OverlayInstance => i !== null)\n\n const getInstance = (id: string): OverlayInstance | undefined => instances().find(i => i.id === id)\n\n function make(\n kind: OverlayKind,\n component: Component,\n props: Record<string, unknown>,\n options: SidePanelOptions & ModalOptions,\n ): OverlayInstance {\n return { id: nextId(kind), component: markRaw(component), props, kind, options, dirty: false }\n }\n\n /** A drawer holds one panel, so opening asks the occupant's guard before evicting it. */\n async function openDrawer(\n slot: 'leftPanel' | 'rightPanel',\n kind: OverlayKind,\n component: Component,\n props: Record<string, unknown> = {},\n options: SidePanelOptions = {},\n ): Promise<string | null> {\n const current = state[slot]\n if (current) {\n const guard = guards.get(current.id)\n if (guard && !(await guard())) return null\n // The occupant's own dirty state is deliberately *not* consulted here: it is the\n // caller who is replacing the panel, and a guard is the documented way to object.\n guards.delete(current.id)\n }\n const instance = make(kind, component, props, options)\n state[slot] = instance\n return instance.id\n }\n\n function openModal(\n component: Component,\n props: Record<string, unknown> = {},\n options: ModalOptions = {},\n ): string {\n const instance = make('modal', component, props, options)\n state.modals.push(instance)\n return instance.id\n }\n\n function close(id: string): void {\n guards.delete(id)\n if (state.leftPanel?.id === id) { state.leftPanel = null; return }\n if (state.rightPanel?.id === id) { state.rightPanel = null; return }\n const index = state.modals.findIndex(m => m.id === id)\n if (index !== -1) state.modals.splice(index, 1)\n }\n\n function updateInstance(\n id: string,\n updates: Partial<Pick<OverlayInstance, 'props' | 'options' | 'dirty' | 'dirtyOptions'>>,\n ): void {\n const current = getInstance(id)\n if (!current) return\n const next: OverlayInstance = { ...current, ...updates }\n if (state.leftPanel?.id === id) { state.leftPanel = next; return }\n if (state.rightPanel?.id === id) { state.rightPanel = next; return }\n const index = state.modals.findIndex(m => m.id === id)\n if (index !== -1) state.modals.splice(index, 1, next)\n }\n\n async function requestClose(\n id: string,\n options?: { force?: boolean; confirm?: (instance: OverlayInstance) => Promise<boolean> },\n ): Promise<void> {\n if (options?.force) { close(id); return }\n\n const guard = guards.get(id)\n if (guard && !(await guard())) return\n\n const instance = getInstance(id)\n if (instance?.dirty) {\n if (!options?.confirm) return // no way to ask: refuse rather than discard\n if (!(await options.confirm(instance))) return\n }\n close(id)\n }\n\n return {\n state,\n openLeftPanel: (component, props, options) => openDrawer('leftPanel', 'left-panel', component, props, options),\n openRightPanel: (component, props, options) => openDrawer('rightPanel', 'right-panel', component, props, options),\n openModal,\n close,\n closeAll: () => {\n guards.clear()\n state.leftPanel = null\n state.rightPanel = null\n state.modals.splice(0)\n },\n closeAllModals: () => {\n for (const m of state.modals) guards.delete(m.id)\n state.modals.splice(0)\n },\n getInstance,\n updateInstance,\n setDirty: (id, dirty, options) => updateInstance(id, { dirty, dirtyOptions: options }),\n requestClose,\n registerCloseGuard: (id, guard) => {\n guards.set(id, guard)\n return () => { if (guards.get(id) === guard) guards.delete(id) }\n },\n topmostModal: () => state.modals[state.modals.length - 1] ?? null,\n confirmDiscard: (request) => confirmRenderer?.(request) ?? Promise.resolve(false),\n setConfirmRenderer: (renderer) => { confirmRenderer = renderer },\n dispose: () => { guards.clear(); confirmRenderer = null },\n }\n}\n","/**\n * Panel contributions: toolbar items and sidebar sections a panel publishes, surfaced only\n * while that panel is the active one.\n *\n * The rule that makes this feature safe is the one it shares with the rest of the library: a\n * contribution is read from `activePanelId`, and `activePanelId` never names a panel the user\n * cannot see. rdd's `activePanelId` *could* name an invisible panel (divergence D2), so the\n * shell's toolbar showed controls belonging to a panel behind another tab — controls that\n * looked functional and acted on something out of sight. That is the whole reason the D2 fix\n * came first: this feature is only as trustworthy as that invariant.\n *\n * Kept on the workspace rather than behind its own provider (ADR 0004), which also means the\n * merge helpers can be plain functions instead of hooks.\n */\nimport { computed, markRaw, ref, shallowReactive } from 'vue'\nimport type { Component, ComputedRef } from 'vue'\nimport type { SidebarTab } from './sidebarTypes'\nimport type { ToolbarItem } from './toolbarTypes'\n\n/** One named section a panel contributes to the app's sidebar while it is active. */\nexport interface PanelSidebarSection {\n id: string\n label: string\n icon?: Component\n /** Rendered as the section's content. */\n component: Component\n props?: Record<string, unknown>\n}\n\n/**\n * What a panel publishes. Both fields are optional and independent — a panel may contribute\n * only toolbar items, only sections, both, or nothing.\n *\n * The library assigns no meaning to either: what \"a toolbar item\" is for is the application's\n * decision, and neither `<VddToolbar>` nor `<VddSidebar>` reads this on its own. The shell\n * merges it into its own `items`/`tabs`.\n */\nexport interface PanelContribution {\n toolbarItems?: ToolbarItem[]\n sidebarSections?: PanelSidebarSection[]\n}\n\nexport interface Contributions {\n /**\n * Publish for one panel, replacing anything it published before. The returned function\n * withdraws it — but only if nothing has re-published for that id since, so a republish\n * during a component update cannot be undone by the previous registration's cleanup.\n */\n publish(panelId: string, contribution: PanelContribution): () => void\n /** What one panel has published, or `null`. */\n get(panelId: string): PanelContribution | null\n /** Every panel that has published something. */\n ids(): string[]\n}\n\nexport function createContributions(): Contributions {\n // `shallowReactive`: the values hold components and caller callbacks, and deep-proxying an\n // application's own objects is not the library's business. A contribution changes by being\n // replaced, which a shallow map tracks.\n const map = shallowReactive(new Map<string, PanelContribution>())\n const version = ref(0)\n\n return {\n publish(panelId, contribution) {\n const stored: PanelContribution = {\n toolbarItems: contribution.toolbarItems,\n sidebarSections: contribution.sidebarSections?.map(s => ({\n ...s,\n component: markRaw(s.component),\n icon: s.icon ? markRaw(s.icon) : undefined,\n })),\n }\n map.set(panelId, stored)\n version.value++\n return () => {\n if (map.get(panelId) === stored) {\n map.delete(panelId)\n version.value++\n }\n }\n },\n get(panelId) {\n void version.value // tracked, so a computed sees publishes and withdrawals\n return map.get(panelId) ?? null\n },\n ids() {\n void version.value\n return Array.from(map.keys())\n },\n }\n}\n\n// ── merge helpers ───────────────────────────────────────────────────────────\n\n/**\n * Turn a contributed section into a sidebar tab.\n *\n * `eagerMount` and `preserveState` are deliberately left unset: a contribution exists only\n * while its panel is mounted *and* active, so neither has anything to mean here.\n */\nexport function sectionToTab(section: PanelSidebarSection, fallbackIcon?: Component): SidebarTab {\n return {\n id: section.id,\n label: section.label,\n icon: section.icon ?? fallbackIcon,\n component: section.component,\n props: section.props,\n }\n}\n\n/**\n * Append the active panel's contributed items to a static list, behind a separator.\n *\n * A plain function of a `computed`, not a hook — the state is on the workspace, so there is\n * nothing to subscribe to. rdd needed `useMergedToolbarItems()` because reading the store\n * required `useSyncExternalStore`.\n */\nexport function mergeToolbarItems(\n staticItems: ToolbarItem[],\n contribution: PanelContribution | null,\n): ToolbarItem[] {\n const contributed = contribution?.toolbarItems\n if (!contributed?.length) return staticItems\n return [...staticItems, { type: 'separator' }, ...contributed]\n}\n\n/** Append the active panel's contributed sections to a static tab list. */\nexport function mergeSidebarTabs(\n staticTabs: SidebarTab[],\n contribution: PanelContribution | null,\n fallbackIcon?: Component,\n): SidebarTab[] {\n const sections = contribution?.sidebarSections\n if (!sections?.length) return staticTabs\n return [...staticTabs, ...sections.map(s => sectionToTab(s, fallbackIcon))]\n}\n\n/** @internal — used by the workspace to expose the active contribution as a ref. */\nexport function activeContributionRef(\n contributions: Contributions,\n activePanelId: () => string | null,\n): ComputedRef<PanelContribution | null> {\n return computed(() => {\n const id = activePanelId()\n return id ? contributions.get(id) : null\n })\n}\n\n","/**\n * The workspace: reactive state plus every action that mutates it.\n *\n * Created outside the component tree and live from that moment, so an application can drive\n * it from a service, a router guard or a plain module — no queueing, no \"connected\" state\n * (docs/decisions/0004-store-outside-components.md).\n */\nimport { computed, markRaw, reactive, readonly, ref, shallowRef, toRaw } from 'vue'\nimport type { App, Component, ComputedRef, InjectionKey } from 'vue'\nimport type {\n DirtyStateOptions, DropPosition, FloatAnchor, FloatingWindow, Label, LayoutNode,\n MessageDescriptor, MessageFormatter, PanelInfo, SerializedLayout, SplitDirection,\n} from '../types'\nimport {\n addPanelToLeaf, deriveActivePanelId, dockToEdge, emptyRoot, findFirstLeafId, findLeafForPanel,\n insertPanelInLeaf, isVisibleActiveTarget, leafExists, removeLeafFromTree, removePanelFromTree,\n selectPanelInTree, splitLeafInTree, updateSizesAtPath,\n} from './layoutTree'\nimport type { ActiveTargetScope } from './layoutTree'\nimport { LAYOUT_VERSION, parseInitialState } from './serialize'\nimport { isSerializable } from './serializable'\nimport { PanelRegistry } from './registry'\nimport type { PanelDefaultOptions } from './registry'\nimport { EventBus } from './eventBus'\nimport { menuPosition } from './contextMenu'\nimport type { ContextMenuItem, ShowContextMenuOptions } from './contextMenu'\nimport type { BuiltInEvents } from './eventBus'\nimport { defaultMessages } from './messages'\nimport { createToolbarState } from './toolbarState'\nimport type { ToolbarState } from './toolbarState'\nimport { createOverlays } from './overlays'\nimport type { Overlays } from './overlays'\nimport { activeContributionRef, createContributions } from './contributions'\nimport type { Contributions, PanelContribution } from './contributions'\n\n/** One entry in the panel catalogue passed to {@link createWorkspace}. */\nexport interface PanelDefinition {\n component: Component\n defaultOptions?: PanelDefaultOptions\n}\n\n/** Configuration for {@link createWorkspace}. */\nexport interface WorkspaceConfig {\n /** The panel catalogue: keys used by `openPanel` and in saved layouts. */\n panels?: Record<string, PanelDefinition>\n /** A layout from a previous `saveLayout()`. `null`/omitted starts from an empty workspace. */\n initialState?: string | null\n /** Reading direction. `'rtl'` mirrors drop zones, tab order and corner anchors. @default 'ltr' */\n dir?: 'ltr' | 'rtl'\n /** Resolves the library's own message descriptors. The whole i18n surface. */\n formatMessage?: MessageFormatter\n /** Override any subset of the built-in UI strings. */\n messages?: Partial<Record<keyof typeof defaultMessages, MessageDescriptor>>\n /** Fraction taken by a panel dropped on a leaf's edge. Clamped to 0.1–0.9. @default 0.5 */\n defaultSplitRatio?: number\n /** Fraction taken by a panel dropped on a workspace edge. Clamped to 0.1–0.9. @default 0.2 */\n defaultEdgeSplitRatio?: number\n /** Base z-index for floating windows and all library chrome, mirrored as `--vdd-z-base`. @default 1000 */\n zIndexBase?: number\n /**\n * Your own classes, added to the library's own chrome.\n *\n * For attaching a styling framework's utilities — Tailwind, MUI, Bootstrap — to elements\n * you otherwise cannot reach, since they are inside the library's markup. The library's\n * own `vdd-` classes stay, so this adds rather than replaces.\n *\n * rdd exposed the same thing through a `useStyleClasses()` hook whose only test was that\n * the hook returned the config; here the gate and a test assert the classes reach the\n * rendered elements, which is the part that can actually break.\n */\n classes?: HostClasses\n}\n\n/** Consumer classes added to the library's chrome. Every field is optional. */\nexport interface HostClasses {\n /** The modal's window box. */\n modal?: string\n /** The modal's scrolling body. */\n modalBody?: string\n /** A side panel's window box. */\n sidePanel?: string\n /** A side panel's scrolling body. */\n sidePanelBody?: string\n /** A floating window's outer box. */\n window?: string\n /** A floating window's body, which holds the panel. */\n windowBody?: string\n}\n\n/** Options for {@link Workspace.openPanel}. */\nexport interface OpenPanelOptions<P extends object = Record<string, unknown>> {\n /** Override the tab/window title. */\n title?: Label\n /** Where it goes. @default the registry's `initialTarget`, else `'docked'` */\n initialTarget?: 'floating' | 'docked' | 'tabbed'\n /** Corner to pin to, for a floating panel. */\n anchor?: FloatAnchor | null\n /** Make it the active panel. @default true */\n focus?: boolean\n /** Per-instance data handed to the component. Whether it survives `saveLayout()` is a\n * runtime fact — see {@link PanelInfo.serializable}. */\n props?: P\n /** If another open panel of the same `component` has this key, focus that one instead. */\n dedupeKey?: string\n}\n\n/** The live workspace state. */\nexport interface WorkspaceState {\n gridRoot: LayoutNode\n floating: FloatingWindow[]\n minimized: { id: string; title: Label; component: string }[]\n panels: Record<string, PanelInfo>\n /** The panel the user is looking at. Always visible, never minimised. */\n activePanelId: string | null\n /** The panel being dragged, or `null`. */\n draggedPanelId: string | null\n dir: 'ltr' | 'rtl'\n isRtl: boolean\n splitRatio: number\n edgeSplitRatio: number\n}\n\nconst clampRatio = (n: number | undefined, fallback: number): number =>\n Math.min(0.9, Math.max(0.1, n ?? fallback))\n\nconst DEFAULT_RECT = { x: 300, y: 150, width: 450, height: 350 }\n\n/** A workspace instance: reactive state, actions, registry, and a Vue plugin. */\nexport interface Workspace<TEvents extends Record<string, unknown> = Record<string, unknown>> {\n /** The whole state, read-only. Reactive — use it in templates and `computed`. */\n readonly state: Readonly<WorkspaceState>\n /** The panel catalogue. */\n readonly registry: PanelRegistry\n\n // Destructurable reactive views of the state.\n readonly gridRoot: ComputedRef<LayoutNode>\n readonly floating: ComputedRef<FloatingWindow[]>\n readonly minimized: ComputedRef<WorkspaceState['minimized']>\n readonly panels: ComputedRef<Record<string, PanelInfo>>\n readonly activePanelId: ComputedRef<string | null>\n readonly draggedPanelId: ComputedRef<string | null>\n readonly dir: ComputedRef<'ltr' | 'rtl'>\n readonly isRtl: ComputedRef<boolean>\n\n openPanel<P extends object = Record<string, unknown>>(id: string, component: string, options?: OpenPanelOptions<P>): void\n closePanel(id: string): void\n requestClosePanel(id: string, options?: { force?: boolean; onConfirm?: (opts?: DirtyStateOptions) => Promise<boolean> }): Promise<void>\n minimizePanel(id: string): void\n restorePanel(id: string, options?: { focus?: boolean }): void\n floatPanel(id: string, rect?: { x: number; y: number; width: number; height: number }, anchor?: FloatAnchor | null): void\n dockPanel(id: string, targetLeafId?: string): void\n dockPanelToGroup(id: string, targetLeafId: string, position: DropPosition): void\n dockPanelToWorkspaceEdge(id: string, position: SplitDirection): void\n /** Toggle a panel between filling the workspace and its previous size. A minimised panel\n * is restored first — rdd's equivalent silently did nothing (divergence D1). */\n maximizePanel(id: string): void\n focusPanel(id: string): void\n movePanelOrder(panelId: string, targetLeafId: string, targetIndex: number): void\n closeLeafGroup(leafId: string): void\n updateSplitSizes(path: number[], sizes: number[]): void\n updateFloatingPosition(id: string, updates: Partial<Pick<FloatingWindow, 'x' | 'y' | 'width' | 'height' | 'anchor'>>): void\n setPanelDirty(id: string, dirty: boolean, options?: DirtyStateOptions): void\n updatePanelTitle(id: string, title: Label): void\n setDirection(dir: 'ltr' | 'rtl'): void\n /** @internal drives drag visuals */\n setDraggedPanelId(id: string | null): void\n\n isOpen(id: string): boolean\n getOpenPanelIds(): string[]\n findPanelId(component: string, dedupeKey: string): string | null\n\n saveLayout(): string\n loadLayout(json: string): boolean\n\n publish<K extends keyof (TEvents & BuiltInEvents) & string>(event: K, data: (TEvents & BuiltInEvents)[K]): void\n subscribe<K extends keyof (TEvents & BuiltInEvents) & string>(event: K, cb: (data: (TEvents & BuiltInEvents)[K]) => void): () => void\n\n registerCloseGuard(id: string, guard: () => boolean | Promise<boolean>): () => void\n registerStateProvider(id: string, provider: () => unknown): () => void\n /**\n * Contribute extra items to a panel's own context menu. The getter is read each time the\n * menu opens, so state-driven changes (enabled, checked, present at all) just work.\n */\n registerPanelMenu(id: string, getItems: () => ContextMenuItem[]): () => void\n /** The items a panel has contributed, or an empty list. */\n panelMenuItems(id: string): ContextMenuItem[]\n\n /**\n * Open a context menu.\n *\n * Callable from anywhere, including outside the component tree — the request is state, and\n * `<VddContextMenu>` renders whatever is pending. rdd needed a provider, an adapter ref and\n * a registration handshake to make this reachable from components on either side of the\n * workspace in the tree.\n */\n showContextMenu(options: ShowContextMenuOptions): void\n /** Dismiss the open menu, if any. */\n closeContextMenu(): void\n /** The pending menu request, or `null`. @internal */\n readonly contextMenu: ComputedRef<{ x: number; y: number; items: ContextMenuItem[] } | null>\n\n /**\n * Toolbar selection state, for items that are not controlled by the caller.\n *\n * On the workspace rather than behind its own provider, so `useToolbar()` works anywhere —\n * including from a service. rdd needed a `<ToolbarProvider>` in the tree for this.\n */\n readonly toolbar: ToolbarState\n\n /**\n * Toolbar items and sidebar sections panels publish while they are active.\n *\n * Read through {@link Workspace.activeContribution}, which is driven by `activePanelId` —\n * and therefore by the invariant that `activePanelId` never names a panel the user cannot\n * see. rdd's could (divergence D2), which made the shell's toolbar show controls belonging\n * to a panel hidden behind another tab.\n */\n readonly contributions: Contributions\n /** What the active panel has published, or `null`. Reactive. */\n readonly activeContribution: ComputedRef<PanelContribution | null>\n\n /**\n * Side panels and the modal stack.\n *\n * Here for the same reason as `toolbar`: a modal is very often opened from something that\n * is not a component — a save handler, a router guard, an error interceptor. rdd needed a\n * `<PanelProvider>` in the tree, so those callers had to be handed an action object.\n */\n readonly overlays: Overlays\n\n /** Your own classes for the library's chrome, with every field present. */\n readonly classes: Required<HostClasses>\n\n /** Resolve a label through the configured formatter. */\n format(label: Label | undefined): string\n /** The merged message table. */\n readonly messages: Record<keyof typeof defaultMessages, MessageDescriptor>\n readonly config: Readonly<Required<Pick<WorkspaceConfig, 'zIndexBase'>> & WorkspaceConfig>\n\n /** Vue plugin entry point: `app.use(workspace)`. */\n install(app: App): void\n /** Drop every listener and guard. Only needed when workspaces are created dynamically. */\n dispose(): void\n}\n\n/** Injection key for {@link useWorkspace}. */\nexport const WORKSPACE_KEY = Symbol('vdd-workspace') as InjectionKey<Workspace<never>>\n\n/**\n * Create a workspace.\n *\n * The returned object is both the imperative API and a Vue plugin, matching the\n * `createPinia()` / `createRouter()` shape a Vue developer already knows.\n */\nexport function createWorkspace<TEvents extends Record<string, unknown> = Record<string, unknown>>(\n config: WorkspaceConfig = {},\n): Workspace<TEvents> {\n const registry = new PanelRegistry()\n for (const [id, def] of Object.entries(config.panels ?? {})) {\n registry.register(id, def.component, def.defaultOptions)\n }\n\n const bus = new EventBus<TEvents>()\n const closeGuards = new Map<string, () => boolean | Promise<boolean>>()\n const stateProviders = new Map<string, () => unknown>()\n const panelMenus = new Map<string, () => ContextMenuItem[]>()\n /**\n * Bumped whenever a panel registers or withdraws menu items.\n *\n * The map itself is deliberately plain — the *items* are pulled fresh on every open, so\n * nothing about them needs to be reactive. But a `computed` that asks \"does this panel\n * contribute anything?\" (the window's more-actions button does) has to have something to\n * track, or it caches the answer from before the panel ever registered.\n */\n const panelMenuVersion = ref(0)\n const menu = shallowRef<{ x: number; y: number; items: ContextMenuItem[] } | null>(null)\n const messages = { ...defaultMessages, ...(config.messages ?? {}) } as Record<keyof typeof defaultMessages, MessageDescriptor>\n const zIndexBase = config.zIndexBase ?? 1000\n let maxZ = zIndexBase\n\n const initial = parseInitialState(config.initialState, warn)\n\n const state = reactive<WorkspaceState>({\n gridRoot: initial.gridRoot,\n floating: initial.floating,\n minimized: initial.minimized,\n panels: initial.panels,\n activePanelId: initial.activePanelId,\n draggedPanelId: null,\n dir: config.dir ?? 'ltr',\n isRtl: config.dir === 'rtl',\n splitRatio: clampRatio(config.defaultSplitRatio, 0.5),\n edgeSplitRatio: clampRatio(config.defaultEdgeSplitRatio, 0.2),\n })\n\n // Keep maxZ above anything a restored layout already used.\n for (const w of state.floating) maxZ = Math.max(maxZ, w.z)\n\n function warn(message: string): void {\n if (process.env.NODE_ENV !== 'production') console.warn(`[vue-dockable-desktop] ${message}`)\n }\n\n const scope = (): ActiveTargetScope => ({\n gridRoot: toRaw(state).gridRoot,\n floating: toRaw(state).floating,\n panels: toRaw(state).panels,\n })\n\n /**\n * The single place `activePanelId` is decided. **Every** action that changes where a panel\n * lives must end by calling this.\n *\n * react-dockable-desktop resolved it action by action, and accumulated five places that\n * forgot to: a layout restore, a close, a minimise, an un-minimise, and every dock/float\n * (divergence D2). The result was a panel the user could not see being globally active,\n * so contributed toolbar controls acted on the wrong panel while looking functional.\n * Funnelling it through one function is what makes that bug unrepeatable rather than\n * merely fixed.\n *\n * @param prefer a panel that should become active if it is visible — the one just opened,\n * restored or docked. Omit to keep the current one if still valid.\n */\n function resolveActive(prefer?: string | null): void {\n const previous = state.activePanelId\n let next: string | null\n\n if (prefer && isVisibleActiveTarget(prefer, scope())) {\n next = prefer\n } else if (state.activePanelId && isVisibleActiveTarget(state.activePanelId, scope())) {\n next = state.activePanelId\n } else {\n next = deriveActivePanelId(scope())\n }\n\n if (next !== previous) {\n state.activePanelId = next\n bus.emit('panel:activated', { id: next, previous })\n }\n }\n\n const layoutChanged = () => bus.emit('layout:changed', {})\n\n const entryFor = (id: string) => {\n const panel = state.panels[id]\n return panel ? registry.get(panel.component) : undefined\n }\n const optionsFor = (id: string): PanelDefaultOptions => entryFor(id)?.defaultOptions ?? {}\n\n /** Free a spot for a new floating window, cascading away from anything at the same place. */\n function cascade(\n fav: { x: number | string; y: number | string; width: number | string; height: number | string },\n ): { x: number; y: number; width: number; height: number } {\n const num = (v: number | string, fallback: number) => {\n const n = typeof v === 'string' ? Number.parseFloat(v) : v\n return Number.isNaN(n) ? fallback : n\n }\n let x = num(fav.x, DEFAULT_RECT.x)\n let y = num(fav.y, DEFAULT_RECT.y)\n const width = num(fav.width, DEFAULT_RECT.width)\n const height = num(fav.height, DEFAULT_RECT.height)\n\n const overlaps = (px: number, py: number) => state.floating.some(w => {\n if (w.maximized) return false\n const wx = typeof w.x === 'string' ? Number.parseFloat(w.x) : w.x\n const wy = typeof w.y === 'string' ? Number.parseFloat(w.y) : w.y\n return Math.abs(wx - px) < 20 && Math.abs(wy - py) < 20\n })\n\n let attempts = 0\n while (overlaps(x, y) && attempts < 10) { x += 30; y += 30; attempts++ }\n\n // A workspace can be driven with no window at all (a service, a test), so guard both.\n const viewW = Math.max(100, (typeof window !== 'undefined' && window.innerWidth) || 1024)\n const viewH = Math.max(100, (typeof window !== 'undefined' && window.innerHeight) || 768)\n if (x + width > viewW || y + height > viewH) {\n x = 100 + (attempts % 5) * 30\n y = 100 + (attempts % 5) * 30\n }\n return {\n x: Math.max(0, Math.min(x, viewW - 100)), // keep the title bar reachable\n y: Math.max(0, Math.min(y, viewH - 40)),\n width,\n height,\n }\n }\n\n function addFloating(id: string, rect: ReturnType<typeof cascade>, anchor: FloatAnchor | null): void {\n maxZ += 1\n state.floating = [...state.floating, { ...rect, id, z: maxZ, anchor }]\n }\n\n /** Detach a panel from wherever it currently is, without deciding where it goes next. */\n function detach(id: string): void {\n state.gridRoot = removePanelFromTree(toRaw(state).gridRoot, id) ?? emptyRoot()\n state.floating = state.floating.filter(w => w.id !== id)\n state.minimized = state.minimized.filter(m => m.id !== id)\n }\n\n // ── Actions ────────────────────────────────────────────────────────────────\n\n function openPanel<P extends object = Record<string, unknown>>(\n id: string, component: string, options?: OpenPanelOptions<P>,\n ): void {\n if (!registry.has(component)) {\n warn(\n `Panel \"${id}\" references component key \"${component}\", which is not registered. ` +\n `Add it to createWorkspace({ panels: { \"${component}\": { component: YourComponent } } }).`,\n )\n }\n\n // Dedupe redirect first: the caller's id and props are ignored when an equivalent panel\n // is already open, exactly as re-opening an open id focuses it rather than duplicating.\n let resolvedId = id\n if (options?.dedupeKey !== undefined) {\n const match = Object.values(state.panels).find(\n p => p.component === component && p.dedupeKey === options.dedupeKey,\n )\n if (match) resolvedId = match.id\n }\n\n const existing = state.panels[resolvedId]\n const shouldFocus = options?.focus !== false\n const entry = registry.get(component)\n const target = options?.initialTarget ?? entry?.defaultOptions?.initialTarget ?? 'docked'\n const favourite = entry?.defaultOptions?.favoritePosition ?? DEFAULT_RECT\n\n if (existing) {\n if (existing.state === 'minimized') {\n // Honour where it came from, like `restorePanel` does. rdd sent it to the *first*\n // leaf instead, so the two paths disagreed (divergence D3).\n restorePanel(resolvedId, { focus: shouldFocus })\n return\n }\n if (shouldFocus) focusPanel(resolvedId)\n return\n }\n\n const propsProvided = options?.props !== undefined\n const info: PanelInfo = {\n id: resolvedId,\n title: options?.title ?? entry?.defaultOptions?.title ?? resolvedId,\n component,\n state: target === 'floating' ? 'floating' : 'docked',\n serializable: propsProvided ? isSerializable(toRaw(options!.props)) : true,\n ...(propsProvided ? { props: markRaw(options!.props as Record<string, unknown>) } : {}),\n ...(options?.dedupeKey !== undefined ? { dedupeKey: options.dedupeKey } : {}),\n }\n state.panels = { ...state.panels, [resolvedId]: info }\n\n if (target === 'floating') {\n addFloating(resolvedId, cascade(favourite), options?.anchor ?? entry?.defaultOptions?.defaultAnchor ?? null)\n } else {\n const leaf = findFirstLeafId(toRaw(state).gridRoot) ?? emptyRoot().id\n state.gridRoot = addPanelToLeaf(toRaw(state).gridRoot, leaf, resolvedId, { select: shouldFocus })\n }\n\n resolveActive(shouldFocus ? resolvedId : null)\n bus.emit('panel:opened', { id: resolvedId, component })\n layoutChanged()\n }\n\n function closePanel(id: string): void {\n const panel = state.panels[id]\n if (!panel) return\n if (optionsFor(id).canClose === false) return\n\n closeGuards.delete(id)\n stateProviders.delete(id)\n detach(id)\n const next = { ...state.panels }\n delete next[id]\n state.panels = next\n\n resolveActive()\n bus.emit('panel:closed', { id })\n layoutChanged()\n }\n\n async function requestClosePanel(\n id: string,\n options?: { force?: boolean; onConfirm?: (opts?: DirtyStateOptions) => Promise<boolean> },\n ): Promise<void> {\n if (options?.force) { closePanel(id); return }\n\n const guard = closeGuards.get(id)\n if (guard && !(await guard())) return\n\n const panel = state.panels[id]\n if (panel?.dirty) {\n // The built-in question is the default, so no call site has to know about it — a tab's\n // ×, a window's ×, a taskbar menu and `usePanel().close()` all get it for free. rdd\n // wired this separately into each container and never wired it into the docked path at\n // all, so closing a dirty tab discarded the edits without asking.\n const confirm = options?.onConfirm\n ?? ((dirtyOptions?: DirtyStateOptions) =>\n overlays.confirmDiscard({ title: format(panel.title), dirtyOptions }))\n if (!(await confirm(panel.dirtyOptions))) return\n }\n closePanel(id)\n }\n\n function minimizePanel(id: string): void {\n const panel = state.panels[id]\n if (!panel || panel.state === 'minimized') return\n if (optionsFor(id).canMinimize === false) return\n\n let lastFloatingRect: PanelInfo['lastFloatingRect']\n let lastLeafId: string | undefined\n\n if (panel.state === 'floating') {\n const win = state.floating.find(w => w.id === id)\n if (win) {\n lastFloatingRect = {\n x: Number(win.x), y: Number(win.y),\n width: Number(win.width), height: Number(win.height),\n anchor: win.anchor ?? null,\n }\n }\n } else {\n lastLeafId = findLeafForPanel(toRaw(state).gridRoot, id) ?? undefined\n }\n\n detach(id)\n state.panels = {\n ...state.panels,\n [id]: { ...panel, state: 'minimized', previousState: panel.state, lastFloatingRect, lastLeafId },\n }\n state.minimized = [...state.minimized, { id, title: panel.title, component: panel.component }]\n\n resolveActive()\n bus.emit('panel:minimized', { id })\n layoutChanged()\n }\n\n function restorePanel(id: string, options?: { focus?: boolean }): void {\n const panel = state.panels[id]\n if (!panel || panel.state !== 'minimized') return\n const shouldFocus = options?.focus !== false\n\n state.minimized = state.minimized.filter(m => m.id !== id)\n const previous = panel.previousState ?? 'docked'\n const entry = registry.get(panel.component)\n const favourite = panel.lastFloatingRect ?? entry?.defaultOptions?.favoritePosition ?? DEFAULT_RECT\n\n const toFloating = () => {\n state.panels = { ...state.panels, [id]: { ...panel, state: 'floating' } }\n addFloating(id, cascade(favourite), panel.lastFloatingRect?.anchor ?? null)\n }\n const toLeaf = (leafId: string) => {\n state.panels = { ...state.panels, [id]: { ...panel, state: 'docked' } }\n state.gridRoot = addPanelToLeaf(toRaw(state).gridRoot, leafId, id, { select: shouldFocus })\n }\n\n if (previous === 'floating') {\n toFloating()\n } else if (panel.lastLeafId && leafExists(toRaw(state).gridRoot, panel.lastLeafId)) {\n toLeaf(panel.lastLeafId)\n } else if (optionsFor(id).canDrag !== false) {\n toFloating() // its leaf is gone; float it if it may float\n } else {\n toLeaf(findFirstLeafId(toRaw(state).gridRoot) ?? emptyRoot().id)\n }\n\n resolveActive(shouldFocus ? id : null)\n bus.emit('panel:restored', { id })\n layoutChanged()\n }\n\n function floatPanel(\n id: string,\n rect?: { x: number; y: number; width: number; height: number },\n anchor?: FloatAnchor | null,\n ): void {\n const panel = state.panels[id]\n if (!panel) return\n if (optionsFor(id).canDrag === false) return\n\n const favourite = rect ?? registry.get(panel.component)?.defaultOptions?.favoritePosition ?? DEFAULT_RECT\n detach(id)\n state.panels = { ...state.panels, [id]: { ...panel, state: 'floating' } }\n addFloating(id, cascade(favourite), anchor ?? null)\n\n resolveActive(id)\n layoutChanged()\n }\n\n function dockPanel(id: string, targetLeafId?: string): void {\n const panel = state.panels[id]\n if (!panel) return\n detach(id)\n // `lastLeafId` can name a leaf that has since been removed — emptying a leaf deletes it.\n // Docking into a leaf that does not exist silently puts the panel nowhere: it stays in\n // `panels`, so it is \"open\", but no slot ever shows it. Caught by the M4 browser gate.\n const remembered = panel.lastLeafId && leafExists(toRaw(state).gridRoot, panel.lastLeafId)\n ? panel.lastLeafId\n : undefined\n const leafId = targetLeafId ?? remembered ?? findFirstLeafId(toRaw(state).gridRoot) ?? emptyRoot().id\n state.panels = { ...state.panels, [id]: { ...panel, state: 'docked' } }\n state.gridRoot = addPanelToLeaf(toRaw(state).gridRoot, leafId, id)\n\n resolveActive(id)\n layoutChanged()\n }\n\n function dockPanelToGroup(id: string, targetLeafId: string, position: DropPosition): void {\n const panel = state.panels[id]\n if (!panel) return\n detach(id)\n state.panels = { ...state.panels, [id]: { ...panel, state: 'docked' } }\n state.gridRoot = position === 'center'\n ? addPanelToLeaf(toRaw(state).gridRoot, targetLeafId, id)\n : splitLeafInTree(toRaw(state).gridRoot, targetLeafId, id, position, state.splitRatio)\n state.draggedPanelId = null\n\n resolveActive(id)\n layoutChanged()\n }\n\n function dockPanelToWorkspaceEdge(id: string, position: SplitDirection): void {\n const panel = state.panels[id]\n if (!panel) return\n detach(id)\n state.panels = { ...state.panels, [id]: { ...panel, state: 'docked' } }\n state.gridRoot = dockToEdge(toRaw(state).gridRoot, id, position, state.edgeSplitRatio)\n state.draggedPanelId = null\n\n resolveActive(id)\n layoutChanged()\n }\n\n function movePanelOrder(panelId: string, targetLeafId: string, targetIndex: number): void {\n const panel = state.panels[panelId]\n if (!panel) return\n const cleaned = removePanelFromTree(toRaw(state).gridRoot, panelId) ?? emptyRoot()\n state.floating = state.floating.filter(w => w.id !== panelId)\n state.minimized = state.minimized.filter(m => m.id !== panelId)\n state.panels = { ...state.panels, [panelId]: { ...panel, state: 'docked' } }\n state.gridRoot = insertPanelInLeaf(cleaned, targetLeafId, panelId, targetIndex)\n state.draggedPanelId = null\n\n resolveActive(panelId)\n layoutChanged()\n }\n\n function maximizePanel(id: string): void {\n // A minimised panel is not in `floating`, so rdd's version silently did nothing here —\n // its taskbar context menu offered a \"Maximize\" item that never worked (divergence D1).\n if (state.panels[id]?.state === 'minimized') restorePanel(id)\n\n const panel = state.panels[id]\n if (!panel) return\n if (panel.state === 'docked') floatPanel(id)\n\n state.floating = state.floating.map(w => (w.id === id ? { ...w, maximized: !w.maximized } : w))\n resolveActive(id)\n layoutChanged()\n }\n\n function focusPanel(id: string): void {\n const panel = state.panels[id]\n if (!panel) return\n\n if (panel.state === 'floating') {\n const win = state.floating.find(w => w.id === id)\n if (win) {\n const alreadyTop = !state.floating.some(w => w.z > win.z)\n if (!alreadyTop) {\n maxZ += 1\n const z = maxZ\n state.floating = state.floating.map(w => (w.id === id ? { ...w, z } : w))\n }\n }\n } else if (panel.state === 'docked') {\n state.gridRoot = selectPanelInTree(toRaw(state).gridRoot, id)\n }\n\n // An explicit focus on a minimised panel is honoured as a deliberate caller decision,\n // matching rdd. `resolveActive` would refuse it, so set it directly.\n if (panel.state === 'minimized') {\n const previous = state.activePanelId\n if (previous !== id) {\n state.activePanelId = id\n bus.emit('panel:activated', { id, previous })\n }\n return\n }\n resolveActive(id)\n }\n\n function closeLeafGroup(leafId: string): void {\n state.gridRoot = removeLeafFromTree(toRaw(state).gridRoot, leafId) ?? emptyRoot()\n resolveActive()\n layoutChanged()\n }\n\n function updateSplitSizes(path: number[], sizes: number[]): void {\n state.gridRoot = updateSizesAtPath(toRaw(state).gridRoot, path, sizes)\n layoutChanged()\n }\n\n function updateFloatingPosition(\n id: string,\n updates: Partial<Pick<FloatingWindow, 'x' | 'y' | 'width' | 'height' | 'anchor'>>,\n ): void {\n state.floating = state.floating.map(w => (w.id === id ? { ...w, ...updates } : w))\n }\n\n function setPanelDirty(id: string, dirty: boolean, options?: DirtyStateOptions): void {\n const panel = state.panels[id]\n if (!panel) return\n state.panels = { ...state.panels, [id]: { ...panel, dirty, dirtyOptions: options } }\n }\n\n function updatePanelTitle(id: string, title: Label): void {\n const panel = state.panels[id]\n if (!panel) return\n state.panels = { ...state.panels, [id]: { ...panel, title } }\n const idx = state.minimized.findIndex(m => m.id === id)\n if (idx !== -1) {\n state.minimized = state.minimized.map(m => (m.id === id ? { ...m, title } : m))\n }\n }\n\n function setDirection(dir: 'ltr' | 'rtl'): void {\n if (state.dir === dir) return\n state.dir = dir\n state.isRtl = dir === 'rtl'\n }\n\n // ── Persistence ────────────────────────────────────────────────────────────\n\n function saveLayout(): string {\n const raw = toRaw(state)\n const excluded: string[] = []\n const included: Record<string, PanelInfo> = {}\n\n // A registered provider is pulled fresh on every save: a panel's serialisability can\n // change over its lifetime, so it is never cached from open time for provider-backed panels.\n for (const [id, info] of Object.entries(raw.panels)) {\n const provider = stateProviders.get(id)\n const dynamic = provider?.()\n const hasDynamic = provider !== undefined && dynamic !== undefined\n const props = hasDynamic ? (dynamic as Record<string, unknown>) : info.props\n const ok = hasDynamic ? isSerializable(toRaw(dynamic)) : info.serializable\n if (ok) included[id] = hasDynamic ? { ...info, props, serializable: true } : info\n else excluded.push(id)\n }\n\n // Excluded panels are pruned from the snapshot's tree/floating/minimized too, so a\n // restore never references a panel it has no data to recreate. None of this touches the\n // live state: an excluded panel keeps working on screen.\n let gridRoot: LayoutNode = raw.gridRoot\n let floating = raw.floating\n let minimized = raw.minimized\n for (const id of excluded) {\n gridRoot = removePanelFromTree(gridRoot, id) ?? emptyRoot()\n floating = floating.filter(w => w.id !== id)\n minimized = minimized.filter(m => m.id !== id)\n }\n\n if (excluded.length) {\n bus.emit('layout:panels-excluded', {\n panels: excluded.map(id => ({ id, component: raw.panels[id]!.component })),\n })\n }\n\n // Validated against the *pruned* snapshot: if the active panel was itself excluded, the\n // field is omitted rather than naming a panel this payload does not contain.\n const live = raw.activePanelId\n const activePanelId = live !== null && isVisibleActiveTarget(live, { gridRoot, floating, panels: included })\n ? live\n : null\n\n const payload: SerializedLayout = {\n version: LAYOUT_VERSION,\n ...(activePanelId !== null ? { activePanelId } : {}),\n gridRoot,\n floating,\n minimized,\n panels: included,\n }\n return JSON.stringify(payload)\n }\n\n function loadLayout(json: string): boolean {\n let parsed: ReturnType<typeof parseInitialState>\n try {\n const payload = JSON.parse(json)\n const result = parseInitialState(JSON.stringify(payload), warn)\n if (!payload || typeof payload !== 'object' || !payload.gridRoot) return false\n parsed = result\n } catch {\n warn('loadLayout received invalid JSON; the current layout is unchanged.')\n return false\n }\n state.gridRoot = parsed.gridRoot\n state.floating = parsed.floating\n state.minimized = parsed.minimized\n state.panels = parsed.panels\n state.draggedPanelId = null\n maxZ = Math.max(zIndexBase, ...parsed.floating.map(w => w.z), 0)\n\n const previous = state.activePanelId\n state.activePanelId = parsed.activePanelId\n if (previous !== parsed.activePanelId) {\n bus.emit('panel:activated', { id: parsed.activePanelId, previous })\n }\n layoutChanged()\n return true\n }\n\n // ── Registrations ──────────────────────────────────────────────────────────\n\n function registerCloseGuard(id: string, guard: () => boolean | Promise<boolean>): () => void {\n closeGuards.set(id, guard)\n return () => { if (closeGuards.get(id) === guard) closeGuards.delete(id) }\n }\n\n function registerStateProvider(id: string, provider: () => unknown): () => void {\n stateProviders.set(id, provider)\n return () => { if (stateProviders.get(id) === provider) stateProviders.delete(id) }\n }\n\n function registerPanelMenu(id: string, getItems: () => ContextMenuItem[]): () => void {\n panelMenus.set(id, getItems)\n panelMenuVersion.value++\n return () => {\n if (panelMenus.get(id) === getItems) {\n panelMenus.delete(id)\n panelMenuVersion.value++\n }\n }\n }\n\n const format = (label: Label | undefined): string => {\n if (label === undefined || label === null) return ''\n if (typeof label === 'string') return label\n if (config.formatMessage) return config.formatMessage(label)\n let text = label.defaultMessage ?? label.id\n for (const [k, v] of Object.entries(label.values ?? {})) text = text.replace(`{${k}}`, String(v))\n return text\n }\n\n const overlays = createOverlays()\n const contributions = createContributions()\n\n const workspace: Workspace<TEvents> = {\n state: readonly(state) as Readonly<WorkspaceState>,\n registry,\n gridRoot: computed(() => state.gridRoot),\n floating: computed(() => state.floating),\n minimized: computed(() => state.minimized),\n panels: computed(() => state.panels),\n activePanelId: computed(() => state.activePanelId),\n draggedPanelId: computed(() => state.draggedPanelId),\n dir: computed(() => state.dir),\n isRtl: computed(() => state.isRtl),\n\n openPanel, closePanel, requestClosePanel, minimizePanel, restorePanel, floatPanel,\n dockPanel, dockPanelToGroup, dockPanelToWorkspaceEdge, maximizePanel, focusPanel,\n movePanelOrder, closeLeafGroup, updateSplitSizes, updateFloatingPosition,\n setPanelDirty, updatePanelTitle, setDirection,\n setDraggedPanelId: (id) => { state.draggedPanelId = id },\n\n isOpen: (id) => id in state.panels,\n getOpenPanelIds: () => Object.keys(state.panels),\n findPanelId: (component, dedupeKey) =>\n Object.values(state.panels).find(p => p.component === component && p.dedupeKey === dedupeKey)?.id ?? null,\n\n saveLayout, loadLayout,\n publish: (event, data) => bus.publish(event, data),\n subscribe: (event, cb) => bus.subscribe(event, cb),\n toolbar: createToolbarState(),\n overlays,\n contributions,\n activeContribution: activeContributionRef(contributions, () => state.activePanelId),\n registerCloseGuard, registerStateProvider, registerPanelMenu,\n panelMenuItems: (id) => {\n void panelMenuVersion.value // tracked, so a computed sees registrations\n return panelMenus.get(id)?.() ?? []\n },\n showContextMenu: (options) => {\n options.event?.preventDefault?.()\n menu.value = { ...menuPosition(options), items: options.items }\n },\n closeContextMenu: () => { menu.value = null },\n contextMenu: computed(() => menu.value),\n // Normalised to all-present-and-empty, so a template can bind every one without a guard.\n classes: {\n modal: config.classes?.modal ?? '',\n modalBody: config.classes?.modalBody ?? '',\n sidePanel: config.classes?.sidePanel ?? '',\n sidePanelBody: config.classes?.sidePanelBody ?? '',\n window: config.classes?.window ?? '',\n windowBody: config.classes?.windowBody ?? '',\n },\n format,\n messages,\n config: { ...config, zIndexBase },\n\n install(app: App) {\n app.provide(WORKSPACE_KEY, workspace as unknown as Workspace<never>)\n },\n dispose() {\n bus.clear()\n overlays.dispose()\n closeGuards.clear()\n stateProviders.clear()\n panelMenus.clear()\n menu.value = null\n },\n }\n\n return workspace\n}\n","import type { FloatAnchor } from '../types'\n\n/**\n * Mirrors a physical workspace corner to its horizontal opposite.\n *\n * Used only to translate a physically-hovered corner (raw pointer/screen\n * position, which CSS cannot reason about) into the logical `FloatAnchor`\n * value stored on a `FloatingWindow` under RTL. Render-time positioning\n * should use CSS logical properties (`insetInlineStart`/`insetInlineEnd`)\n * driven by the element's `dir` attribute instead of calling this.\n */\nexport function flipZoneHorizontal(zone: FloatAnchor): FloatAnchor {\n if (zone === 'top-left') return 'top-right'\n if (zone === 'top-right') return 'top-left'\n if (zone === 'bottom-left') return 'bottom-right'\n return 'bottom-left'\n}\n\n// ── Keeping a floating window reachable ─────────────────────────────────────\n\n/** A floating window's stored box, as numbers. */\nexport interface FloatingRect {\n x: number\n y: number\n width: number\n height: number\n}\n\n/** Smallest a clamp will shrink a window to, so it never becomes ungrabbable. */\nexport const MIN_CLAMP_W = 200\nexport const MIN_CLAMP_H = 150\n\n/**\n * Where a floating window has to move to stay reachable inside a shrinking workspace.\n *\n * Returns the clamped rect, or the **same object** when nothing needed to change. That\n * identity is the point: the caller watches the window list deeply and writes back to it, so\n * writing on \"a bound was exceeded\" rather than on \"a value changed\" re-triggers the watcher\n * forever. Both clamps have a floor, so in a workspace narrower than that floor the clamped\n * value still fails its own condition — and the rewrite loop that produced was found by\n * M13's browser tour as \"Maximum recursive updates exceeded\".\n *\n * Size is clamped for every window. Position is clamped only for a free-floating one: an\n * anchored window is placed entirely by its anchor and reading direction, so its stored x/y\n * do not affect where it appears.\n */\nexport function clampFloatingRect(\n rect: FloatingRect,\n view: { width: number; height: number },\n anchored: boolean,\n): FloatingRect {\n let { x, y, width, height } = rect\n\n if (width > view.width) width = Math.max(MIN_CLAMP_W, view.width - 20)\n if (height > view.height) height = Math.max(MIN_CLAMP_H, view.height - 40)\n\n if (!anchored) {\n const maxX = view.width - 100 // keep enough title bar to grab\n const maxY = view.height - 40\n if (x > maxX) x = Math.max(0, maxX)\n if (y > maxY) y = Math.max(0, maxY)\n }\n\n const unchanged = x === rect.x && y === rect.y && width === rect.width && height === rect.height\n return unchanged ? rect : { x, y, width, height }\n}\n","/**\n * The persistence port: the mechanism that makes a panel survive being moved.\n *\n * Each panel gets **one** element for its whole lifetime. Panel content is teleported into\n * that element once; moving the panel between the grid, a floating window, a hover preview\n * and the off-screen store moves *the element*, which Vue never observes. That is what keeps\n * a WebGL context, a Monaco model, a playing video and an open socket alive across every\n * layout change.\n *\n * Proven in the M0 spike, including the choice of this strategy over teleporting straight to\n * the host — see docs/decisions/0002-zero-unmount-via-teleport.md. One instance per\n * `<VddDesktop>`, never a module-level singleton: rdd's module-level `domCache` meant two\n * workspaces on a page shared panel DOM.\n */\n\n/** Scroll offsets and focus captured from a panel's subtree. */\ninterface Preserved {\n scrolls: { el: Element; top: number; left: number }[]\n focus: { el: HTMLElement; start: number | null; end: number | null } | null\n}\n\n/** Does this element currently participate in layout? */\nfunction isLaidOut(el: HTMLElement): boolean {\n return el.offsetParent !== null || el.getClientRects().length > 0\n}\n\nfunction scrollables(root: Element): Element[] {\n const out: Element[] = []\n const walk = (el: Element) => {\n if (el.scrollHeight > el.clientHeight || el.scrollWidth > el.clientWidth) out.push(el)\n for (const child of Array.from(el.children)) walk(child)\n }\n walk(root)\n return out\n}\n\nexport class PanelDomCache {\n private elements = new Map<string, HTMLDivElement>()\n /**\n * Preserved state, **owned per panel and outliving the hidden period**.\n *\n * Capturing at each move is not enough: while a panel sits in the hidden store it has no\n * layout, so every scroller reports `scrollHeight === clientHeight === 0` and there is\n * nothing to read. So the record is only refreshed while the panel is laid out, and always\n * applied when it becomes laid out again — which is what makes minimise → restore work.\n * M0 verified this failing, then fixed.\n */\n private preserved = new Map<string, Preserved>()\n /**\n * The last size each panel was laid out at.\n *\n * Kept here because it outlives any one host: the taskbar's hover preview needs to know how\n * big a panel *was* in order to scale a thumbnail of it, and by then the panel is minimised\n * and has no size of its own.\n */\n private sizes = new Map<string, { width: number; height: number }>()\n private hidden: HTMLElement | null = null\n\n constructor(private readonly doc: Document = document) {}\n\n /** The off-screen store. Panels live here while minimised, and before their first host. */\n hiddenStore(): HTMLElement {\n if (!this.hidden || !this.hidden.isConnected) {\n const el = this.doc.createElement('div')\n el.className = 'vdd-panel-store'\n el.style.display = 'none'\n this.doc.body.appendChild(el)\n this.hidden = el\n }\n return this.hidden\n }\n\n /**\n * This panel's element, created on first ask.\n *\n * Created **already in the document**, inside the hidden store: a `<Teleport>` target must\n * exist when the teleport mounts, or Vue warns and renders nothing.\n */\n elementFor(id: string): HTMLDivElement {\n let el = this.elements.get(id)\n if (!el) {\n el = this.doc.createElement('div')\n el.className = 'vdd-panel-mount'\n el.dataset.vddPanel = id\n this.hiddenStore().appendChild(el)\n this.elements.set(id, el)\n }\n return el\n }\n\n /** Whether this panel has an element yet. */\n has(id: string): boolean {\n return this.elements.has(id)\n }\n\n /** Record the size a panel was last laid out at. */\n reportSize(id: string, size: { width: number; height: number }): void {\n this.sizes.set(id, size)\n }\n\n /** The size a panel was last laid out at, or a sensible default for a thumbnail. */\n sizeOf(id: string): { width: number; height: number } {\n return this.sizes.get(id) ?? { width: 800, height: 500 }\n }\n\n /** The host this panel's element is currently inside. */\n hostOf(id: string): HTMLElement | null {\n return this.elements.get(id)?.parentElement ?? null\n }\n\n /**\n * Move a panel's element into `host` (or the hidden store when `null`), preserving scroll\n * and focus around the move.\n *\n * @param refocus restore focus as well. Only true when the panel is becoming the active\n * one — restoring focus into a background panel would steal the caret from wherever the\n * user is actually typing.\n */\n moveTo(id: string, host: HTMLElement | null, options?: { refocus?: boolean; preserveScroll?: boolean }): void {\n const el = this.elementFor(id)\n const target = host ?? this.hiddenStore()\n if (el.parentElement === target) return\n\n const preserve = options?.preserveScroll !== false\n if (preserve) this.remember(id, el)\n target.appendChild(el)\n if (preserve) this.apply(id, el, options?.refocus === true)\n }\n\n /** Forget a panel entirely. Called when the panel closes. */\n release(id: string): void {\n this.elements.get(id)?.remove()\n this.elements.delete(id)\n this.preserved.delete(id)\n this.sizes.delete(id)\n }\n\n /** Drop every element and the hidden store. */\n dispose(): void {\n for (const el of this.elements.values()) el.remove()\n this.elements.clear()\n this.preserved.clear()\n this.sizes.clear()\n this.hidden?.remove()\n this.hidden = null\n }\n\n // ── ADR 0014 ───────────────────────────────────────────────────────────────\n\n private remember(id: string, el: HTMLElement): void {\n if (!isLaidOut(el)) return // nothing meaningful to read; keep what we have\n const scrolls = scrollables(el).map(s => ({ el: s, top: s.scrollTop, left: s.scrollLeft }))\n let focus: Preserved['focus'] = null\n const active = this.doc.activeElement as HTMLElement | null\n if (active && el.contains(active)) {\n const field = active as HTMLInputElement\n const hasSelection = 'selectionStart' in field && field.selectionStart !== null\n focus = {\n el: active,\n start: hasSelection ? field.selectionStart : null,\n end: hasSelection ? field.selectionEnd : null,\n }\n }\n this.preserved.set(id, { scrolls, focus })\n }\n\n private apply(id: string, el: HTMLElement, refocus: boolean): void {\n const record = this.preserved.get(id)\n if (!record) return\n const run = () => {\n for (const s of record.scrolls) { s.el.scrollTop = s.top; s.el.scrollLeft = s.left }\n if (refocus && record.focus) {\n record.focus.el.focus({ preventScroll: true })\n if (record.focus.start !== null) {\n try {\n (record.focus.el as HTMLInputElement).setSelectionRange(record.focus.start, record.focus.end ?? record.focus.start)\n } catch { /* the element does not support selection */ }\n }\n }\n }\n if (isLaidOut(el)) run()\n // Again next frame: on the way out of the hidden store the subtree has not been laid out\n // yet, so the synchronous attempt cannot stick.\n if (typeof requestAnimationFrame === 'function') requestAnimationFrame(() => { if (isLaidOut(el)) run() })\n }\n}\n","import { inject, provide } from 'vue'\nimport type { InjectionKey } from 'vue'\nimport type { PanelDomCache } from '../core/panelDom'\n\nexport const PANEL_DOM_KEY = Symbol('vdd-panel-dom') as InjectionKey<PanelDomCache>\n\n/** @internal — `<VddDesktop>` owns the cache and shares it with the slots inside it. */\nexport function providePanelDom(cache: PanelDomCache): void {\n provide(PANEL_DOM_KEY, cache)\n}\n\n/** @internal */\nexport function usePanelDom(): PanelDomCache | null {\n return inject(PANEL_DOM_KEY, null)\n}\n","/**\n * The drag-and-dock state machine.\n *\n * One source of truth for a gesture in progress: what is being dragged, where the pointer\n * is, and which drop target is currently armed. Provided by `<VddDesktop>` and consumed by\n * the tab bars, the drop-zone overlays and the ghost.\n *\n * Two input paths, deliberately different:\n *\n * - **mouse and pen** — listeners on `window`, with **no** `setPointerCapture`. Capture\n * would suppress `pointerenter`/`pointerleave` on the drop zones, which is how hover is\n * tracked. A 5px threshold distinguishes a drag from a click.\n * - **touch** — a 300ms long press *with* capture, because a touch drag must not be\n * confused with a scroll. Moving more than 8px before the press completes cancels it.\n * Since capture suppresses hover events, the armed target is resolved by hit-testing the\n * pointer position instead.\n *\n * Drop resolution order is fixed: workspace edge, then a tab (a precise intent), then a\n * leaf's cross target, then a corner anchor, then a free float. It matters — a leaf's cross\n * commonly overlaps the edge zones beneath it, and the more specific target must win.\n */\nimport { computed, inject, provide, ref, shallowRef } from 'vue'\nimport type { ComputedRef, InjectionKey, Ref } from 'vue'\nimport type { DropPosition, FloatAnchor, SplitDirection } from '../types'\nimport { flipZoneHorizontal } from '../core/anchorGeometry'\nimport { findLeaf } from '../core/layoutTree'\nimport type { Workspace } from '../core/workspace'\n\n/** How far a mouse must move before a press becomes a drag. */\nconst DRAG_THRESHOLD_PX = 5\n/** How long a touch must rest before it becomes a drag. */\nexport const LONG_PRESS_MS = 300\n/** How far a touch may wander before the long press is abandoned. */\nexport const CANCEL_MOVE_PX = 8\n\n/** Which tab a dragged panel would be inserted next to. */\nexport interface TabTarget {\n leafId: string\n panelId: string\n index: number\n side: 'left' | 'right'\n}\n\nexport interface DragDock {\n /** The panel being dragged, or `null`. */\n draggedId: Ref<string | null>\n /** Pointer position, for the ghost. */\n pointer: Ref<{ x: number; y: number }>\n /** The armed leaf cross target. */\n zone: Ref<{ leafId: string; position: DropPosition } | null>\n /** The armed workspace edge. */\n edge: Ref<SplitDirection | null>\n /** The armed corner anchor. */\n corner: Ref<FloatAnchor | null>\n /** The armed tab insertion point. */\n tab: Ref<TabTarget | null>\n /** True while a gesture is in progress. */\n dragging: ComputedRef<boolean>\n\n startTabDrag: (panelId: string, event: PointerEvent, onLongPressWithoutDrag?: (e: PointerEvent) => void) => void\n /** Begin a gesture that another component drives (a floating window's title bar). */\n beginDrag: (panelId: string) => void\n /**\n * Update the pointer and re-resolve the armed target by hit-testing.\n *\n * Needed whenever the gesture holds pointer capture — a captured pointer routes every\n * event to the capturing element, so the drop zones never receive `pointerenter`. The\n * dragged window sets `pointer-events: none` on itself, so the zones beneath it are what\n * the hit test finds.\n */\n trackPointer: (x: number, y: number) => void\n hoverZone: (leafId: string, position: DropPosition | null) => void\n hoverEdge: (edge: SplitDirection | null) => void\n hoverCorner: (corner: FloatAnchor | null) => void\n hoverTab: (target: TabTarget | null) => void\n /** Finish a gesture started elsewhere (a floating window's title bar). */\n finishDrag: (panelId: string, event: PointerEvent) => void\n cancel: () => void\n}\n\nconst DRAG_DOCK_KEY = Symbol('vdd-drag-dock') as InjectionKey<DragDock>\n\n/** @internal */\nexport function provideDragDock(ws: Workspace<never>): DragDock {\n const draggedId = ref<string | null>(null)\n const pointer = ref({ x: 0, y: 0 })\n const zone = ref<{ leafId: string; position: DropPosition } | null>(null)\n const edge = ref<SplitDirection | null>(null)\n const corner = ref<FloatAnchor | null>(null)\n const tab = shallowRef<TabTarget | null>(null)\n\n const dragging = computed(() => draggedId.value !== null)\n\n /** Mirror a physical drop side to a logical one under RTL. */\n const flip = (position: DropPosition): DropPosition => {\n if (!ws.state.isRtl) return position\n if (position === 'left') return 'right'\n if (position === 'right') return 'left'\n return position\n }\n\n function reset(): void {\n draggedId.value = null\n zone.value = null\n edge.value = null\n corner.value = null\n tab.value = null\n ws.setDraggedPanelId(null)\n document.body.classList.remove('vdd-dragging-active')\n }\n\n function begin(panelId: string): void {\n draggedId.value = panelId\n ws.setDraggedPanelId(panelId)\n document.body.classList.add('vdd-dragging-active')\n }\n\n function hoverZone(leafId: string, position: DropPosition | null): void {\n zone.value = position ? { leafId, position } : null\n // A leaf's cross overlaps the edge and corner zones beneath it; the more specific target\n // wins, so arming one disarms the coarser ones.\n if (position) { edge.value = null; corner.value = null }\n }\n const hoverEdge = (value: SplitDirection | null) => { edge.value = value }\n const hoverCorner = (value: FloatAnchor | null) => {\n corner.value = value\n if (value) edge.value = null\n }\n const hoverTab = (value: TabTarget | null) => { tab.value = value }\n\n /**\n * Resolve the armed target by hit-testing, for touch.\n *\n * `setPointerCapture` routes every event to the captured element, so the drop zones never\n * see `pointerenter`. Reading the elements under the pointer is the only way to know what\n * is being hovered. Tabs are checked before edges: a precise intent beats a coarse zone.\n */\n function resolveFromPoint(x: number, y: number): void {\n const elements = document.elementsFromPoint(x, y)\n let foundZone = false\n let foundEdge = false\n let foundTab = false\n\n for (const el of elements) {\n if (!(el instanceof HTMLElement)) continue\n\n if (!foundZone && el.dataset.vddDropZone && el.dataset.vddLeaf) {\n zone.value = { leafId: el.dataset.vddLeaf, position: el.dataset.vddDropZone as DropPosition }\n foundZone = true\n }\n if (!foundTab && el.dataset.vddTab && el.dataset.vddTabLeaf) {\n const rect = el.getBoundingClientRect()\n tab.value = {\n leafId: el.dataset.vddTabLeaf,\n panelId: el.dataset.vddTab,\n index: Number.parseInt(el.dataset.vddTabIndex ?? '0', 10),\n side: x - rect.left < rect.width / 2 ? 'left' : 'right',\n }\n foundTab = true\n }\n if (!foundEdge && el.dataset.vddEdge) {\n edge.value = el.dataset.vddEdge as SplitDirection\n foundEdge = true\n }\n if (!foundZone && el.dataset.vddCorner) {\n corner.value = el.dataset.vddCorner as FloatAnchor\n }\n if (foundZone && foundEdge && foundTab) break\n }\n\n if (!foundZone) zone.value = null\n if (!foundEdge) edge.value = null\n if (!foundTab) tab.value = null\n }\n\n /** Apply the armed target. */\n function finishDrag(panelId: string, event: PointerEvent): void {\n const armedEdge = edge.value\n const armedTab = tab.value\n const armedZone = zone.value\n const armedCorner = corner.value\n\n if (armedEdge) {\n ws.dockPanelToWorkspaceEdge(panelId, flip(armedEdge) as SplitDirection)\n } else if (armedTab) {\n let index = armedTab.index\n if (armedTab.side === 'right') index += 1\n // Tab indices come from the DOM, which is pre-removal. `movePanelOrder` removes the\n // panel before inserting it, shifting later positions down by one within the same leaf.\n const leaf = findLeaf(ws.state.gridRoot, armedTab.leafId)\n if (leaf) {\n const current = leaf.panels.indexOf(panelId)\n if (current !== -1 && current < index) index -= 1\n }\n ws.movePanelOrder(panelId, armedTab.leafId, index)\n } else if (armedZone) {\n ws.dockPanelToGroup(panelId, armedZone.leafId, flip(armedZone.position))\n } else if (armedCorner) {\n ws.floatPanel(panelId, undefined, ws.state.isRtl ? flipZoneHorizontal(armedCorner) : armedCorner)\n } else {\n // Nothing armed: float it where the pointer let go, with the title bar under the cursor.\n ws.floatPanel(panelId, { x: event.clientX - 150, y: event.clientY - 15, width: 450, height: 350 })\n }\n reset()\n }\n\n function startTabDrag(\n panelId: string,\n event: PointerEvent,\n onLongPressWithoutDrag?: (e: PointerEvent) => void,\n ): void {\n if (event.pointerType === 'mouse' && event.button !== 0) return\n const el = event.currentTarget as HTMLElement\n const startX = event.clientX\n const startY = event.clientY\n\n if (event.pointerType === 'touch') {\n const pointerId = event.pointerId\n let cancelled = false\n let dragStarted = false\n\n const cleanupPre = () => {\n clearTimeout(timer)\n el.removeEventListener('pointermove', onPreMove)\n el.removeEventListener('pointerup', onPreUp)\n el.removeEventListener('pointercancel', onPreCancel)\n }\n const onPreMove = (e: PointerEvent) => {\n // A finger that travels before the press completes is scrolling, not dragging.\n if (Math.hypot(e.clientX - startX, e.clientY - startY) > CANCEL_MOVE_PX) {\n cancelled = true\n cleanupPre()\n }\n }\n const onPreUp = () => { cancelled = true; cleanupPre() }\n const onPreCancel = onPreUp\n\n const timer = setTimeout(() => {\n if (cancelled) return\n cleanupPre()\n try { el.setPointerCapture(pointerId) } catch { return }\n el.classList.add('vdd-long-press-active')\n document.body.classList.add('vdd-dragging-active')\n navigator.vibrate?.(10)\n\n const onMove = (e: PointerEvent) => {\n if (!dragStarted) { dragStarted = true; begin(panelId) }\n pointer.value = { x: e.clientX, y: e.clientY }\n resolveFromPoint(e.clientX, e.clientY)\n }\n const onEnd = (e: PointerEvent) => {\n el.classList.remove('vdd-long-press-active')\n el.removeEventListener('pointermove', onMove)\n el.removeEventListener('pointerup', onEnd)\n el.removeEventListener('pointercancel', onCancel)\n if (dragStarted) finishDrag(panelId, e)\n else { document.body.classList.remove('vdd-dragging-active'); onLongPressWithoutDrag?.(e) }\n }\n const onCancel = () => {\n el.classList.remove('vdd-long-press-active')\n el.removeEventListener('pointermove', onMove)\n el.removeEventListener('pointerup', onEnd)\n el.removeEventListener('pointercancel', onCancel)\n reset()\n }\n el.addEventListener('pointermove', onMove)\n el.addEventListener('pointerup', onEnd)\n el.addEventListener('pointercancel', onCancel)\n }, LONG_PRESS_MS)\n\n el.addEventListener('pointermove', onPreMove)\n el.addEventListener('pointerup', onPreUp)\n el.addEventListener('pointercancel', onPreCancel)\n return\n }\n\n // Mouse and pen: no pointer capture, so the drop zones keep receiving enter/leave.\n let dragStarted = false\n const onMove = (e: PointerEvent) => {\n if (!dragStarted) {\n if (Math.abs(e.clientX - startX) <= DRAG_THRESHOLD_PX && Math.abs(e.clientY - startY) <= DRAG_THRESHOLD_PX) return\n dragStarted = true\n begin(panelId)\n }\n pointer.value = { x: e.clientX, y: e.clientY }\n }\n const teardown = () => {\n window.removeEventListener('pointermove', onMove)\n window.removeEventListener('pointerup', onUp)\n window.removeEventListener('pointercancel', onCancel)\n }\n const onUp = (e: PointerEvent) => {\n teardown()\n if (dragStarted) finishDrag(panelId, e)\n else document.body.classList.remove('vdd-dragging-active')\n }\n const onCancel = () => { teardown(); reset() }\n\n window.addEventListener('pointermove', onMove)\n window.addEventListener('pointerup', onUp)\n window.addEventListener('pointercancel', onCancel)\n }\n\n const trackPointer = (x: number, y: number): void => {\n pointer.value = { x, y }\n resolveFromPoint(x, y)\n }\n\n const api: DragDock = {\n draggedId, pointer, zone, edge, corner, tab, dragging,\n startTabDrag, beginDrag: begin, trackPointer,\n hoverZone, hoverEdge, hoverCorner, hoverTab, finishDrag, cancel: reset,\n }\n provide(DRAG_DOCK_KEY, api)\n return api\n}\n\n/** @internal */\nexport function useDragDock(): DragDock | null {\n return inject(DRAG_DOCK_KEY, null)\n}\n","/**\n * The standard menus the library offers for a panel.\n *\n * Built here rather than in a component so the same menu is offered wherever a panel is\n * right-clicked — a tab, a floating window's title bar, a taskbar icon — and so the item\n * set is testable without rendering anything.\n */\nimport type { ContextMenuItem } from './contextMenu'\nimport type { PanelDefaultOptions } from './registry'\nimport type { Label, MessageDescriptor } from '../types'\n\n/**\n * Just enough of the workspace to build a menu, so this module does not depend on the store.\n *\n * `format` takes `Label | undefined`, matching the workspace exactly: widening it to\n * `unknown` looks harmless but breaks assignability, since a function accepting a narrow\n * type cannot stand in for one accepting anything.\n */\ninterface MenuHost {\n format: (label: Label | undefined) => string\n messages: Record<string, MessageDescriptor>\n panelMenuItems: (id: string) => ContextMenuItem[]\n}\n\nexport interface PanelMenuActions {\n float: () => void\n minimize: () => void\n close: () => void\n restore: () => void\n maximize: () => void\n}\n\n/**\n * The menu for a docked tab or a floating window.\n *\n * Entries the panel forbids are absent rather than disabled: an action a panel has opted out\n * of is not a temporarily unavailable action, it is not an action at all.\n */\nexport function buildPanelMenu(\n host: MenuHost,\n panelId: string,\n options: PanelDefaultOptions,\n actions: PanelMenuActions,\n): ContextMenuItem[] {\n const items: ContextMenuItem[] = []\n if (options.canDrag !== false) items.push({ label: host.messages.floatWindow!, action: actions.float })\n if (options.canMinimize !== false) items.push({ label: host.messages.minimizePanel!, action: actions.minimize })\n if (items.length > 0 && options.canClose !== false) items.push({ separator: true })\n if (options.canClose !== false) items.push({ label: host.messages.closeTab!, action: actions.close })\n return withPanelContributions(host, panelId, items)\n}\n\n/**\n * The menu for a minimised panel's taskbar icon.\n *\n * \"Maximize\" works here. rdd offered the same item but its `maximizePanel` only mapped over\n * floating windows, where a minimised panel is not — so the item did nothing at all\n * (divergence D1). Restoring first is what makes it mean something.\n */\nexport function buildTaskbarMenu(\n host: MenuHost,\n panelId: string,\n options: PanelDefaultOptions,\n actions: PanelMenuActions,\n): ContextMenuItem[] {\n const items: ContextMenuItem[] = [{ label: host.messages.restorePanel!, action: actions.restore }]\n if (options.canDrag !== false) items.push({ label: host.messages.maximizePanel!, action: actions.maximize })\n if (options.canClose !== false) {\n items.push({ separator: true })\n items.push({ label: host.messages.closePanel!, action: actions.close })\n }\n return withPanelContributions(host, panelId, items)\n}\n\n/** Append whatever the panel itself contributed, behind a separator. */\nfunction withPanelContributions(host: MenuHost, panelId: string, items: ContextMenuItem[]): ContextMenuItem[] {\n const contributed = host.panelMenuItems(panelId)\n if (contributed.length === 0) return items\n return items.length > 0 ? [...items, { separator: true }, ...contributed] : [...contributed]\n}\n","import { getCurrentScope, inject, onScopeDispose } from 'vue'\nimport type { BuiltInEvents } from '../core/eventBus'\nimport { WORKSPACE_KEY } from '../core/workspace'\nimport type { Workspace } from '../core/workspace'\n\n/**\n * The workspace, from anywhere inside an app that has `app.use(workspace)`.\n *\n * Returns refs and functions, so destructuring keeps reactivity — the shape `storeToRefs`\n * exists to produce in Pinia:\n *\n * ```ts\n * const { activePanelId, panels, openPanel } = useWorkspace()\n * ```\n *\n * There is no selector argument. react-dockable-desktop needed one to control re-renders;\n * in Vue a `computed()` is both more capable and the idiom you already know.\n *\n * `subscribe()` is wrapped so a subscription made during `setup` is disposed with the\n * component. Outside a scope it behaves exactly like `workspace.subscribe`.\n */\nexport function useWorkspace<TEvents extends Record<string, unknown> = Record<string, unknown>>(): Workspace<TEvents> {\n const workspace = inject(WORKSPACE_KEY, null) as Workspace<TEvents> | null\n if (!workspace) {\n throw new Error(\n '[vue-dockable-desktop] useWorkspace() found no workspace. Create one with ' +\n 'createWorkspace() and install it with app.use(workspace).',\n )\n }\n\n if (!getCurrentScope()) return workspace\n\n // Auto-disposing subscribe: register in setup, forget about cleanup.\n return {\n ...workspace,\n subscribe<K extends keyof (TEvents & BuiltInEvents) & string>(\n event: K,\n cb: (data: (TEvents & BuiltInEvents)[K]) => void,\n ): () => void {\n const off = workspace.subscribe(event, cb)\n onScopeDispose(off)\n return off\n },\n }\n}\n","/**\n * Pointer-drag and resize primitives.\n *\n * Framework-agnostic on purpose: there is no Vue in this file, so the grid split resizer,\n * the sidebar drawer resizer and both floating-window resize implementations share one\n * mechanic that cannot drift apart. In react-dockable-desktop these had already drifted\n * four ways, differing in exactly the kind of detail (an inline style present in one and\n * missing in another) that caused a real user-visible bug.\n *\n * Exported publicly so applications can build their own resizable UI inside a panel with\n * the same behaviour as the library's own.\n */\n\n// ── Pointer-capture drag mechanics ──────────────────────────────────────────\n\nexport interface PointerDragConfig<TStart> {\n /** The element to capture the pointer on — normally the handle the user grabbed. */\n element: HTMLElement;\n pointerId: number;\n /** The pointerdown event's clientX/clientY, used as the delta origin. */\n startClientX: number;\n startClientY: number;\n /** Snapshot whatever state the caller needs at drag start (sizes, positions, ...). */\n captureStart: () => TStart;\n /** Called on every pointermove with the delta from the drag's start position. */\n onMove: (dx: number, dy: number, start: TStart) => void;\n /** Called once when the drag ends (pointerup or pointercancel). */\n onEnd?: (start: TStart) => void;\n /** Classes toggled on the given elements for the duration of the drag. */\n activeClasses?: Array<{ el: HTMLElement; classes: string[] }>;\n}\n\n/**\n * Starts a pointer-capture-based drag: captures the pointer on `element`, tracks\n * movement via listeners scoped to that element's own lifetime (not `window`), and\n * cleans up automatically on release or cancel.\n */\nexport function startPointerDrag<TStart>(config: PointerDragConfig<TStart>): void {\n const { element, pointerId, startClientX, startClientY, captureStart, onMove, onEnd, activeClasses } = config;\n\n element.setPointerCapture(pointerId);\n activeClasses?.forEach(({ el, classes }) => el.classList.add(...classes));\n const start = captureStart();\n\n const handleMove = (e: PointerEvent) => {\n onMove(e.clientX - startClientX, e.clientY - startClientY, start);\n };\n\n const handleEnd = () => {\n activeClasses?.forEach(({ el, classes }) => el.classList.remove(...classes));\n element.removeEventListener('pointermove', handleMove);\n element.removeEventListener('pointerup', handleEnd);\n element.removeEventListener('pointercancel', handleEnd);\n onEnd?.(start);\n };\n\n element.addEventListener('pointermove', handleMove);\n element.addEventListener('pointerup', handleEnd);\n element.addEventListener('pointercancel', handleEnd);\n}\n\n// ── 8-directional resize math ────────────────────────────────────────────────\n\nexport type ResizeDir = 'n' | 'ne' | 'e' | 'se' | 's' | 'sw' | 'w' | 'nw';\n\nexport interface ResizeRect {\n x: number;\n y: number;\n w: number;\n h: number;\n}\n\nexport interface ResizeConstraints {\n minW: number;\n minH: number;\n /** Upper bound on width — only applies to eastward growth (dir includes 'e'). */\n maxW?: number;\n /** Upper bound on height — only applies to southward growth (dir includes 's'). */\n maxH?: number;\n /** Lower bound on the resulting x — only applies to westward growth (dir includes 'w'). */\n minX?: number;\n /** Lower bound on the resulting y — only applies to northward growth (dir includes 'n'). */\n minY?: number;\n}\n\n/**\n * Pure function computing the new rect for an 8-directional resize handle drag.\n *\n * `maxW`/`maxH` and `minX`/`minY` are independent, direction-scoped constraints\n * rather than one \"container bound\" — a resize toward the fixed edge (e/s) is\n * naturally bounded by a maximum dimension, while a resize toward the moving edge\n * (w/n) is naturally bounded by a minimum position, and the two calling sites this\n * was extracted from need different subsets of these (a workspace floating window omits all four, so it may grow unbounded and be dragged\n * off-screen; a docked inner widget supplies all four to stay within its panel).\n */\nexport function computeResizedRect(dir: ResizeDir, dx: number, dy: number, start: ResizeRect, constraints: ResizeConstraints): ResizeRect {\n const { minW, minH, maxW, maxH, minX, minY } = constraints;\n let { x, y, w, h } = start;\n\n if (dir.includes('e')) {\n w = Math.max(minW, Math.min(start.w + dx, maxW ?? Infinity));\n }\n if (dir.includes('w')) {\n const maxDx = start.w - minW; // largest rightward (shrinking) delta before hitting minW\n const minDx = minX != null ? -(start.x - minX) : -Infinity; // most negative (growing) delta before x hits minX\n const clampedDx = Math.max(minDx, Math.min(dx, maxDx));\n w = start.w - clampedDx;\n x = start.x + clampedDx;\n }\n if (dir.includes('s')) {\n h = Math.max(minH, Math.min(start.h + dy, maxH ?? Infinity));\n }\n if (dir.includes('n')) {\n const maxDy = start.h - minH;\n const minDy = minY != null ? -(start.y - minY) : -Infinity;\n const clampedDy = Math.max(minDy, Math.min(dy, maxDy));\n h = start.h - clampedDy;\n y = start.y + clampedDy;\n }\n\n return { x, y, w, h };\n}\n","<script setup lang=\"ts\">\n/**\n * A place a panel is shown.\n *\n * Renders an empty box and moves the panel's element into it. The panel's own DOM is never a\n * child of this component in Vue's eyes — the persistence port owns it — so this component\n * can mount, unmount and re-render freely without touching panel state.\n *\n * The move happens in a **function ref**, which Vue calls during the patch itself, with the\n * element (and with `null` when it goes away). A watcher would work too but lands a tick\n * later, leaving the panel briefly parked in the off-screen store; a function ref places it\n * as the slot appears. The move is also made here rather than reported upwards, because this\n * is the first moment the host element exists at all.\n */\nimport { onBeforeUnmount } from 'vue'\nimport { usePanelDom } from '../composables/usePanelDom'\nimport { useWorkspace } from '../composables/useWorkspace'\n\nconst props = defineProps<{\n /** The panel to show here. */\n panelId: string\n /** Extra class for the host box. */\n hostClass?: string\n}>()\n\nconst dom = usePanelDom()\nconst ws = useWorkspace()\nlet host: HTMLElement | null = null\n\nfunction setHost(el: unknown): void {\n const element = (el as HTMLElement | null) ?? null\n host = element\n if (!dom || !element) return\n dom.moveTo(props.panelId, element, {\n refocus: ws.state.activePanelId === props.panelId,\n preserveScroll: ws.registry.get(ws.state.panels[props.panelId]?.component ?? '')\n ?.defaultOptions?.preserveScroll !== false,\n })\n}\n\nonBeforeUnmount(() => {\n // Park the panel off-screen — unless another slot has already claimed it, which is what\n // happens when a panel moves from one leaf to another.\n if (dom && dom.hostOf(props.panelId) === host) dom.moveTo(props.panelId, null)\n})\n</script>\n\n<template>\n <div :ref=\"setHost\" class=\"vdd-panel-slot\" :class=\"hostClass\" :data-vdd-slot=\"panelId\" />\n</template>\n","<script setup lang=\"ts\">\n/**\n * A place a panel is shown.\n *\n * Renders an empty box and moves the panel's element into it. The panel's own DOM is never a\n * child of this component in Vue's eyes — the persistence port owns it — so this component\n * can mount, unmount and re-render freely without touching panel state.\n *\n * The move happens in a **function ref**, which Vue calls during the patch itself, with the\n * element (and with `null` when it goes away). A watcher would work too but lands a tick\n * later, leaving the panel briefly parked in the off-screen store; a function ref places it\n * as the slot appears. The move is also made here rather than reported upwards, because this\n * is the first moment the host element exists at all.\n */\nimport { onBeforeUnmount } from 'vue'\nimport { usePanelDom } from '../composables/usePanelDom'\nimport { useWorkspace } from '../composables/useWorkspace'\n\nconst props = defineProps<{\n /** The panel to show here. */\n panelId: string\n /** Extra class for the host box. */\n hostClass?: string\n}>()\n\nconst dom = usePanelDom()\nconst ws = useWorkspace()\nlet host: HTMLElement | null = null\n\nfunction setHost(el: unknown): void {\n const element = (el as HTMLElement | null) ?? null\n host = element\n if (!dom || !element) return\n dom.moveTo(props.panelId, element, {\n refocus: ws.state.activePanelId === props.panelId,\n preserveScroll: ws.registry.get(ws.state.panels[props.panelId]?.component ?? '')\n ?.defaultOptions?.preserveScroll !== false,\n })\n}\n\nonBeforeUnmount(() => {\n // Park the panel off-screen — unless another slot has already claimed it, which is what\n // happens when a panel moves from one leaf to another.\n if (dom && dom.hostOf(props.panelId) === host) dom.moveTo(props.panelId, null)\n})\n</script>\n\n<template>\n <div :ref=\"setHost\" class=\"vdd-panel-slot\" :class=\"hostClass\" :data-vdd-slot=\"panelId\" />\n</template>\n","<script setup lang=\"ts\">\n/**\n * The cross of drop targets over one leaf: four sides plus the centre.\n *\n * Armed state is driven by the drag machine rather than CSS `:hover`. `:hover` is unreliable\n * during an active drag in some browsers and does not exist at all on touch, whereas the\n * machine already tracks the armed target for every input type.\n */\nimport { computed } from 'vue'\nimport type { DropPosition } from '../types'\nimport { useDragDock } from '../composables/useDragDock'\nimport { useWorkspace } from '../composables/useWorkspace'\n\nconst props = defineProps<{ leafId: string }>()\nconst drag = useDragDock()\nconst ws = useWorkspace()\n\nconst POSITIONS: DropPosition[] = ['top', 'bottom', 'left', 'right', 'center']\nconst GLYPH: Record<DropPosition, string> = {\n top: '▲', bottom: '▼', left: '◀', right: '▶', center: '▣',\n}\n\nconst isArmed = (position: DropPosition) =>\n drag?.zone.value?.leafId === props.leafId && drag.zone.value.position === position\n\n/** The preview fills the share of the leaf the drop would actually take. */\nconst preview = computed(() => {\n const armed = drag?.zone.value\n if (!armed || armed.leafId !== props.leafId) return null\n const share = `${ws.state.splitRatio * 100}%`\n const rest = `${(1 - ws.state.splitRatio) * 100}%`\n const p = armed.position\n return {\n left: p === 'right' ? rest : '0',\n top: p === 'bottom' ? rest : '0',\n width: p === 'left' || p === 'right' ? share : '100%',\n height: p === 'top' || p === 'bottom' ? share : '100%',\n }\n})\n</script>\n\n<template>\n <div class=\"vdd-dock-drop-zone-overlay\">\n <div class=\"vdd-dock-target-cross\">\n <div\n v-for=\"position in POSITIONS\"\n :key=\"position\"\n class=\"vdd-dock-target-box\"\n :class=\"[`vdd-dock-target-${position}`, { 'vdd-dock-target-box--active': isArmed(position) }]\n \"\n :data-vdd-drop-zone=\"position\"\n :data-vdd-leaf=\"leafId\"\n @pointerenter=\"drag?.hoverZone(leafId, position)\"\n @pointerleave=\"drag?.hoverZone(leafId, null)\"\n >{{ GLYPH[position] }}</div>\n </div>\n </div>\n <div v-if=\"preview\" class=\"vdd-dock-preview-highlight\" :style=\"preview\" />\n</template>\n","<script setup lang=\"ts\">\n/**\n * The cross of drop targets over one leaf: four sides plus the centre.\n *\n * Armed state is driven by the drag machine rather than CSS `:hover`. `:hover` is unreliable\n * during an active drag in some browsers and does not exist at all on touch, whereas the\n * machine already tracks the armed target for every input type.\n */\nimport { computed } from 'vue'\nimport type { DropPosition } from '../types'\nimport { useDragDock } from '../composables/useDragDock'\nimport { useWorkspace } from '../composables/useWorkspace'\n\nconst props = defineProps<{ leafId: string }>()\nconst drag = useDragDock()\nconst ws = useWorkspace()\n\nconst POSITIONS: DropPosition[] = ['top', 'bottom', 'left', 'right', 'center']\nconst GLYPH: Record<DropPosition, string> = {\n top: '▲', bottom: '▼', left: '◀', right: '▶', center: '▣',\n}\n\nconst isArmed = (position: DropPosition) =>\n drag?.zone.value?.leafId === props.leafId && drag.zone.value.position === position\n\n/** The preview fills the share of the leaf the drop would actually take. */\nconst preview = computed(() => {\n const armed = drag?.zone.value\n if (!armed || armed.leafId !== props.leafId) return null\n const share = `${ws.state.splitRatio * 100}%`\n const rest = `${(1 - ws.state.splitRatio) * 100}%`\n const p = armed.position\n return {\n left: p === 'right' ? rest : '0',\n top: p === 'bottom' ? rest : '0',\n width: p === 'left' || p === 'right' ? share : '100%',\n height: p === 'top' || p === 'bottom' ? share : '100%',\n }\n})\n</script>\n\n<template>\n <div class=\"vdd-dock-drop-zone-overlay\">\n <div class=\"vdd-dock-target-cross\">\n <div\n v-for=\"position in POSITIONS\"\n :key=\"position\"\n class=\"vdd-dock-target-box\"\n :class=\"[`vdd-dock-target-${position}`, { 'vdd-dock-target-box--active': isArmed(position) }]\n \"\n :data-vdd-drop-zone=\"position\"\n :data-vdd-leaf=\"leafId\"\n @pointerenter=\"drag?.hoverZone(leafId, position)\"\n @pointerleave=\"drag?.hoverZone(leafId, null)\"\n >{{ GLYPH[position] }}</div>\n </div>\n </div>\n <div v-if=\"preview\" class=\"vdd-dock-preview-highlight\" :style=\"preview\" />\n</template>\n","<script setup lang=\"ts\">\n/**\n * A tab group: a tab bar and the selected panel's body.\n *\n * The tab bar distinguishes *selected* from *globally active*: the selected tab of an\n * inactive group is drawn differently from the one the user is actually working in. Those\n * must never disagree with `activePanelId` — the invariant behind divergence D2.\n */\nimport { computed } from 'vue'\nimport type { LayoutLeafNode } from '../types'\nimport { useWorkspace } from '../composables/useWorkspace'\nimport { useDragDock } from '../composables/useDragDock'\nimport { buildPanelMenu } from '../core/panelMenu'\nimport VddPanelSlot from './VddPanelSlot.vue'\nimport VddDropZones from './VddDropZones.vue'\n\nconst props = defineProps<{ leaf: LayoutLeafNode }>()\n\nconst ws = useWorkspace()\nconst drag = useDragDock()\n\n/** The standard panel menu, plus whatever the panel itself contributed. */\nfunction openMenu(panelId: string, event: MouseEvent | PointerEvent): void {\n const panel = ws.state.panels[panelId]\n if (!panel) return\n const options = ws.registry.get(panel.component)?.defaultOptions ?? {}\n const items = buildPanelMenu(ws, panelId, options, {\n float: () => ws.floatPanel(panelId),\n minimize: () => ws.minimizePanel(panelId),\n close: () => ws.requestClosePanel(panelId),\n restore: () => ws.restorePanel(panelId),\n maximize: () => ws.maximizePanel(panelId),\n })\n if (items.length === 0) return\n ws.showContextMenu({ event, items })\n}\n\n/** Which half of a tab the pointer is over decides which side the panel is inserted on. */\nfunction onTabPointerMove(leafId: string, panelId: string, index: number, event: PointerEvent): void {\n if (!drag?.dragging.value || event.pointerType === 'touch') return\n const rect = (event.currentTarget as HTMLElement).getBoundingClientRect()\n drag.hoverTab({ leafId, panelId, index, side: event.clientX - rect.left < rect.width / 2 ? 'left' : 'right' })\n}\n\nconst tabs = computed(() => props.leaf.panels.flatMap((id) => {\n const panel = ws.state.panels[id]\n return panel ? [{ id, panel, options: ws.registry.get(panel.component)?.defaultOptions ?? {} }] : []\n}))\n\nconst selectedId = computed(() => props.leaf.activePanelId)\n\nconst tabClass = (id: string) => {\n const hovered = drag?.tab.value\n const isInsertionPoint = hovered?.leafId === props.leaf.id && hovered.panelId === id\n return [\n 'vdd-workspace-tab',\n id === selectedId.value\n ? ['vdd-active', id === ws.state.activePanelId ? 'vdd-workspace-tab-active-focused' : 'vdd-workspace-tab-active-unfocused']\n : 'vdd-workspace-tab-inactive',\n isInsertionPoint ? (hovered.side === 'left' ? 'vdd-drag-hover-left' : 'vdd-drag-hover-right') : '',\n ]\n}\n</script>\n\n<template>\n <div\n class=\"vdd-workspace-panel\"\n :data-vdd-leaf=\"leaf.id\"\n :data-active-panel-id=\"selectedId ?? ''\"\n @pointerdown=\"selectedId && ws.focusPanel(selectedId)\"\n >\n <div class=\"vdd-workspace-tab-bar\">\n <div class=\"vdd-tab-headers-container\">\n <div\n v-for=\"(tab, index) in tabs\"\n :key=\"tab.id\"\n :class=\"tabClass(tab.id)\"\n :data-vdd-tab=\"tab.id\"\n :data-vdd-tab-leaf=\"leaf.id\"\n :data-vdd-tab-index=\"String(index)\"\n role=\"tab\"\n :aria-selected=\"tab.id === selectedId\"\n :style=\"{ cursor: tab.options.canDrag === false ? 'default' : 'pointer' }\"\n @click=\"ws.focusPanel(tab.id)\"\n @contextmenu.prevent=\"openMenu(tab.id, $event)\"\n @pointerdown=\"tab.options.canDrag !== false\n ? drag?.startTabDrag(tab.id, $event, (e) => openMenu(tab.id, e))\n : undefined\"\n @pointermove=\"onTabPointerMove(leaf.id, tab.id, index, $event)\"\n @pointerleave=\"drag?.dragging.value && drag.hoverTab(null)\"\n >\n <span class=\"vdd-text-truncate\">\n <span v-if=\"tab.options.icon\" class=\"vdd-workspace-tab-icon\">\n <component :is=\"tab.options.icon\" />\n </span>\n <span>{{ ws.format(tab.panel.title) }}{{ tab.panel.dirty ? ' *' : '' }}</span>\n </span>\n <span\n v-if=\"tab.options.canClose !== false\"\n class=\"vdd-close-tab-x\"\n :title=\"ws.format(ws.messages.closeTab)\"\n :data-vdd-close=\"tab.id\"\n @click.stop=\"ws.requestClosePanel(tab.id)\"\n >\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\">\n <path d=\"M18 6L6 18M6 6l12 12\" />\n </svg>\n </span>\n </div>\n </div>\n\n <!--\n An empty split group can otherwise only be closed programmatically. rdd offered this\n button and vdd did not — the `closeLeafGroup` action existed with nothing to call it,\n and three CSS rules sat in the stylesheet matching nothing. Found by M13's class/rule\n correspondence sweep, which is exactly the shape of defect it looks for.\n -->\n <div v-if=\"tabs.length === 0\" class=\"vdd-tab-header-actions\">\n <button\n type=\"button\"\n class=\"vdd-header-close-empty-group\"\n :title=\"ws.format(ws.messages.closeEmptyGroup)\"\n :aria-label=\"ws.format(ws.messages.closeEmptyGroup)\"\n data-vdd-close-empty-group\n @click.stop=\"ws.closeLeafGroup(leaf.id)\"\n >\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\">\n <path d=\"M18 6L6 18M6 6l12 12\" />\n </svg>\n </button>\n </div>\n </div>\n\n <div class=\"vdd-panel-body\">\n <VddDropZones v-if=\"drag?.dragging.value\" :leaf-id=\"leaf.id\" />\n <!--\n Keyed by the selected panel so switching tabs mounts a new slot, which hands the\n persistence port a new host. The panel itself is never re-created.\n -->\n <VddPanelSlot v-if=\"selectedId\" :key=\"selectedId\" :panel-id=\"selectedId\" />\n <div v-else class=\"vdd-empty-leaf-placeholder\">\n <span>{{ ws.format(ws.messages.emptyGroup) }}</span>\n </div>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/**\n * A tab group: a tab bar and the selected panel's body.\n *\n * The tab bar distinguishes *selected* from *globally active*: the selected tab of an\n * inactive group is drawn differently from the one the user is actually working in. Those\n * must never disagree with `activePanelId` — the invariant behind divergence D2.\n */\nimport { computed } from 'vue'\nimport type { LayoutLeafNode } from '../types'\nimport { useWorkspace } from '../composables/useWorkspace'\nimport { useDragDock } from '../composables/useDragDock'\nimport { buildPanelMenu } from '../core/panelMenu'\nimport VddPanelSlot from './VddPanelSlot.vue'\nimport VddDropZones from './VddDropZones.vue'\n\nconst props = defineProps<{ leaf: LayoutLeafNode }>()\n\nconst ws = useWorkspace()\nconst drag = useDragDock()\n\n/** The standard panel menu, plus whatever the panel itself contributed. */\nfunction openMenu(panelId: string, event: MouseEvent | PointerEvent): void {\n const panel = ws.state.panels[panelId]\n if (!panel) return\n const options = ws.registry.get(panel.component)?.defaultOptions ?? {}\n const items = buildPanelMenu(ws, panelId, options, {\n float: () => ws.floatPanel(panelId),\n minimize: () => ws.minimizePanel(panelId),\n close: () => ws.requestClosePanel(panelId),\n restore: () => ws.restorePanel(panelId),\n maximize: () => ws.maximizePanel(panelId),\n })\n if (items.length === 0) return\n ws.showContextMenu({ event, items })\n}\n\n/** Which half of a tab the pointer is over decides which side the panel is inserted on. */\nfunction onTabPointerMove(leafId: string, panelId: string, index: number, event: PointerEvent): void {\n if (!drag?.dragging.value || event.pointerType === 'touch') return\n const rect = (event.currentTarget as HTMLElement).getBoundingClientRect()\n drag.hoverTab({ leafId, panelId, index, side: event.clientX - rect.left < rect.width / 2 ? 'left' : 'right' })\n}\n\nconst tabs = computed(() => props.leaf.panels.flatMap((id) => {\n const panel = ws.state.panels[id]\n return panel ? [{ id, panel, options: ws.registry.get(panel.component)?.defaultOptions ?? {} }] : []\n}))\n\nconst selectedId = computed(() => props.leaf.activePanelId)\n\nconst tabClass = (id: string) => {\n const hovered = drag?.tab.value\n const isInsertionPoint = hovered?.leafId === props.leaf.id && hovered.panelId === id\n return [\n 'vdd-workspace-tab',\n id === selectedId.value\n ? ['vdd-active', id === ws.state.activePanelId ? 'vdd-workspace-tab-active-focused' : 'vdd-workspace-tab-active-unfocused']\n : 'vdd-workspace-tab-inactive',\n isInsertionPoint ? (hovered.side === 'left' ? 'vdd-drag-hover-left' : 'vdd-drag-hover-right') : '',\n ]\n}\n</script>\n\n<template>\n <div\n class=\"vdd-workspace-panel\"\n :data-vdd-leaf=\"leaf.id\"\n :data-active-panel-id=\"selectedId ?? ''\"\n @pointerdown=\"selectedId && ws.focusPanel(selectedId)\"\n >\n <div class=\"vdd-workspace-tab-bar\">\n <div class=\"vdd-tab-headers-container\">\n <div\n v-for=\"(tab, index) in tabs\"\n :key=\"tab.id\"\n :class=\"tabClass(tab.id)\"\n :data-vdd-tab=\"tab.id\"\n :data-vdd-tab-leaf=\"leaf.id\"\n :data-vdd-tab-index=\"String(index)\"\n role=\"tab\"\n :aria-selected=\"tab.id === selectedId\"\n :style=\"{ cursor: tab.options.canDrag === false ? 'default' : 'pointer' }\"\n @click=\"ws.focusPanel(tab.id)\"\n @contextmenu.prevent=\"openMenu(tab.id, $event)\"\n @pointerdown=\"tab.options.canDrag !== false\n ? drag?.startTabDrag(tab.id, $event, (e) => openMenu(tab.id, e))\n : undefined\"\n @pointermove=\"onTabPointerMove(leaf.id, tab.id, index, $event)\"\n @pointerleave=\"drag?.dragging.value && drag.hoverTab(null)\"\n >\n <span class=\"vdd-text-truncate\">\n <span v-if=\"tab.options.icon\" class=\"vdd-workspace-tab-icon\">\n <component :is=\"tab.options.icon\" />\n </span>\n <span>{{ ws.format(tab.panel.title) }}{{ tab.panel.dirty ? ' *' : '' }}</span>\n </span>\n <span\n v-if=\"tab.options.canClose !== false\"\n class=\"vdd-close-tab-x\"\n :title=\"ws.format(ws.messages.closeTab)\"\n :data-vdd-close=\"tab.id\"\n @click.stop=\"ws.requestClosePanel(tab.id)\"\n >\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\">\n <path d=\"M18 6L6 18M6 6l12 12\" />\n </svg>\n </span>\n </div>\n </div>\n\n <!--\n An empty split group can otherwise only be closed programmatically. rdd offered this\n button and vdd did not — the `closeLeafGroup` action existed with nothing to call it,\n and three CSS rules sat in the stylesheet matching nothing. Found by M13's class/rule\n correspondence sweep, which is exactly the shape of defect it looks for.\n -->\n <div v-if=\"tabs.length === 0\" class=\"vdd-tab-header-actions\">\n <button\n type=\"button\"\n class=\"vdd-header-close-empty-group\"\n :title=\"ws.format(ws.messages.closeEmptyGroup)\"\n :aria-label=\"ws.format(ws.messages.closeEmptyGroup)\"\n data-vdd-close-empty-group\n @click.stop=\"ws.closeLeafGroup(leaf.id)\"\n >\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\">\n <path d=\"M18 6L6 18M6 6l12 12\" />\n </svg>\n </button>\n </div>\n </div>\n\n <div class=\"vdd-panel-body\">\n <VddDropZones v-if=\"drag?.dragging.value\" :leaf-id=\"leaf.id\" />\n <!--\n Keyed by the selected panel so switching tabs mounts a new slot, which hands the\n persistence port a new host. The panel itself is never re-created.\n -->\n <VddPanelSlot v-if=\"selectedId\" :key=\"selectedId\" :panel-id=\"selectedId\" />\n <div v-else class=\"vdd-empty-leaf-placeholder\">\n <span>{{ ws.format(ws.messages.emptyGroup) }}</span>\n </div>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/**\n * The recursive grid: a branch renders its children with a draggable divider between each\n * pair; a leaf renders a tab group.\n *\n * `path` is the index route from the root to this node — what `updateSplitSizes` addresses.\n */\nimport { computed } from 'vue'\nimport type { LayoutNode } from '../types'\nimport { useWorkspace } from '../composables/useWorkspace'\nimport { startPointerDrag } from '../core/dragResize'\nimport VddLeafGroup from './VddLeafGroup.vue'\n\nconst props = defineProps<{ node: LayoutNode; path: number[] }>()\n\nconst ws = useWorkspace()\nconst isRow = computed(() => props.node.type === 'branch' && props.node.orientation === 'horizontal')\n\nfunction onDividerDown(index: number, event: PointerEvent): void {\n if (props.node.type !== 'branch') return\n event.preventDefault()\n const divider = event.currentTarget as HTMLElement\n const parent = divider.parentElement\n const extent = parent\n ? (isRow.value ? parent.clientWidth : parent.clientHeight)\n : (isRow.value ? 1000 : 800)\n const sizes = [...props.node.sizes]\n\n startPointerDrag({\n element: divider,\n pointerId: event.pointerId,\n startClientX: event.clientX,\n startClientY: event.clientY,\n captureStart: () => sizes,\n activeClasses: [\n { el: divider, classes: ['vdd-active'] },\n { el: document.body, classes: ['vdd-resizing-active', isRow.value ? 'vdd-resizing-col-active' : 'vdd-resizing-row-active'] },\n ],\n onMove: (dx, dy, start) => {\n const delta = (isRow.value ? dx : dy) / extent\n const next = [...start]\n next[index] = (start[index] ?? 0) + delta\n next[index + 1] = (start[index + 1] ?? 0) - delta\n // Neither neighbour may collapse; below this the divider simply stops.\n if ((next[index] ?? 0) > 0.05 && (next[index + 1] ?? 0) > 0.05) {\n ws.updateSplitSizes(props.path, next)\n }\n },\n })\n}\n</script>\n\n<template>\n <VddLeafGroup v-if=\"node.type === 'leaf'\" :leaf=\"node\" />\n\n <div\n v-else\n class=\"vdd-workspace-branch\"\n :class=\"isRow ? 'vdd-row' : 'vdd-column'\"\n :style=\"{ display: 'flex', flexDirection: isRow ? 'row' : 'column', width: '100%', height: '100%', overflow: 'hidden', position: 'relative' }\"\n >\n <template v-for=\"(child, index) in node.children\" :key=\"index\">\n <div :style=\"{ flexGrow: node.sizes[index], flexBasis: `${(node.sizes[index] ?? 0) * 100}%`, overflow: 'hidden', position: 'relative', minWidth: 0, minHeight: 0 }\">\n <VddWorkspaceGrid :node=\"child\" :path=\"[...path, index]\" />\n </div>\n <div\n v-if=\"index < node.children.length - 1\"\n class=\"vdd-resizer-bar\"\n :data-vdd-divider=\"[...path, index].join('-')\"\n :style=\"{ cursor: isRow ? 'col-resize' : 'row-resize', width: isRow ? '1px' : '100%', height: isRow ? '100%' : '1px', zIndex: 20 }\"\n @pointerdown=\"onDividerDown(index, $event)\"\n />\n </template>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/**\n * The recursive grid: a branch renders its children with a draggable divider between each\n * pair; a leaf renders a tab group.\n *\n * `path` is the index route from the root to this node — what `updateSplitSizes` addresses.\n */\nimport { computed } from 'vue'\nimport type { LayoutNode } from '../types'\nimport { useWorkspace } from '../composables/useWorkspace'\nimport { startPointerDrag } from '../core/dragResize'\nimport VddLeafGroup from './VddLeafGroup.vue'\n\nconst props = defineProps<{ node: LayoutNode; path: number[] }>()\n\nconst ws = useWorkspace()\nconst isRow = computed(() => props.node.type === 'branch' && props.node.orientation === 'horizontal')\n\nfunction onDividerDown(index: number, event: PointerEvent): void {\n if (props.node.type !== 'branch') return\n event.preventDefault()\n const divider = event.currentTarget as HTMLElement\n const parent = divider.parentElement\n const extent = parent\n ? (isRow.value ? parent.clientWidth : parent.clientHeight)\n : (isRow.value ? 1000 : 800)\n const sizes = [...props.node.sizes]\n\n startPointerDrag({\n element: divider,\n pointerId: event.pointerId,\n startClientX: event.clientX,\n startClientY: event.clientY,\n captureStart: () => sizes,\n activeClasses: [\n { el: divider, classes: ['vdd-active'] },\n { el: document.body, classes: ['vdd-resizing-active', isRow.value ? 'vdd-resizing-col-active' : 'vdd-resizing-row-active'] },\n ],\n onMove: (dx, dy, start) => {\n const delta = (isRow.value ? dx : dy) / extent\n const next = [...start]\n next[index] = (start[index] ?? 0) + delta\n next[index + 1] = (start[index + 1] ?? 0) - delta\n // Neither neighbour may collapse; below this the divider simply stops.\n if ((next[index] ?? 0) > 0.05 && (next[index + 1] ?? 0) > 0.05) {\n ws.updateSplitSizes(props.path, next)\n }\n },\n })\n}\n</script>\n\n<template>\n <VddLeafGroup v-if=\"node.type === 'leaf'\" :leaf=\"node\" />\n\n <div\n v-else\n class=\"vdd-workspace-branch\"\n :class=\"isRow ? 'vdd-row' : 'vdd-column'\"\n :style=\"{ display: 'flex', flexDirection: isRow ? 'row' : 'column', width: '100%', height: '100%', overflow: 'hidden', position: 'relative' }\"\n >\n <template v-for=\"(child, index) in node.children\" :key=\"index\">\n <div :style=\"{ flexGrow: node.sizes[index], flexBasis: `${(node.sizes[index] ?? 0) * 100}%`, overflow: 'hidden', position: 'relative', minWidth: 0, minHeight: 0 }\">\n <VddWorkspaceGrid :node=\"child\" :path=\"[...path, index]\" />\n </div>\n <div\n v-if=\"index < node.children.length - 1\"\n class=\"vdd-resizer-bar\"\n :data-vdd-divider=\"[...path, index].join('-')\"\n :style=\"{ cursor: isRow ? 'col-resize' : 'row-resize', width: isRow ? '1px' : '100%', height: isRow ? '100%' : '1px', zIndex: 20 }\"\n @pointerdown=\"onDividerDown(index, $event)\"\n />\n </template>\n </div>\n</template>\n","import { computed, getCurrentScope, inject, onScopeDispose, provide, ref } from 'vue'\nimport type { ComputedRef, InjectionKey, Ref } from 'vue'\nimport type { ContainerType, DirtyStateOptions, Label } from '../types'\nimport { WORKSPACE_KEY } from '../core/workspace'\nimport type { Workspace } from '../core/workspace'\n\n/** What a panel's container tells it about itself. @internal */\nexport interface PanelContext {\n id: string\n containerType: Ref<ContainerType>\n /** Set by a container that is not the workspace (a modal, a side panel). */\n close?: (options?: { force?: boolean }) => void | Promise<void>\n setTitle?: (title: Label) => void\n setIcon?: (icon: unknown) => void\n setDirty?: (dirty: boolean, options?: DirtyStateOptions) => void\n size?: Ref<{ width: number; height: number } | null>\n}\n\nexport const PANEL_KEY = Symbol('vdd-panel') as InjectionKey<PanelContext>\n\n/** @internal — used by containers to tell their content which panel it is. */\nexport function providePanel(context: PanelContext): void {\n provide(PANEL_KEY, context)\n}\n\n/** What {@link usePanel} returns. */\nexport interface UsePanelReturn {\n /** This panel's instance id. */\n id: string\n /** Live title. */\n title: ComputedRef<Label>\n /** Whether this is the globally active panel — the one contributions are read from. */\n isActive: ComputedRef<boolean>\n /** Whether this panel is minimised. It is still mounted and still running. */\n isMinimized: ComputedRef<boolean>\n /** Whether this panel is a floating window. */\n isFloating: ComputedRef<boolean>\n /** Where this panel is rendered. */\n containerType: ComputedRef<ContainerType>\n /** The panel's rendered size, or `null` before it has been laid out. */\n size: ComputedRef<{ width: number; height: number } | null>\n /** Unsaved changes. */\n dirty: ComputedRef<boolean>\n setTitle: (title: Label) => void\n setIcon: (icon: unknown) => void\n setDirty: (dirty: boolean, options?: DirtyStateOptions) => void\n /** Close, honouring dirty state and any `onBeforeClose` guard. */\n close: (options?: { force?: boolean }) => void | Promise<void>\n minimize: () => void\n /**\n * Veto a close. Return `false` (or a promise of it) to block.\n * Registered in `setup` and disposed with the component — nothing to unsubscribe.\n */\n onBeforeClose: (guard: () => boolean | Promise<boolean>) => void\n /**\n * Contribute this panel's live state to `saveLayout()`, pulled fresh on every save — for\n * state that accumulates after opening and so cannot be captured by open-time props.\n * Must be synchronous and JSON-serialisable. Disposed with the component.\n */\n onSaveState: (provider: () => unknown) => void\n}\n\nconst NO_PANEL = 'standalone'\n\n/**\n * A panel's view of itself and its container.\n *\n * Everything that describes *state* is a ref, so you react with the tools you already use:\n *\n * ```ts\n * const { isActive, size } = usePanel()\n * watch(isActive, active => active && editor.focus())\n * watch(size, ({ width, height }) => chart.resize(width, height))\n * ```\n *\n * react-dockable-desktop exposed six subscription methods plus two size APIs for this,\n * because React cannot hand a component live state. Those are all refs here\n * (docs/decisions/0006-refs-over-subscriptions.md).\n *\n * Works outside any container too: the refs then report a `'standalone'` panel and the\n * actions are no-ops with a development warning, so a panel component can be rendered on\n * its own — in a test, or a storybook — without special-casing.\n */\nexport function usePanel(): UsePanelReturn {\n const ctx = inject(PANEL_KEY, null)\n const workspace = inject(WORKSPACE_KEY, null) as Workspace<never> | null\n const id = ctx?.id ?? NO_PANEL\n\n const warnStandalone = (what: string) => {\n if (process.env.NODE_ENV !== 'production') {\n console.warn(\n `[vue-dockable-desktop] usePanel().${what} was called outside a panel container, ` +\n `so it did nothing. This is expected when a panel component is rendered standalone.`,\n )\n }\n }\n\n const info = computed(() => (workspace ? workspace.state.panels[id] : undefined))\n const fallbackSize = ref<{ width: number; height: number } | null>(null)\n\n return {\n id,\n title: computed(() => info.value?.title ?? id),\n isActive: computed(() => !!workspace && workspace.state.activePanelId === id),\n isMinimized: computed(() => info.value?.state === 'minimized'),\n isFloating: computed(() => info.value?.state === 'floating'),\n containerType: computed(() => ctx?.containerType.value ?? NO_PANEL),\n size: computed(() => (ctx?.size ?? fallbackSize).value),\n dirty: computed(() => info.value?.dirty === true),\n\n setTitle: (title) => {\n if (ctx?.setTitle) ctx.setTitle(title)\n else if (workspace && info.value) workspace.updatePanelTitle(id, title)\n else warnStandalone('setTitle')\n },\n setIcon: (icon) => {\n if (ctx?.setIcon) ctx.setIcon(icon)\n else warnStandalone('setIcon')\n },\n setDirty: (dirty, options) => {\n if (ctx?.setDirty) ctx.setDirty(dirty, options)\n else if (workspace && info.value) workspace.setPanelDirty(id, dirty, options)\n else warnStandalone('setDirty')\n },\n close: (options) => {\n if (ctx?.close) return ctx.close(options)\n if (workspace && info.value) return workspace.requestClosePanel(id, options)\n warnStandalone('close')\n },\n minimize: () => {\n if (workspace && info.value) workspace.minimizePanel(id)\n else warnStandalone('minimize')\n },\n\n onBeforeClose: (guard) => {\n if (!workspace || !info.value) { warnStandalone('onBeforeClose'); return }\n const off = workspace.registerCloseGuard(id, guard)\n if (getCurrentScope()) onScopeDispose(off)\n },\n onSaveState: (provider) => {\n if (!workspace || !info.value) { warnStandalone('onSaveState'); return }\n const off = workspace.registerStateProvider(id, provider)\n if (getCurrentScope()) onScopeDispose(off)\n },\n }\n}\n","<script setup lang=\"ts\">\n/**\n * One panel, mounted once for its whole life.\n *\n * Teleports the panel's component into that panel's cache element and tells it who it is.\n * The teleport target never changes and never disappears, so no layout change can unmount\n * the panel — the structural guarantee behind\n * docs/decisions/0002-zero-unmount-via-teleport.md.\n */\nimport { computed, ref, watchEffect } from 'vue'\nimport type { PanelDomCache } from '../core/panelDom'\nimport { providePanel } from '../composables/usePanel'\nimport { useWorkspace } from '../composables/useWorkspace'\nimport type { ContainerType } from '../types'\n\nconst props = defineProps<{ panelId: string; cache: PanelDomCache }>()\n\nconst ws = useWorkspace()\nconst info = computed(() => ws.state.panels[props.panelId])\nconst entry = computed(() => (info.value ? ws.registry.get(info.value.component) : undefined))\n\n/**\n * Which kind of container this panel is in.\n *\n * A minimised panel reports what it *was*, not `'dockable-panel'`: minimising does not move\n * a panel between containers, it only takes it off screen — the panel is still mounted and\n * still running, and restoring puts it back where it came from. Reporting the docked type\n * while minimised made one minimise/restore cycle of a floating window look like two\n * container changes to anything watching, which is exactly the false signal rdd's\n * `onContainerTypeChange` promised not to send.\n */\nconst containerType = computed<ContainerType>(() => {\n const panel = info.value\n if (!panel) return 'dockable-panel'\n const effective = panel.state === 'minimized' ? panel.previousState ?? 'docked' : panel.state\n return effective === 'floating' ? 'floating-window' : 'dockable-panel'\n})\n\n/** Reported by the slot that currently shows this panel; `null` until it has been laid out. */\nconst size = ref<{ width: number; height: number } | null>(null)\n\nprovidePanel({\n id: props.panelId,\n containerType,\n size,\n})\n\nconst target = computed(() => props.cache.elementFor(props.panelId))\n\n// Track the rendered size so `usePanel().size` is live without the panel asking for it.\nlet observer: ResizeObserver | null = null\nwatchEffect((onCleanup) => {\n const el = target.value\n if (typeof ResizeObserver === 'undefined') return\n observer = new ResizeObserver((entries) => {\n for (const e of entries) {\n const { width, height } = e.contentRect\n if (width > 0 && height > 0) {\n size.value = { width, height }\n // Remembered beyond this host's lifetime: the taskbar preview scales a thumbnail from\n // the size the panel had while it was still on screen.\n props.cache.reportSize(props.panelId, { width, height })\n }\n }\n })\n observer.observe(el)\n onCleanup(() => { observer?.disconnect(); observer = null })\n})\n</script>\n\n<template>\n <Teleport :to=\"target\">\n <div class=\"vdd-panel-content\" :dir=\"ws.state.dir\">\n <component\n v-if=\"entry\"\n :is=\"entry.component\"\n v-bind=\"info?.props ?? {}\"\n :panel-id=\"panelId\"\n />\n <div v-else class=\"vdd-unregistered-panel\">\n <strong>Unregistered panel</strong>\n <span>No component is registered for key \"{{ info?.component }}\".</span>\n </div>\n </div>\n </Teleport>\n</template>\n","<script setup lang=\"ts\">\n/**\n * One panel, mounted once for its whole life.\n *\n * Teleports the panel's component into that panel's cache element and tells it who it is.\n * The teleport target never changes and never disappears, so no layout change can unmount\n * the panel — the structural guarantee behind\n * docs/decisions/0002-zero-unmount-via-teleport.md.\n */\nimport { computed, ref, watchEffect } from 'vue'\nimport type { PanelDomCache } from '../core/panelDom'\nimport { providePanel } from '../composables/usePanel'\nimport { useWorkspace } from '../composables/useWorkspace'\nimport type { ContainerType } from '../types'\n\nconst props = defineProps<{ panelId: string; cache: PanelDomCache }>()\n\nconst ws = useWorkspace()\nconst info = computed(() => ws.state.panels[props.panelId])\nconst entry = computed(() => (info.value ? ws.registry.get(info.value.component) : undefined))\n\n/**\n * Which kind of container this panel is in.\n *\n * A minimised panel reports what it *was*, not `'dockable-panel'`: minimising does not move\n * a panel between containers, it only takes it off screen — the panel is still mounted and\n * still running, and restoring puts it back where it came from. Reporting the docked type\n * while minimised made one minimise/restore cycle of a floating window look like two\n * container changes to anything watching, which is exactly the false signal rdd's\n * `onContainerTypeChange` promised not to send.\n */\nconst containerType = computed<ContainerType>(() => {\n const panel = info.value\n if (!panel) return 'dockable-panel'\n const effective = panel.state === 'minimized' ? panel.previousState ?? 'docked' : panel.state\n return effective === 'floating' ? 'floating-window' : 'dockable-panel'\n})\n\n/** Reported by the slot that currently shows this panel; `null` until it has been laid out. */\nconst size = ref<{ width: number; height: number } | null>(null)\n\nprovidePanel({\n id: props.panelId,\n containerType,\n size,\n})\n\nconst target = computed(() => props.cache.elementFor(props.panelId))\n\n// Track the rendered size so `usePanel().size` is live without the panel asking for it.\nlet observer: ResizeObserver | null = null\nwatchEffect((onCleanup) => {\n const el = target.value\n if (typeof ResizeObserver === 'undefined') return\n observer = new ResizeObserver((entries) => {\n for (const e of entries) {\n const { width, height } = e.contentRect\n if (width > 0 && height > 0) {\n size.value = { width, height }\n // Remembered beyond this host's lifetime: the taskbar preview scales a thumbnail from\n // the size the panel had while it was still on screen.\n props.cache.reportSize(props.panelId, { width, height })\n }\n }\n })\n observer.observe(el)\n onCleanup(() => { observer?.disconnect(); observer = null })\n})\n</script>\n\n<template>\n <Teleport :to=\"target\">\n <div class=\"vdd-panel-content\" :dir=\"ws.state.dir\">\n <component\n v-if=\"entry\"\n :is=\"entry.component\"\n v-bind=\"info?.props ?? {}\"\n :panel-id=\"panelId\"\n />\n <div v-else class=\"vdd-unregistered-panel\">\n <strong>Unregistered panel</strong>\n <span>No component is registered for key \"{{ info?.component }}\".</span>\n </div>\n </div>\n </Teleport>\n</template>\n","<script setup lang=\"ts\">\n/**\n * A panel detached from the grid: its own title bar, draggable, resizable on eight edges,\n * stackable by z-order, and optionally pinned to a workspace corner.\n *\n * Positioning is deliberately split. A free-floating window is placed by `left`/`top`, which\n * are physical. An **anchored** window is placed by CSS *logical* properties\n * (`insetInlineStart`/`insetInlineEnd`) driven by the element's own `dir`, so switching\n * reading direction mirrors it with no JavaScript and no stored physical coordinates — which\n * is what lets a layout saved in one direction restore correctly in the other.\n */\nimport { computed } from 'vue'\nimport type { CSSProperties } from 'vue'\nimport type { FloatingWindow } from '../types'\nimport { useWorkspace } from '../composables/useWorkspace'\nimport { useDragDock } from '../composables/useDragDock'\nimport { buildPanelMenu } from '../core/panelMenu'\nimport { computeResizedRect, startPointerDrag } from '../core/dragResize'\nimport type { ResizeDir } from '../core/dragResize'\nimport VddPanelSlot from './VddPanelSlot.vue'\n\nconst props = defineProps<{\n window: FloatingWindow\n /** The workspace's measured size, for the clamps. */\n viewport: { width: number; height: number }\n}>()\n\nconst ws = useWorkspace()\nconst drag = useDragDock()\n\n/** Inset from the workspace edge, and the gap between windows stacked in one corner. */\nconst CORNER_INSET = 8\nconst CORNER_GAP = 8\nconst HANDLES: ResizeDir[] = ['n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw']\n\nconst panel = computed(() => ws.state.panels[props.window.id])\nconst options = computed(() => (panel.value ? ws.registry.get(panel.value.component)?.defaultOptions ?? {} : {}))\nconst isFocused = computed(() => ws.state.activePanelId === props.window.id)\nconst isDragged = computed(() => ws.state.draggedPanelId === props.window.id)\nconst px = (v: number | string) => (typeof v === 'number' ? `${v}px` : v)\n\n/** Windows sharing this corner, in stack order. */\nconst stack = computed(() =>\n ws.state.floating.filter(w => w.anchor === props.window.anchor && !w.maximized))\n\n/** How far along the block axis this window sits, clearing whatever is stacked before it. */\nconst stackOffset = computed(() => {\n if (!props.window.anchor) return 0\n const index = stack.value.findIndex(w => w.id === props.window.id)\n let offset = CORNER_INSET\n for (let i = 0; i < index; i++) {\n const h = stack.value[i]!.height\n offset += (typeof h === 'number' ? h : Number.parseFloat(String(h))) + CORNER_GAP\n }\n return offset\n})\n\nconst style = computed<CSSProperties>(() => {\n const w = props.window\n const base: CSSProperties = {\n position: 'absolute',\n zIndex: w.z,\n pointerEvents: isDragged.value ? 'none' : 'auto',\n }\n if (w.maximized) return { ...base, left: 0, top: 0, width: '100%', height: '100%' }\n\n if (w.anchor) {\n const isTop = w.anchor.startsWith('top')\n const isEnd = w.anchor.endsWith('-right')\n return {\n ...base,\n [isEnd ? 'insetInlineEnd' : 'insetInlineStart']: `${CORNER_INSET}px`,\n [isTop ? 'top' : 'bottom']: `${stackOffset.value}px`,\n width: px(w.width),\n height: px(w.height),\n transition: isDragged.value ? 'none' : 'top 0.2s ease, bottom 0.2s ease',\n }\n }\n return { ...base, left: px(w.x), top: px(w.y), width: px(w.width), height: px(w.height) }\n})\n\nconst contributed = computed(() => ws.panelMenuItems(props.window.id))\n\nfunction openMenu(event: MouseEvent | PointerEvent, onlyContributed = false): void {\n const items = onlyContributed\n ? contributed.value\n : buildPanelMenu(ws, props.window.id, options.value, {\n float: () => ws.floatPanel(props.window.id),\n minimize: () => ws.minimizePanel(props.window.id),\n close: () => ws.requestClosePanel(props.window.id),\n restore: () => ws.restorePanel(props.window.id),\n maximize: () => ws.maximizePanel(props.window.id),\n })\n if (items.length === 0) return\n ws.showContextMenu({ event, items })\n}\n\nfunction onTitlePointerDown(event: PointerEvent): void {\n if (options.value.canDrag === false || props.window.maximized) return\n if (event.button !== 0) return\n event.preventDefault()\n ws.focusPanel(props.window.id)\n\n const el = (event.currentTarget as HTMLElement).closest('.vdd-floating-window') as HTMLElement | null\n const startX = el?.offsetLeft ?? 0\n const startY = el?.offsetTop ?? 0\n const originClientX = event.clientX\n const originClientY = event.clientY\n let moved = false\n\n startPointerDrag({\n element: event.currentTarget as HTMLElement,\n pointerId: event.pointerId,\n startClientX: event.clientX,\n startClientY: event.clientY,\n captureStart: () => ({ x: startX, y: startY }),\n activeClasses: [{ el: document.body, classes: ['vdd-dragging-active'] }],\n onMove: (dx, dy, start) => {\n // A window dragged over a drop target docks there on release, exactly like a tab — so\n // the gesture is registered with the drag machine, which makes the zones appear.\n if (!moved) { moved = true; drag?.beginDrag(props.window.id) }\n // Dragging a pinned window unpins it: a corner anchor and a free position are mutually\n // exclusive, and the user has just chosen the position.\n ws.updateFloatingPosition(props.window.id, { x: start.x + dx, y: start.y + dy, anchor: null })\n // `startPointerDrag` holds pointer capture, so the zones get no hover events: the armed\n // target has to be resolved by hit-testing the pointer instead.\n drag?.trackPointer(originClientX + dx, originClientY + dy)\n },\n onEnd: () => {\n if (!moved || !drag) return\n const armed = drag.zone.value ?? drag.edge.value ?? drag.corner.value ?? drag.tab.value\n // Released over nothing: the window simply stays where the drag left it.\n if (armed) drag.finishDrag(props.window.id, new PointerEvent('pointerup'))\n else drag.cancel()\n },\n })\n}\n\nfunction onHandlePointerDown(dir: ResizeDir, event: PointerEvent): void {\n if (props.window.maximized) return\n if (event.button !== 0) return\n event.preventDefault()\n event.stopPropagation()\n ws.focusPanel(props.window.id)\n\n const el = (event.currentTarget as HTMLElement).closest('.vdd-floating-window') as HTMLElement | null\n const start = {\n x: el?.offsetLeft ?? 0,\n y: el?.offsetTop ?? 0,\n w: el?.offsetWidth ?? 400,\n h: el?.offsetHeight ?? 300,\n }\n\n startPointerDrag({\n element: event.currentTarget as HTMLElement,\n pointerId: event.pointerId,\n startClientX: event.clientX,\n startClientY: event.clientY,\n captureStart: () => start,\n activeClasses: [{ el: document.body, classes: ['vdd-resizing-active'] }],\n // No maxW/maxH/minX/minY: a workspace window may grow past the viewport and be dragged\n // off-screen, matching rdd. The clamp on workspace *resize* pulls it back into reach.\n onMove: (dx, dy, from) => {\n const r = computeResizedRect(dir, dx, dy, from, { minW: 200, minH: 150 })\n ws.updateFloatingPosition(props.window.id, { x: r.x, y: r.y, width: r.w, height: r.h })\n },\n })\n}\n</script>\n\n<template>\n <div\n v-if=\"panel\"\n class=\"vdd-floating-window\"\n :class=\"[{\n 'vdd-maximized': window.maximized,\n 'vdd-window-focused': isFocused,\n }, ws.classes.window]\"\n :data-vdd-window=\"window.id\"\n :dir=\"ws.state.dir\"\n :style=\"style\"\n @pointerdown.capture=\"ws.focusPanel(window.id)\"\n >\n <div\n class=\"vdd-floating-window-titlebar\"\n :data-vdd-titlebar=\"window.id\"\n :style=\"{ cursor: window.maximized || options.canDrag === false ? 'default' : 'move' }\"\n @pointerdown=\"onTitlePointerDown\"\n @contextmenu.prevent=\"openMenu($event)\"\n @dblclick=\"ws.maximizePanel(window.id)\"\n >\n <span class=\"vdd-floating-window-title\">\n <span v-if=\"options.icon\" class=\"vdd-window-title-icon\"><component :is=\"options.icon\" /></span>\n <span>{{ ws.format(panel.title) }}{{ panel.dirty ? ' *' : '' }}</span>\n </span>\n <div class=\"vdd-titlebar-actions\" @pointerdown.stop>\n <button\n v-if=\"contributed.length > 0\"\n type=\"button\"\n class=\"vdd-custom-tab-btn vdd-btn-more-actions\"\n :data-vdd-more=\"window.id\"\n :title=\"ws.format(ws.messages.moreActions)\"\n @click=\"openMenu($event, true)\"\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"currentColor\" style=\"display: block\">\n <circle cx=\"12\" cy=\"5\" r=\"2\" /><circle cx=\"12\" cy=\"12\" r=\"2\" /><circle cx=\"12\" cy=\"19\" r=\"2\" />\n </svg>\n </button>\n <button\n type=\"button\"\n class=\"vdd-custom-tab-btn vdd-btn-maximize-tab\"\n :data-vdd-maximize=\"window.id\"\n :title=\"ws.format(window.maximized ? ws.messages.restoreSize : ws.messages.maximize)\"\n @click=\"ws.maximizePanel(window.id)\"\n >\n <svg width=\"10\" height=\"10\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\">\n <rect x=\"4\" y=\"4\" width=\"16\" height=\"16\" rx=\"1.5\" />\n </svg>\n </button>\n <button\n v-if=\"options.canMinimize !== false\"\n type=\"button\"\n class=\"vdd-custom-tab-btn vdd-btn-minimize-tab\"\n :data-vdd-minimize=\"window.id\"\n :title=\"ws.format(ws.messages.minimize)\"\n @click=\"ws.minimizePanel(window.id)\"\n >\n <svg width=\"10\" height=\"10\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"3\">\n <path d=\"M5 12h14\" />\n </svg>\n </button>\n <button\n v-if=\"options.canClose !== false\"\n type=\"button\"\n class=\"vdd-custom-tab-btn vdd-btn-close-tab\"\n :data-vdd-close-window=\"window.id\"\n :title=\"ws.format(ws.messages.close)\"\n @click=\"ws.requestClosePanel(window.id)\"\n >\n <svg width=\"10\" height=\"10\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"3\">\n <path d=\"M18 6L6 18M6 6l12 12\" />\n </svg>\n </button>\n </div>\n </div>\n\n <div class=\"vdd-floating-window-body\" :class=\"ws.classes.windowBody\">\n <VddPanelSlot :panel-id=\"window.id\" />\n </div>\n\n <template v-if=\"!window.maximized\">\n <div\n v-for=\"dir in HANDLES\"\n :key=\"dir\"\n class=\"vdd-resize-handle\"\n :class=\"`vdd-resize-${dir}`\"\n :data-vdd-handle=\"`${window.id}:${dir}`\"\n @pointerdown=\"onHandlePointerDown(dir, $event)\"\n />\n </template>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/**\n * A panel detached from the grid: its own title bar, draggable, resizable on eight edges,\n * stackable by z-order, and optionally pinned to a workspace corner.\n *\n * Positioning is deliberately split. A free-floating window is placed by `left`/`top`, which\n * are physical. An **anchored** window is placed by CSS *logical* properties\n * (`insetInlineStart`/`insetInlineEnd`) driven by the element's own `dir`, so switching\n * reading direction mirrors it with no JavaScript and no stored physical coordinates — which\n * is what lets a layout saved in one direction restore correctly in the other.\n */\nimport { computed } from 'vue'\nimport type { CSSProperties } from 'vue'\nimport type { FloatingWindow } from '../types'\nimport { useWorkspace } from '../composables/useWorkspace'\nimport { useDragDock } from '../composables/useDragDock'\nimport { buildPanelMenu } from '../core/panelMenu'\nimport { computeResizedRect, startPointerDrag } from '../core/dragResize'\nimport type { ResizeDir } from '../core/dragResize'\nimport VddPanelSlot from './VddPanelSlot.vue'\n\nconst props = defineProps<{\n window: FloatingWindow\n /** The workspace's measured size, for the clamps. */\n viewport: { width: number; height: number }\n}>()\n\nconst ws = useWorkspace()\nconst drag = useDragDock()\n\n/** Inset from the workspace edge, and the gap between windows stacked in one corner. */\nconst CORNER_INSET = 8\nconst CORNER_GAP = 8\nconst HANDLES: ResizeDir[] = ['n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw']\n\nconst panel = computed(() => ws.state.panels[props.window.id])\nconst options = computed(() => (panel.value ? ws.registry.get(panel.value.component)?.defaultOptions ?? {} : {}))\nconst isFocused = computed(() => ws.state.activePanelId === props.window.id)\nconst isDragged = computed(() => ws.state.draggedPanelId === props.window.id)\nconst px = (v: number | string) => (typeof v === 'number' ? `${v}px` : v)\n\n/** Windows sharing this corner, in stack order. */\nconst stack = computed(() =>\n ws.state.floating.filter(w => w.anchor === props.window.anchor && !w.maximized))\n\n/** How far along the block axis this window sits, clearing whatever is stacked before it. */\nconst stackOffset = computed(() => {\n if (!props.window.anchor) return 0\n const index = stack.value.findIndex(w => w.id === props.window.id)\n let offset = CORNER_INSET\n for (let i = 0; i < index; i++) {\n const h = stack.value[i]!.height\n offset += (typeof h === 'number' ? h : Number.parseFloat(String(h))) + CORNER_GAP\n }\n return offset\n})\n\nconst style = computed<CSSProperties>(() => {\n const w = props.window\n const base: CSSProperties = {\n position: 'absolute',\n zIndex: w.z,\n pointerEvents: isDragged.value ? 'none' : 'auto',\n }\n if (w.maximized) return { ...base, left: 0, top: 0, width: '100%', height: '100%' }\n\n if (w.anchor) {\n const isTop = w.anchor.startsWith('top')\n const isEnd = w.anchor.endsWith('-right')\n return {\n ...base,\n [isEnd ? 'insetInlineEnd' : 'insetInlineStart']: `${CORNER_INSET}px`,\n [isTop ? 'top' : 'bottom']: `${stackOffset.value}px`,\n width: px(w.width),\n height: px(w.height),\n transition: isDragged.value ? 'none' : 'top 0.2s ease, bottom 0.2s ease',\n }\n }\n return { ...base, left: px(w.x), top: px(w.y), width: px(w.width), height: px(w.height) }\n})\n\nconst contributed = computed(() => ws.panelMenuItems(props.window.id))\n\nfunction openMenu(event: MouseEvent | PointerEvent, onlyContributed = false): void {\n const items = onlyContributed\n ? contributed.value\n : buildPanelMenu(ws, props.window.id, options.value, {\n float: () => ws.floatPanel(props.window.id),\n minimize: () => ws.minimizePanel(props.window.id),\n close: () => ws.requestClosePanel(props.window.id),\n restore: () => ws.restorePanel(props.window.id),\n maximize: () => ws.maximizePanel(props.window.id),\n })\n if (items.length === 0) return\n ws.showContextMenu({ event, items })\n}\n\nfunction onTitlePointerDown(event: PointerEvent): void {\n if (options.value.canDrag === false || props.window.maximized) return\n if (event.button !== 0) return\n event.preventDefault()\n ws.focusPanel(props.window.id)\n\n const el = (event.currentTarget as HTMLElement).closest('.vdd-floating-window') as HTMLElement | null\n const startX = el?.offsetLeft ?? 0\n const startY = el?.offsetTop ?? 0\n const originClientX = event.clientX\n const originClientY = event.clientY\n let moved = false\n\n startPointerDrag({\n element: event.currentTarget as HTMLElement,\n pointerId: event.pointerId,\n startClientX: event.clientX,\n startClientY: event.clientY,\n captureStart: () => ({ x: startX, y: startY }),\n activeClasses: [{ el: document.body, classes: ['vdd-dragging-active'] }],\n onMove: (dx, dy, start) => {\n // A window dragged over a drop target docks there on release, exactly like a tab — so\n // the gesture is registered with the drag machine, which makes the zones appear.\n if (!moved) { moved = true; drag?.beginDrag(props.window.id) }\n // Dragging a pinned window unpins it: a corner anchor and a free position are mutually\n // exclusive, and the user has just chosen the position.\n ws.updateFloatingPosition(props.window.id, { x: start.x + dx, y: start.y + dy, anchor: null })\n // `startPointerDrag` holds pointer capture, so the zones get no hover events: the armed\n // target has to be resolved by hit-testing the pointer instead.\n drag?.trackPointer(originClientX + dx, originClientY + dy)\n },\n onEnd: () => {\n if (!moved || !drag) return\n const armed = drag.zone.value ?? drag.edge.value ?? drag.corner.value ?? drag.tab.value\n // Released over nothing: the window simply stays where the drag left it.\n if (armed) drag.finishDrag(props.window.id, new PointerEvent('pointerup'))\n else drag.cancel()\n },\n })\n}\n\nfunction onHandlePointerDown(dir: ResizeDir, event: PointerEvent): void {\n if (props.window.maximized) return\n if (event.button !== 0) return\n event.preventDefault()\n event.stopPropagation()\n ws.focusPanel(props.window.id)\n\n const el = (event.currentTarget as HTMLElement).closest('.vdd-floating-window') as HTMLElement | null\n const start = {\n x: el?.offsetLeft ?? 0,\n y: el?.offsetTop ?? 0,\n w: el?.offsetWidth ?? 400,\n h: el?.offsetHeight ?? 300,\n }\n\n startPointerDrag({\n element: event.currentTarget as HTMLElement,\n pointerId: event.pointerId,\n startClientX: event.clientX,\n startClientY: event.clientY,\n captureStart: () => start,\n activeClasses: [{ el: document.body, classes: ['vdd-resizing-active'] }],\n // No maxW/maxH/minX/minY: a workspace window may grow past the viewport and be dragged\n // off-screen, matching rdd. The clamp on workspace *resize* pulls it back into reach.\n onMove: (dx, dy, from) => {\n const r = computeResizedRect(dir, dx, dy, from, { minW: 200, minH: 150 })\n ws.updateFloatingPosition(props.window.id, { x: r.x, y: r.y, width: r.w, height: r.h })\n },\n })\n}\n</script>\n\n<template>\n <div\n v-if=\"panel\"\n class=\"vdd-floating-window\"\n :class=\"[{\n 'vdd-maximized': window.maximized,\n 'vdd-window-focused': isFocused,\n }, ws.classes.window]\"\n :data-vdd-window=\"window.id\"\n :dir=\"ws.state.dir\"\n :style=\"style\"\n @pointerdown.capture=\"ws.focusPanel(window.id)\"\n >\n <div\n class=\"vdd-floating-window-titlebar\"\n :data-vdd-titlebar=\"window.id\"\n :style=\"{ cursor: window.maximized || options.canDrag === false ? 'default' : 'move' }\"\n @pointerdown=\"onTitlePointerDown\"\n @contextmenu.prevent=\"openMenu($event)\"\n @dblclick=\"ws.maximizePanel(window.id)\"\n >\n <span class=\"vdd-floating-window-title\">\n <span v-if=\"options.icon\" class=\"vdd-window-title-icon\"><component :is=\"options.icon\" /></span>\n <span>{{ ws.format(panel.title) }}{{ panel.dirty ? ' *' : '' }}</span>\n </span>\n <div class=\"vdd-titlebar-actions\" @pointerdown.stop>\n <button\n v-if=\"contributed.length > 0\"\n type=\"button\"\n class=\"vdd-custom-tab-btn vdd-btn-more-actions\"\n :data-vdd-more=\"window.id\"\n :title=\"ws.format(ws.messages.moreActions)\"\n @click=\"openMenu($event, true)\"\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"currentColor\" style=\"display: block\">\n <circle cx=\"12\" cy=\"5\" r=\"2\" /><circle cx=\"12\" cy=\"12\" r=\"2\" /><circle cx=\"12\" cy=\"19\" r=\"2\" />\n </svg>\n </button>\n <button\n type=\"button\"\n class=\"vdd-custom-tab-btn vdd-btn-maximize-tab\"\n :data-vdd-maximize=\"window.id\"\n :title=\"ws.format(window.maximized ? ws.messages.restoreSize : ws.messages.maximize)\"\n @click=\"ws.maximizePanel(window.id)\"\n >\n <svg width=\"10\" height=\"10\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\">\n <rect x=\"4\" y=\"4\" width=\"16\" height=\"16\" rx=\"1.5\" />\n </svg>\n </button>\n <button\n v-if=\"options.canMinimize !== false\"\n type=\"button\"\n class=\"vdd-custom-tab-btn vdd-btn-minimize-tab\"\n :data-vdd-minimize=\"window.id\"\n :title=\"ws.format(ws.messages.minimize)\"\n @click=\"ws.minimizePanel(window.id)\"\n >\n <svg width=\"10\" height=\"10\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"3\">\n <path d=\"M5 12h14\" />\n </svg>\n </button>\n <button\n v-if=\"options.canClose !== false\"\n type=\"button\"\n class=\"vdd-custom-tab-btn vdd-btn-close-tab\"\n :data-vdd-close-window=\"window.id\"\n :title=\"ws.format(ws.messages.close)\"\n @click=\"ws.requestClosePanel(window.id)\"\n >\n <svg width=\"10\" height=\"10\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"3\">\n <path d=\"M18 6L6 18M6 6l12 12\" />\n </svg>\n </button>\n </div>\n </div>\n\n <div class=\"vdd-floating-window-body\" :class=\"ws.classes.windowBody\">\n <VddPanelSlot :panel-id=\"window.id\" />\n </div>\n\n <template v-if=\"!window.maximized\">\n <div\n v-for=\"dir in HANDLES\"\n :key=\"dir\"\n class=\"vdd-resize-handle\"\n :class=\"`vdd-resize-${dir}`\"\n :data-vdd-handle=\"`${window.id}:${dir}`\"\n @pointerdown=\"onHandlePointerDown(dir, $event)\"\n />\n </template>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/**\n * Workspace-level drop targets: the four outer edges, and the four corners.\n *\n * An edge dock creates a full-width or full-height row. A corner **floats** the panel pinned\n * to that corner — two different outcomes from adjacent gestures, which is why a corner\n * disarms the edge it overlaps.\n */\nimport { computed } from 'vue'\nimport type { FloatAnchor, SplitDirection } from '../types'\nimport { useDragDock } from '../composables/useDragDock'\nimport { useWorkspace } from '../composables/useWorkspace'\n\nconst drag = useDragDock()\nconst ws = useWorkspace()\n\nconst EDGES: SplitDirection[] = ['left', 'right', 'top', 'bottom']\nconst CORNERS: FloatAnchor[] = ['top-left', 'top-right', 'bottom-left', 'bottom-right']\n\n/** The share of the workspace an edge dock would take. */\nconst preview = computed(() => {\n const armed = drag?.edge.value\n if (!armed) return null\n const share = `${ws.state.edgeSplitRatio * 100}%`\n switch (armed) {\n case 'left': return { left: '0', top: '0', bottom: '0', width: share }\n case 'right': return { right: '0', top: '0', bottom: '0', width: share }\n case 'top': return { top: '0', left: '0', right: '0', height: share }\n default: return { bottom: '0', left: '0', right: '0', height: share }\n }\n})\n</script>\n\n<template>\n <div\n v-for=\"e in EDGES\"\n :key=\"e\"\n class=\"vdd-workspace-edge-trigger\"\n :class=\"`vdd-edge-trigger-${e}`\"\n :data-vdd-edge=\"e\"\n @pointerenter=\"drag?.hoverEdge(e)\"\n @pointerleave=\"drag?.hoverEdge(null)\"\n />\n\n <div\n v-for=\"c in CORNERS\"\n :key=\"c\"\n class=\"vdd-corner-zone\"\n :class=\"[`vdd-corner-zone--${c}`, { 'vdd-corner-zone--hovered': drag?.corner.value === c }]\"\n :data-vdd-corner=\"c\"\n aria-hidden=\"true\"\n @pointerenter=\"drag?.hoverCorner(c)\"\n @pointerleave=\"drag?.hoverCorner(null)\"\n />\n\n <div v-if=\"preview\" class=\"vdd-workspace-edge-preview\" :style=\"preview\" />\n</template>\n","<script setup lang=\"ts\">\n/**\n * Workspace-level drop targets: the four outer edges, and the four corners.\n *\n * An edge dock creates a full-width or full-height row. A corner **floats** the panel pinned\n * to that corner — two different outcomes from adjacent gestures, which is why a corner\n * disarms the edge it overlaps.\n */\nimport { computed } from 'vue'\nimport type { FloatAnchor, SplitDirection } from '../types'\nimport { useDragDock } from '../composables/useDragDock'\nimport { useWorkspace } from '../composables/useWorkspace'\n\nconst drag = useDragDock()\nconst ws = useWorkspace()\n\nconst EDGES: SplitDirection[] = ['left', 'right', 'top', 'bottom']\nconst CORNERS: FloatAnchor[] = ['top-left', 'top-right', 'bottom-left', 'bottom-right']\n\n/** The share of the workspace an edge dock would take. */\nconst preview = computed(() => {\n const armed = drag?.edge.value\n if (!armed) return null\n const share = `${ws.state.edgeSplitRatio * 100}%`\n switch (armed) {\n case 'left': return { left: '0', top: '0', bottom: '0', width: share }\n case 'right': return { right: '0', top: '0', bottom: '0', width: share }\n case 'top': return { top: '0', left: '0', right: '0', height: share }\n default: return { bottom: '0', left: '0', right: '0', height: share }\n }\n})\n</script>\n\n<template>\n <div\n v-for=\"e in EDGES\"\n :key=\"e\"\n class=\"vdd-workspace-edge-trigger\"\n :class=\"`vdd-edge-trigger-${e}`\"\n :data-vdd-edge=\"e\"\n @pointerenter=\"drag?.hoverEdge(e)\"\n @pointerleave=\"drag?.hoverEdge(null)\"\n />\n\n <div\n v-for=\"c in CORNERS\"\n :key=\"c\"\n class=\"vdd-corner-zone\"\n :class=\"[`vdd-corner-zone--${c}`, { 'vdd-corner-zone--hovered': drag?.corner.value === c }]\"\n :data-vdd-corner=\"c\"\n aria-hidden=\"true\"\n @pointerenter=\"drag?.hoverCorner(c)\"\n @pointerleave=\"drag?.hoverCorner(null)\"\n />\n\n <div v-if=\"preview\" class=\"vdd-workspace-edge-preview\" :style=\"preview\" />\n</template>\n","<script setup lang=\"ts\">\n/**\n * The label that follows the pointer during a tab drag.\n *\n * Only for a tab: dragging a floating window moves the window itself, so a ghost would be\n * a second thing to look at.\n */\nimport { computed } from 'vue'\nimport { useDragDock } from '../composables/useDragDock'\nimport { useWorkspace } from '../composables/useWorkspace'\n\nconst drag = useDragDock()\nconst ws = useWorkspace()\n\nconst label = computed(() => {\n const id = drag?.draggedId.value\n if (!id) return ''\n const panel = ws.state.panels[id]\n return panel ? ws.format(panel.title) : id\n})\n\nconst isTabDrag = computed(() =>\n !!drag?.draggedId.value && !ws.state.floating.some(w => w.id === drag.draggedId.value))\n</script>\n\n<template>\n <div\n v-if=\"isTabDrag\"\n class=\"vdd-drag-ghost-tab\"\n data-vdd-ghost\n :style=\"{ left: `${(drag?.pointer.value.x ?? 0) + 12}px`, top: `${(drag?.pointer.value.y ?? 0) + 12}px` }\"\n >{{ label }}</div>\n</template>\n","<script setup lang=\"ts\">\n/**\n * The label that follows the pointer during a tab drag.\n *\n * Only for a tab: dragging a floating window moves the window itself, so a ghost would be\n * a second thing to look at.\n */\nimport { computed } from 'vue'\nimport { useDragDock } from '../composables/useDragDock'\nimport { useWorkspace } from '../composables/useWorkspace'\n\nconst drag = useDragDock()\nconst ws = useWorkspace()\n\nconst label = computed(() => {\n const id = drag?.draggedId.value\n if (!id) return ''\n const panel = ws.state.panels[id]\n return panel ? ws.format(panel.title) : id\n})\n\nconst isTabDrag = computed(() =>\n !!drag?.draggedId.value && !ws.state.floating.some(w => w.id === drag.draggedId.value))\n</script>\n\n<template>\n <div\n v-if=\"isTabDrag\"\n class=\"vdd-drag-ghost-tab\"\n data-vdd-ghost\n :style=\"{ left: `${(drag?.pointer.value.x ?? 0) + 12}px`, top: `${(drag?.pointer.value.y ?? 0) + 12}px` }\"\n >{{ label }}</div>\n</template>\n","<script setup lang=\"ts\">\n/**\n * The hover preview for a minimised panel.\n *\n * Not a screenshot — **the panel itself**, moved into a scaled box. The panel is still\n * mounted and still running, so what you see is live: a map still renders, a video still\n * plays, a spinner still spins. That is only possible because the panel's DOM is never\n * re-created (see src/core/panelDom.ts).\n *\n * Teleported to `document.body` and positioned `fixed`, so it is never clipped by the\n * taskbar's own overflow.\n *\n * The pointer handlers are on the tooltip's own element, not left to fall through from the\n * component: this component's root is a `<Teleport>`, which is not a real element, so a\n * listener bound on the component tag attaches to nothing. Without them, leaving the icon\n * starts a dismissal that nothing cancels, the preview disappears before the pointer\n * arrives, and it can never be clicked at all.\n */\nimport { computed, onBeforeUnmount, watch } from 'vue'\nimport type { PanelDomCache } from '../core/panelDom'\nimport { useWorkspace } from '../composables/useWorkspace'\n\nconst props = defineProps<{\n panelId: string\n /** The icon's on-screen rect, so the preview can sit above it. */\n anchor: { left: number; top: number; width: number }\n cache: PanelDomCache\n}>()\n\nconst emit = defineEmits<{\n restore: []\n close: []\n dismiss: []\n /** The pointer is over the preview: whatever dismissal is pending must be cancelled. */\n keep: []\n}>()\n\nconst ws = useWorkspace()\n\n/** The thumbnail never exceeds this box; the panel's real aspect ratio is preserved. */\nconst MAX_W = 220\nconst MAX_H = 140\n\nconst panel = computed(() => ws.state.panels[props.panelId])\nconst options = computed(() => (panel.value ? ws.registry.get(panel.value.component)?.defaultOptions ?? {} : {}))\nconst live = computed(() => options.value.disableLivePreview !== true)\n\nconst source = computed(() => props.cache.sizeOf(props.panelId))\nconst scale = computed(() => Math.min(MAX_W / source.value.width, MAX_H / source.value.height))\nconst frame = computed(() => ({\n width: `${Math.round(source.value.width * scale.value)}px`,\n height: `${Math.round(source.value.height * scale.value)}px`,\n}))\nconst inner = computed(() => ({\n width: `${source.value.width}px`,\n height: `${source.value.height}px`,\n transform: `scale(${scale.value})`,\n transformOrigin: 'top left',\n position: 'absolute' as const,\n top: '0',\n left: '0',\n ['--vdd-preview-scale' as string]: String(scale.value),\n}))\n\n/** First letter of the title, for a panel that opts out of a live preview. */\nconst initial = computed(() => {\n const text = ws.format(panel.value?.title) || props.panelId\n return (Array.from(text)[0] ?? 'P').toUpperCase()\n})\n\n/**\n * Move the panel into the preview while it is open, and back to the off-screen store when it\n * closes. `refocus: false` — hovering a taskbar icon must never steal the caret from whatever\n * the user is actually typing in.\n */\nlet mine: HTMLElement | null = null\n\n/**\n * Take the panel when this element appears; hand it back when it goes.\n *\n * Vue calls a function ref with `null` on teardown, so this runs for both. The `null` case\n * must go through the ownership check rather than parking the panel outright: restoring from\n * the preview gives the panel a slot *and* closes the preview, and if the teardown parks it\n * unconditionally the panel ends up off-screen while a slot for it sits empty.\n */\nfunction host(el: unknown): void {\n if (!live.value) return\n const element = (el as HTMLElement | null) ?? null\n if (element) {\n mine = element\n props.cache.moveTo(props.panelId, element, { refocus: false })\n } else {\n releaseIfStillMine(props.panelId)\n }\n}\n\n/**\n * Give the panel back — but only if this preview still has it.\n *\n * Restoring from the preview closes the preview *and* gives the panel a slot, and the order\n * is not guaranteed. Parking it unconditionally meant the closing preview could steal the\n * panel back from the slot that had just claimed it, leaving a docked panel with nowhere to\n * be shown. Same ownership check `VddPanelSlot` makes for the same reason.\n */\nfunction releaseIfStillMine(id: string): void {\n if (!live.value) return\n if (mine === null || props.cache.hostOf(id) !== mine) { mine = null; return }\n mine = null\n props.cache.moveTo(id, null, { refocus: false })\n}\n\nwatch(() => props.panelId, (_id, previous) => {\n if (previous !== undefined) releaseIfStillMine(previous)\n})\n\nonBeforeUnmount(() => releaseIfStillMine(props.panelId))\n</script>\n\n<template>\n <Teleport to=\"body\">\n <div\n class=\"vdd-taskbar-item-tooltip\"\n data-vdd-preview\n :dir=\"ws.state.dir\"\n :style=\"{\n position: 'fixed',\n left: `${anchor.left + anchor.width / 2}px`,\n top: `${anchor.top - 8}px`,\n transform: 'translateX(-50%) translateY(-100%)',\n }\"\n @click=\"emit('restore')\"\n @pointerenter=\"emit('keep')\"\n @pointerleave=\"emit('dismiss')\"\n >\n <div class=\"vdd-tooltip-header-row\">\n <span class=\"vdd-tooltip-title-text vdd-text-truncate\">\n {{ ws.format(panel?.title) }}{{ panel?.dirty ? ' *' : '' }}\n </span>\n <span\n class=\"vdd-tooltip-close-x\"\n :data-vdd-preview-close=\"panelId\"\n :title=\"ws.format(ws.messages.closePanel)\"\n @click.stop=\"emit('close')\"\n >\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\">\n <path d=\"M18 6L6 18M6 6l12 12\" />\n </svg>\n </span>\n </div>\n\n <div class=\"vdd-taskbar-item-preview-frame\" :style=\"frame\">\n <!--\n The panel's own element is moved in here by `host()`. Nothing is rendered inside\n this div by Vue — if it were, the live panel would have to be duplicated, and a\n duplicate is exactly what a preview must not be.\n -->\n <div v-if=\"live\" :ref=\"host\" class=\"vdd-taskbar-item-preview-host\" :style=\"inner\" />\n <div v-else class=\"vdd-taskbar-item-preview-letter\">{{ initial }}</div>\n </div>\n </div>\n </Teleport>\n</template>\n","<script setup lang=\"ts\">\n/**\n * The hover preview for a minimised panel.\n *\n * Not a screenshot — **the panel itself**, moved into a scaled box. The panel is still\n * mounted and still running, so what you see is live: a map still renders, a video still\n * plays, a spinner still spins. That is only possible because the panel's DOM is never\n * re-created (see src/core/panelDom.ts).\n *\n * Teleported to `document.body` and positioned `fixed`, so it is never clipped by the\n * taskbar's own overflow.\n *\n * The pointer handlers are on the tooltip's own element, not left to fall through from the\n * component: this component's root is a `<Teleport>`, which is not a real element, so a\n * listener bound on the component tag attaches to nothing. Without them, leaving the icon\n * starts a dismissal that nothing cancels, the preview disappears before the pointer\n * arrives, and it can never be clicked at all.\n */\nimport { computed, onBeforeUnmount, watch } from 'vue'\nimport type { PanelDomCache } from '../core/panelDom'\nimport { useWorkspace } from '../composables/useWorkspace'\n\nconst props = defineProps<{\n panelId: string\n /** The icon's on-screen rect, so the preview can sit above it. */\n anchor: { left: number; top: number; width: number }\n cache: PanelDomCache\n}>()\n\nconst emit = defineEmits<{\n restore: []\n close: []\n dismiss: []\n /** The pointer is over the preview: whatever dismissal is pending must be cancelled. */\n keep: []\n}>()\n\nconst ws = useWorkspace()\n\n/** The thumbnail never exceeds this box; the panel's real aspect ratio is preserved. */\nconst MAX_W = 220\nconst MAX_H = 140\n\nconst panel = computed(() => ws.state.panels[props.panelId])\nconst options = computed(() => (panel.value ? ws.registry.get(panel.value.component)?.defaultOptions ?? {} : {}))\nconst live = computed(() => options.value.disableLivePreview !== true)\n\nconst source = computed(() => props.cache.sizeOf(props.panelId))\nconst scale = computed(() => Math.min(MAX_W / source.value.width, MAX_H / source.value.height))\nconst frame = computed(() => ({\n width: `${Math.round(source.value.width * scale.value)}px`,\n height: `${Math.round(source.value.height * scale.value)}px`,\n}))\nconst inner = computed(() => ({\n width: `${source.value.width}px`,\n height: `${source.value.height}px`,\n transform: `scale(${scale.value})`,\n transformOrigin: 'top left',\n position: 'absolute' as const,\n top: '0',\n left: '0',\n ['--vdd-preview-scale' as string]: String(scale.value),\n}))\n\n/** First letter of the title, for a panel that opts out of a live preview. */\nconst initial = computed(() => {\n const text = ws.format(panel.value?.title) || props.panelId\n return (Array.from(text)[0] ?? 'P').toUpperCase()\n})\n\n/**\n * Move the panel into the preview while it is open, and back to the off-screen store when it\n * closes. `refocus: false` — hovering a taskbar icon must never steal the caret from whatever\n * the user is actually typing in.\n */\nlet mine: HTMLElement | null = null\n\n/**\n * Take the panel when this element appears; hand it back when it goes.\n *\n * Vue calls a function ref with `null` on teardown, so this runs for both. The `null` case\n * must go through the ownership check rather than parking the panel outright: restoring from\n * the preview gives the panel a slot *and* closes the preview, and if the teardown parks it\n * unconditionally the panel ends up off-screen while a slot for it sits empty.\n */\nfunction host(el: unknown): void {\n if (!live.value) return\n const element = (el as HTMLElement | null) ?? null\n if (element) {\n mine = element\n props.cache.moveTo(props.panelId, element, { refocus: false })\n } else {\n releaseIfStillMine(props.panelId)\n }\n}\n\n/**\n * Give the panel back — but only if this preview still has it.\n *\n * Restoring from the preview closes the preview *and* gives the panel a slot, and the order\n * is not guaranteed. Parking it unconditionally meant the closing preview could steal the\n * panel back from the slot that had just claimed it, leaving a docked panel with nowhere to\n * be shown. Same ownership check `VddPanelSlot` makes for the same reason.\n */\nfunction releaseIfStillMine(id: string): void {\n if (!live.value) return\n if (mine === null || props.cache.hostOf(id) !== mine) { mine = null; return }\n mine = null\n props.cache.moveTo(id, null, { refocus: false })\n}\n\nwatch(() => props.panelId, (_id, previous) => {\n if (previous !== undefined) releaseIfStillMine(previous)\n})\n\nonBeforeUnmount(() => releaseIfStillMine(props.panelId))\n</script>\n\n<template>\n <Teleport to=\"body\">\n <div\n class=\"vdd-taskbar-item-tooltip\"\n data-vdd-preview\n :dir=\"ws.state.dir\"\n :style=\"{\n position: 'fixed',\n left: `${anchor.left + anchor.width / 2}px`,\n top: `${anchor.top - 8}px`,\n transform: 'translateX(-50%) translateY(-100%)',\n }\"\n @click=\"emit('restore')\"\n @pointerenter=\"emit('keep')\"\n @pointerleave=\"emit('dismiss')\"\n >\n <div class=\"vdd-tooltip-header-row\">\n <span class=\"vdd-tooltip-title-text vdd-text-truncate\">\n {{ ws.format(panel?.title) }}{{ panel?.dirty ? ' *' : '' }}\n </span>\n <span\n class=\"vdd-tooltip-close-x\"\n :data-vdd-preview-close=\"panelId\"\n :title=\"ws.format(ws.messages.closePanel)\"\n @click.stop=\"emit('close')\"\n >\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\">\n <path d=\"M18 6L6 18M6 6l12 12\" />\n </svg>\n </span>\n </div>\n\n <div class=\"vdd-taskbar-item-preview-frame\" :style=\"frame\">\n <!--\n The panel's own element is moved in here by `host()`. Nothing is rendered inside\n this div by Vue — if it were, the live panel would have to be duplicated, and a\n duplicate is exactly what a preview must not be.\n -->\n <div v-if=\"live\" :ref=\"host\" class=\"vdd-taskbar-item-preview-host\" :style=\"inner\" />\n <div v-else class=\"vdd-taskbar-item-preview-letter\">{{ initial }}</div>\n </div>\n </div>\n </Teleport>\n</template>\n","<script setup lang=\"ts\">\n/**\n * The taskbar: one icon per minimised panel, with a live hover preview.\n *\n * Three visibility modes:\n * - `always` — a permanent strip.\n * - `compact` — present only while something is minimised.\n * - `autohide` — an overlay that collapses to an 8px peek strip and expands on hover. It\n * also flashes open for two seconds when a panel is minimised, so the panel\n * is seen going somewhere rather than just vanishing.\n *\n * On touch there is no hover, so the first tap opens the preview and a second restores.\n */\nimport { computed, onBeforeUnmount, ref, shallowRef, watch } from 'vue'\nimport type { PanelDomCache } from '../core/panelDom'\nimport { useWorkspace } from '../composables/useWorkspace'\nimport { CANCEL_MOVE_PX, LONG_PRESS_MS } from '../composables/useDragDock'\nimport VddTaskbarPreview from './VddTaskbarPreview.vue'\n\nexport type TaskbarVisibility = 'always' | 'compact' | 'autohide'\n\nconst props = withDefaults(defineProps<{\n visibility?: TaskbarVisibility\n cache: PanelDomCache\n /** Fallback icon for panels that register none. */\n defaultIcon?: unknown\n}>(), { visibility: 'always' })\n\nconst emit = defineEmits<{\n /** A long press or right-click on an icon: the host opens a menu for it (M8). */\n contextMenu: [panelId: string, event: PointerEvent | MouseEvent]\n}>()\n\nconst ws = useWorkspace()\n\n/** More than this many icons and the strip gets scroll arrows. */\nconst SCROLL_AFTER = 4\nconst COLLAPSE_DELAY_MS = 400\nconst FLASH_MS = 2000\n\nconst strip = shallowRef<HTMLElement | null>(null)\nconst expanded = ref(false)\nconst hovered = ref<{ id: string; anchor: { left: number; top: number; width: number }; fromTouch: boolean } | null>(null)\nlet collapseTimer: ReturnType<typeof setTimeout> | undefined\nlet dismissTimer: ReturnType<typeof setTimeout> | undefined\n\nconst items = computed(() => ws.state.minimized.map(entry => ({\n ...entry,\n options: ws.registry.get(entry.component)?.defaultOptions ?? {},\n})))\n\nconst visible = computed(() => props.visibility === 'always' || items.value.length > 0)\nconst scrollable = computed(() => items.value.length > SCROLL_AFTER)\n\nfunction scrollBy(direction: -1 | 1): void {\n strip.value?.scrollBy({ left: direction * 150, behavior: 'smooth' })\n}\n\n// ── autohide ───────────────────────────────────────────────────────────────\nconst expand = () => { clearTimeout(collapseTimer); expanded.value = true }\nconst scheduleCollapse = () => {\n clearTimeout(collapseTimer)\n collapseTimer = setTimeout(() => { expanded.value = false }, COLLAPSE_DELAY_MS)\n}\n\nwatch(() => ws.state.minimized.length, (now, before) => {\n if (props.visibility !== 'autohide' || now <= (before ?? 0)) return\n expand()\n collapseTimer = setTimeout(() => { expanded.value = false }, FLASH_MS)\n})\n\n// ── preview ────────────────────────────────────────────────────────────────\nconst anchorOf = (el: HTMLElement) => {\n const r = el.getBoundingClientRect()\n return { left: r.left, top: r.top, width: r.width }\n}\n\nfunction showPreview(id: string, el: HTMLElement, fromTouch = false): void {\n clearTimeout(dismissTimer)\n hovered.value = { id, anchor: anchorOf(el), fromTouch }\n}\nconst dismissPreview = () => {\n // A short grace period so the pointer can travel from the icon to the preview without it\n // closing on the way.\n clearTimeout(dismissTimer)\n dismissTimer = setTimeout(() => { hovered.value = null }, 150)\n}\nconst keepPreview = () => clearTimeout(dismissTimer)\n\nfunction onIconEnter(id: string, event: PointerEvent): void {\n if (event.pointerType === 'touch') return\n const el = event.currentTarget as HTMLElement\n // A pointerenter can fire for a stationary cursor when the strip scrolls or reflows under\n // it; ignoring an enter whose coordinates are outside the icon keeps stray previews away.\n const r = el.getBoundingClientRect()\n if (event.clientX < r.left || event.clientX > r.right || event.clientY < r.top || event.clientY > r.bottom) return\n showPreview(id, el, false)\n}\n\nfunction restore(id: string): void {\n hovered.value = null\n ws.restorePanel(id)\n}\n\nfunction onIconClick(id: string, event: MouseEvent): void {\n // On touch the first tap opens the preview; only a second tap restores. The click that\n // follows a tap is ignored here so the two gestures do not collide.\n if (hovered.value?.fromTouch) return\n void event\n restore(id)\n}\n\n/** Touch: tap toggles the preview, long press asks the host for a context menu. */\nfunction onIconPointerDown(id: string, event: PointerEvent): void {\n if (event.pointerType !== 'touch') return\n const el = event.currentTarget as HTMLElement\n const startX = event.clientX\n const startY = event.clientY\n let cancelled = false\n\n const cleanup = () => {\n clearTimeout(timer)\n el.removeEventListener('pointermove', onMove)\n el.removeEventListener('pointerup', onUp)\n el.removeEventListener('pointercancel', cleanup)\n }\n const onMove = (e: PointerEvent) => {\n if (Math.hypot(e.clientX - startX, e.clientY - startY) > CANCEL_MOVE_PX) { cancelled = true; cleanup() }\n }\n const onUp = () => {\n cleanup()\n if (cancelled) return\n if (hovered.value?.id === id) restore(id)\n else showPreview(id, el, true)\n }\n const timer = setTimeout(() => {\n if (cancelled) return\n cleanup()\n navigator.vibrate?.(10)\n emit('contextMenu', id, event)\n }, LONG_PRESS_MS)\n\n el.addEventListener('pointermove', onMove)\n el.addEventListener('pointerup', onUp)\n el.addEventListener('pointercancel', cleanup)\n}\n\n// A preview must not outlive its panel: restoring or closing from elsewhere closes it.\nwatch(() => ws.state.minimized.map(m => m.id).join(','), (ids) => {\n if (hovered.value && !ids.split(',').includes(hovered.value.id)) hovered.value = null\n})\n\nonBeforeUnmount(() => { clearTimeout(collapseTimer); clearTimeout(dismissTimer) })\n</script>\n\n<template>\n <div\n v-if=\"visible\"\n class=\"vdd-taskbar-footer-container\"\n :class=\"[\n `vdd-taskbar-mode-${visibility}`,\n { 'vdd-taskbar-expanded': visibility === 'autohide' && expanded },\n ]\"\n data-vdd-taskbar\n :data-vdd-taskbar-mode=\"visibility\"\n @pointerenter=\"visibility === 'autohide' && expand()\"\n @pointerleave=\"visibility === 'autohide' && scheduleCollapse()\"\n >\n <div v-if=\"visibility === 'autohide'\" class=\"vdd-taskbar-peek-handle\" data-vdd-peek />\n\n <button\n v-if=\"scrollable\"\n type=\"button\"\n class=\"vdd-taskbar-nav-btn\"\n data-vdd-taskbar-scroll=\"left\"\n :aria-label=\"ws.format(ws.messages.scrollTabsLeft)\"\n @click=\"scrollBy(-1)\"\n >◀</button>\n\n <div ref=\"strip\" class=\"vdd-taskbar-items-container\">\n <div\n v-for=\"item in items\"\n :key=\"item.id\"\n class=\"vdd-taskbar-glassmorphic-item\"\n :data-vdd-taskbar-item=\"item.id\"\n role=\"button\"\n :title=\"ws.format(item.title)\"\n @click=\"onIconClick(item.id, $event)\"\n @contextmenu.prevent=\"emit('contextMenu', item.id, $event)\"\n @pointerdown=\"onIconPointerDown(item.id, $event)\"\n @pointerenter=\"onIconEnter(item.id, $event)\"\n @pointerleave=\"dismissPreview()\"\n >\n <span class=\"vdd-taskbar-item-icon\">\n <component :is=\"item.options.icon ?? defaultIcon\" v-if=\"item.options.icon ?? defaultIcon\" />\n <svg v-else width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <rect x=\"3\" y=\"3\" width=\"7\" height=\"9\" rx=\"1\" />\n <rect x=\"14\" y=\"3\" width=\"7\" height=\"5\" rx=\"1\" />\n <rect x=\"14\" y=\"12\" width=\"7\" height=\"9\" rx=\"1\" />\n <rect x=\"3\" y=\"16\" width=\"7\" height=\"5\" rx=\"1\" />\n </svg>\n </span>\n </div>\n </div>\n\n <button\n v-if=\"scrollable\"\n type=\"button\"\n class=\"vdd-taskbar-nav-btn\"\n data-vdd-taskbar-scroll=\"right\"\n :aria-label=\"ws.format(ws.messages.scrollTabsRight)\"\n @click=\"scrollBy(1)\"\n >▶</button>\n\n <VddTaskbarPreview\n v-if=\"hovered\"\n :key=\"hovered.id\"\n :panel-id=\"hovered.id\"\n :anchor=\"hovered.anchor\"\n :cache=\"cache\"\n @keep=\"keepPreview()\"\n @restore=\"restore(hovered.id)\"\n @close=\"ws.requestClosePanel(hovered.id); hovered = null\"\n @dismiss=\"dismissPreview()\"\n />\n </div>\n</template>\n","<script setup lang=\"ts\">\n/**\n * The taskbar: one icon per minimised panel, with a live hover preview.\n *\n * Three visibility modes:\n * - `always` — a permanent strip.\n * - `compact` — present only while something is minimised.\n * - `autohide` — an overlay that collapses to an 8px peek strip and expands on hover. It\n * also flashes open for two seconds when a panel is minimised, so the panel\n * is seen going somewhere rather than just vanishing.\n *\n * On touch there is no hover, so the first tap opens the preview and a second restores.\n */\nimport { computed, onBeforeUnmount, ref, shallowRef, watch } from 'vue'\nimport type { PanelDomCache } from '../core/panelDom'\nimport { useWorkspace } from '../composables/useWorkspace'\nimport { CANCEL_MOVE_PX, LONG_PRESS_MS } from '../composables/useDragDock'\nimport VddTaskbarPreview from './VddTaskbarPreview.vue'\n\nexport type TaskbarVisibility = 'always' | 'compact' | 'autohide'\n\nconst props = withDefaults(defineProps<{\n visibility?: TaskbarVisibility\n cache: PanelDomCache\n /** Fallback icon for panels that register none. */\n defaultIcon?: unknown\n}>(), { visibility: 'always' })\n\nconst emit = defineEmits<{\n /** A long press or right-click on an icon: the host opens a menu for it (M8). */\n contextMenu: [panelId: string, event: PointerEvent | MouseEvent]\n}>()\n\nconst ws = useWorkspace()\n\n/** More than this many icons and the strip gets scroll arrows. */\nconst SCROLL_AFTER = 4\nconst COLLAPSE_DELAY_MS = 400\nconst FLASH_MS = 2000\n\nconst strip = shallowRef<HTMLElement | null>(null)\nconst expanded = ref(false)\nconst hovered = ref<{ id: string; anchor: { left: number; top: number; width: number }; fromTouch: boolean } | null>(null)\nlet collapseTimer: ReturnType<typeof setTimeout> | undefined\nlet dismissTimer: ReturnType<typeof setTimeout> | undefined\n\nconst items = computed(() => ws.state.minimized.map(entry => ({\n ...entry,\n options: ws.registry.get(entry.component)?.defaultOptions ?? {},\n})))\n\nconst visible = computed(() => props.visibility === 'always' || items.value.length > 0)\nconst scrollable = computed(() => items.value.length > SCROLL_AFTER)\n\nfunction scrollBy(direction: -1 | 1): void {\n strip.value?.scrollBy({ left: direction * 150, behavior: 'smooth' })\n}\n\n// ── autohide ───────────────────────────────────────────────────────────────\nconst expand = () => { clearTimeout(collapseTimer); expanded.value = true }\nconst scheduleCollapse = () => {\n clearTimeout(collapseTimer)\n collapseTimer = setTimeout(() => { expanded.value = false }, COLLAPSE_DELAY_MS)\n}\n\nwatch(() => ws.state.minimized.length, (now, before) => {\n if (props.visibility !== 'autohide' || now <= (before ?? 0)) return\n expand()\n collapseTimer = setTimeout(() => { expanded.value = false }, FLASH_MS)\n})\n\n// ── preview ────────────────────────────────────────────────────────────────\nconst anchorOf = (el: HTMLElement) => {\n const r = el.getBoundingClientRect()\n return { left: r.left, top: r.top, width: r.width }\n}\n\nfunction showPreview(id: string, el: HTMLElement, fromTouch = false): void {\n clearTimeout(dismissTimer)\n hovered.value = { id, anchor: anchorOf(el), fromTouch }\n}\nconst dismissPreview = () => {\n // A short grace period so the pointer can travel from the icon to the preview without it\n // closing on the way.\n clearTimeout(dismissTimer)\n dismissTimer = setTimeout(() => { hovered.value = null }, 150)\n}\nconst keepPreview = () => clearTimeout(dismissTimer)\n\nfunction onIconEnter(id: string, event: PointerEvent): void {\n if (event.pointerType === 'touch') return\n const el = event.currentTarget as HTMLElement\n // A pointerenter can fire for a stationary cursor when the strip scrolls or reflows under\n // it; ignoring an enter whose coordinates are outside the icon keeps stray previews away.\n const r = el.getBoundingClientRect()\n if (event.clientX < r.left || event.clientX > r.right || event.clientY < r.top || event.clientY > r.bottom) return\n showPreview(id, el, false)\n}\n\nfunction restore(id: string): void {\n hovered.value = null\n ws.restorePanel(id)\n}\n\nfunction onIconClick(id: string, event: MouseEvent): void {\n // On touch the first tap opens the preview; only a second tap restores. The click that\n // follows a tap is ignored here so the two gestures do not collide.\n if (hovered.value?.fromTouch) return\n void event\n restore(id)\n}\n\n/** Touch: tap toggles the preview, long press asks the host for a context menu. */\nfunction onIconPointerDown(id: string, event: PointerEvent): void {\n if (event.pointerType !== 'touch') return\n const el = event.currentTarget as HTMLElement\n const startX = event.clientX\n const startY = event.clientY\n let cancelled = false\n\n const cleanup = () => {\n clearTimeout(timer)\n el.removeEventListener('pointermove', onMove)\n el.removeEventListener('pointerup', onUp)\n el.removeEventListener('pointercancel', cleanup)\n }\n const onMove = (e: PointerEvent) => {\n if (Math.hypot(e.clientX - startX, e.clientY - startY) > CANCEL_MOVE_PX) { cancelled = true; cleanup() }\n }\n const onUp = () => {\n cleanup()\n if (cancelled) return\n if (hovered.value?.id === id) restore(id)\n else showPreview(id, el, true)\n }\n const timer = setTimeout(() => {\n if (cancelled) return\n cleanup()\n navigator.vibrate?.(10)\n emit('contextMenu', id, event)\n }, LONG_PRESS_MS)\n\n el.addEventListener('pointermove', onMove)\n el.addEventListener('pointerup', onUp)\n el.addEventListener('pointercancel', cleanup)\n}\n\n// A preview must not outlive its panel: restoring or closing from elsewhere closes it.\nwatch(() => ws.state.minimized.map(m => m.id).join(','), (ids) => {\n if (hovered.value && !ids.split(',').includes(hovered.value.id)) hovered.value = null\n})\n\nonBeforeUnmount(() => { clearTimeout(collapseTimer); clearTimeout(dismissTimer) })\n</script>\n\n<template>\n <div\n v-if=\"visible\"\n class=\"vdd-taskbar-footer-container\"\n :class=\"[\n `vdd-taskbar-mode-${visibility}`,\n { 'vdd-taskbar-expanded': visibility === 'autohide' && expanded },\n ]\"\n data-vdd-taskbar\n :data-vdd-taskbar-mode=\"visibility\"\n @pointerenter=\"visibility === 'autohide' && expand()\"\n @pointerleave=\"visibility === 'autohide' && scheduleCollapse()\"\n >\n <div v-if=\"visibility === 'autohide'\" class=\"vdd-taskbar-peek-handle\" data-vdd-peek />\n\n <button\n v-if=\"scrollable\"\n type=\"button\"\n class=\"vdd-taskbar-nav-btn\"\n data-vdd-taskbar-scroll=\"left\"\n :aria-label=\"ws.format(ws.messages.scrollTabsLeft)\"\n @click=\"scrollBy(-1)\"\n >◀</button>\n\n <div ref=\"strip\" class=\"vdd-taskbar-items-container\">\n <div\n v-for=\"item in items\"\n :key=\"item.id\"\n class=\"vdd-taskbar-glassmorphic-item\"\n :data-vdd-taskbar-item=\"item.id\"\n role=\"button\"\n :title=\"ws.format(item.title)\"\n @click=\"onIconClick(item.id, $event)\"\n @contextmenu.prevent=\"emit('contextMenu', item.id, $event)\"\n @pointerdown=\"onIconPointerDown(item.id, $event)\"\n @pointerenter=\"onIconEnter(item.id, $event)\"\n @pointerleave=\"dismissPreview()\"\n >\n <span class=\"vdd-taskbar-item-icon\">\n <component :is=\"item.options.icon ?? defaultIcon\" v-if=\"item.options.icon ?? defaultIcon\" />\n <svg v-else width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <rect x=\"3\" y=\"3\" width=\"7\" height=\"9\" rx=\"1\" />\n <rect x=\"14\" y=\"3\" width=\"7\" height=\"5\" rx=\"1\" />\n <rect x=\"14\" y=\"12\" width=\"7\" height=\"9\" rx=\"1\" />\n <rect x=\"3\" y=\"16\" width=\"7\" height=\"5\" rx=\"1\" />\n </svg>\n </span>\n </div>\n </div>\n\n <button\n v-if=\"scrollable\"\n type=\"button\"\n class=\"vdd-taskbar-nav-btn\"\n data-vdd-taskbar-scroll=\"right\"\n :aria-label=\"ws.format(ws.messages.scrollTabsRight)\"\n @click=\"scrollBy(1)\"\n >▶</button>\n\n <VddTaskbarPreview\n v-if=\"hovered\"\n :key=\"hovered.id\"\n :panel-id=\"hovered.id\"\n :anchor=\"hovered.anchor\"\n :cache=\"cache\"\n @keep=\"keepPreview()\"\n @restore=\"restore(hovered.id)\"\n @close=\"ws.requestClosePanel(hovered.id); hovered = null\"\n @dismiss=\"dismissPreview()\"\n />\n </div>\n</template>\n","<script setup lang=\"ts\">\n/**\n * The workspace.\n *\n * Two jobs. It renders the layout — the recursive grid, and later floating windows and the\n * taskbar. And it owns the **persistence port**: every open panel is rendered here exactly\n * once, teleported into its own cache element, and that element is moved into whichever slot\n * currently shows the panel.\n *\n * Nothing in the port is ever conditionally rendered. That is the entire zero-unmount\n * guarantee, and why a panel keeps its WebGL context, its scroll position and its running\n * timers through every layout change.\n */\nimport { computed, onBeforeUnmount, onMounted, ref, useTemplateRef, watch } from 'vue'\nimport { clampFloatingRect } from '../core/anchorGeometry'\nimport { PanelDomCache } from '../core/panelDom'\nimport { providePanelDom } from '../composables/usePanelDom'\nimport { provideDragDock } from '../composables/useDragDock'\nimport type { Workspace } from '../core/workspace'\nimport { buildTaskbarMenu } from '../core/panelMenu'\nimport { useWorkspace } from '../composables/useWorkspace'\nimport VddWorkspaceGrid from './VddWorkspaceGrid.vue'\nimport VddPanelMount from './VddPanelMount.vue'\nimport VddFloatingWindow from './VddFloatingWindow.vue'\nimport VddEdgeZones from './VddEdgeZones.vue'\nimport VddDragGhost from './VddDragGhost.vue'\nimport VddTaskbar from './VddTaskbar.vue'\nimport type { TaskbarVisibility } from './VddTaskbar.vue'\n\nconst props = withDefaults(defineProps<{\n /** Built-in skin, or a name you scope your own tokens to. @default 'vscode' */\n skin?: string\n /** Enable the library's own transitions. Never affects the host app's animations. @default true */\n animations?: boolean\n /**\n * When the minimised-panel taskbar is shown.\n * - `'always'` — a permanent strip\n * - `'compact'` — only while something is minimised\n * - `'autohide'` — an overlay collapsed to an 8px peek strip, expanding on hover\n * @default 'always'\n */\n taskbar?: TaskbarVisibility\n /** Fallback icon for panels that register none. */\n defaultPanelIcon?: unknown\n}>(), { skin: 'vscode', animations: true, taskbar: 'always' })\n\nconst emit = defineEmits<{\n /**\n * A right-click or long press on a taskbar icon, emitted *after* the library has opened its\n * own menu — so an application can observe it without having to rebuild the standard items.\n */\n taskbarContextMenu: [panelId: string, event: PointerEvent | MouseEvent]\n}>()\n\nconst ws = useWorkspace()\nconst dom = new PanelDomCache()\nprovidePanelDom(dom)\nconst drag = provideDragDock(ws as unknown as Workspace<never>)\n\n/** Every panel that exists, so the port can render each exactly once. */\nconst mounted = computed(() => Object.keys(ws.state.panels))\n\n// A closed panel's element is dropped, or the cache would grow for the session's lifetime.\nwatch(mounted, (now, before) => {\n for (const id of before ?? []) if (!now.includes(id)) dom.release(id)\n})\n\n/**\n * The workspace's own measured size.\n *\n * Measured, not read from the window: the workspace is usually only part of a page — a\n * sidebar may expand, a taskbar may appear — and floating windows are positioned inside it.\n */\n/**\n * The two diagnostics that turn a silent black screen into a message.\n *\n * The stylesheet carries a `--vdd-styles-loaded: 1` sentinel; without the import the\n * workspace renders as an unstyled black rectangle with nothing in the console at all, which\n * is a genuinely hard first hour for someone new to the library. Checked once, on mount.\n */\nonMounted(() => {\n if (process.env.NODE_ENV === 'production') return\n try {\n const sentinel = getComputedStyle(document.documentElement)\n .getPropertyValue('--vdd-styles-loaded').trim()\n if (sentinel !== '1') {\n console.error(\n '[vue-dockable-desktop] the stylesheet is not imported.\\n' +\n 'Add this to your entry file (main.ts):\\n' +\n \" import 'vue-dockable-desktop/styles.css'\\n\" +\n 'Without it the workspace renders as a black rectangle, with no other error.',\n )\n }\n } catch { /* no getComputedStyle: SSR, or a very old environment */ }\n})\n\nconst viewportEl = useTemplateRef<HTMLDivElement>('viewport')\nconst viewport = ref({ width: 1024, height: 768 })\nlet observer: ResizeObserver | null = null\nlet warnedZeroHeight = false\n\nwatch(viewportEl, (el) => {\n observer?.disconnect()\n if (!el || typeof ResizeObserver === 'undefined') return\n observer = new ResizeObserver((entries) => {\n const rect = entries[0]?.contentRect\n if (!rect) return\n viewport.value = { width: Math.max(100, rect.width), height: Math.max(100, rect.height) }\n\n // A zero-height workspace is invisible with no error anywhere, and the cause is always\n // the same: a percentage-height chain broken by an ancestor with `height: auto`. Say so,\n // and name the ancestor, rather than letting the app look broken.\n if (process.env.NODE_ENV !== 'production' && rect.height < 10 && !warnedZeroHeight) {\n warnedZeroHeight = true\n let culprit: Element = el\n let cursor = el.parentElement\n while (cursor && cursor !== document.documentElement) {\n if (cursor.getBoundingClientRect().height < 10) culprit = cursor\n else break\n cursor = cursor.parentElement\n }\n const describe = culprit === el\n ? 'the workspace element itself'\n : `<${culprit.tagName.toLowerCase()}${culprit.id ? ` id=\"${culprit.id}\"` : ''}${culprit.className ? ` class=\"${culprit.className}\"` : ''}>`\n console.warn(\n `[vue-dockable-desktop] The workspace has no height, so it is invisible.\\n\\n` +\n `Zero height starts at: ${describe}\\n\\n` +\n `In CSS, height: 100% only resolves when every ancestor has a real height. If any one ` +\n `of them is height: auto (a div's default), the chain breaks.\\n\\n` +\n `Give the workspace's container a height — 100vh, or flex: 1 with min-height: 0 inside ` +\n `a flex column. The library deliberately does not style your page; .vdd-fill-viewport ` +\n `is available if you want the simple case.`,\n )\n }\n })\n observer.observe(el)\n}, { immediate: true, flush: 'post' })\n\n/**\n * Keep floating windows reachable when the workspace shrinks.\n *\n * The arithmetic is `clampFloatingRect`, which returns the same object when nothing changed —\n * this watcher is deep on the very list it writes to, so a write that changes nothing would\n * re-trigger it forever.\n */\nwatch([viewport, () => ws.state.floating], ([view, windows]) => {\n for (const w of windows) {\n const num = (v: number | string) => (typeof v === 'number' ? v : Number.parseFloat(String(v)))\n const current = { x: num(w.x), y: num(w.y), width: num(w.width), height: num(w.height) }\n const next = clampFloatingRect(current, view, w.anchor != null)\n if (next !== current) ws.updateFloatingPosition(w.id, next)\n }\n}, { deep: true })\n\n/** The menu for a minimised panel: restore, maximise (which rdd's never did), close. */\nfunction onTaskbarMenu(panelId: string, event: PointerEvent | MouseEvent): void {\n const panel = ws.state.panels[panelId]\n if (panel) {\n const options = ws.registry.get(panel.component)?.defaultOptions ?? {}\n const items = buildTaskbarMenu(ws, panelId, options, {\n float: () => ws.floatPanel(panelId),\n minimize: () => ws.minimizePanel(panelId),\n close: () => ws.requestClosePanel(panelId),\n restore: () => ws.restorePanel(panelId),\n maximize: () => ws.maximizePanel(panelId),\n })\n if (items.length > 0) ws.showContextMenu({ event, items })\n }\n emit('taskbarContextMenu', panelId, event)\n}\n\n/**\n * Skin, animation state and the stacking base are mirrored onto the document element, so\n * chrome that teleports to `document.body` — menus, toasts, flyouts, modals — inherits the\n * same tokens as the workspace itself.\n *\n * `--vdd-z-base` is the one that is easy to leave out and impossible to notice: every piece\n * of teleported chrome stacks with `calc(var(--vdd-z-base, 1000) + n)`, so without this the\n * fallback silently applies and `createWorkspace({ zIndexBase })` has no effect at all. The\n * option would exist, the stylesheet would read the variable, and nothing would connect them\n * — the same shape as the four dead CSS hookups this library has already shipped.\n */\nconst syncDocument = () => {\n const root = document.documentElement\n root.setAttribute('data-vdd-skin', props.skin)\n root.classList.toggle('vdd-no-animations', props.animations === false)\n root.style.setProperty('--vdd-z-base', String(ws.config.zIndexBase))\n}\nwatch(() => [props.skin, props.animations, ws.config.zIndexBase], syncDocument)\nonMounted(syncDocument)\n\nonBeforeUnmount(() => {\n observer?.disconnect()\n const root = document.documentElement\n root.removeAttribute('data-vdd-skin')\n root.classList.remove('vdd-no-animations')\n root.style.removeProperty('--vdd-z-base')\n dom.dispose()\n})\n</script>\n\n<template>\n <div\n class=\"vdd-workspace\"\n :class=\"{ 'vdd-no-animations': animations === false }\"\n :data-vdd-skin=\"skin\"\n :dir=\"ws.state.dir\"\n >\n <div ref=\"viewport\" class=\"vdd-workspace-viewport\">\n <VddEdgeZones v-if=\"drag.dragging.value\" />\n\n <VddWorkspaceGrid :node=\"ws.state.gridRoot\" :path=\"[]\" />\n\n <VddFloatingWindow\n v-for=\"w in ws.state.floating\"\n :key=\"w.id\"\n :window=\"w\"\n :viewport=\"viewport\"\n />\n </div>\n\n <VddTaskbar\n :visibility=\"taskbar\"\n :cache=\"dom\"\n :default-icon=\"defaultPanelIcon\"\n @context-menu=\"onTaskbarMenu\"\n />\n\n <VddDragGhost />\n\n <!--\n The persistence port. Every open panel is rendered here once, for as long as it is\n open, and teleported into its own cache element. Never conditional, never keyed on\n placement — that is the whole mechanism.\n -->\n <div class=\"vdd-panel-port\" aria-hidden=\"true\">\n <VddPanelMount v-for=\"id in mounted\" :key=\"id\" :panel-id=\"id\" :cache=\"dom\" />\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/**\n * The workspace.\n *\n * Two jobs. It renders the layout — the recursive grid, and later floating windows and the\n * taskbar. And it owns the **persistence port**: every open panel is rendered here exactly\n * once, teleported into its own cache element, and that element is moved into whichever slot\n * currently shows the panel.\n *\n * Nothing in the port is ever conditionally rendered. That is the entire zero-unmount\n * guarantee, and why a panel keeps its WebGL context, its scroll position and its running\n * timers through every layout change.\n */\nimport { computed, onBeforeUnmount, onMounted, ref, useTemplateRef, watch } from 'vue'\nimport { clampFloatingRect } from '../core/anchorGeometry'\nimport { PanelDomCache } from '../core/panelDom'\nimport { providePanelDom } from '../composables/usePanelDom'\nimport { provideDragDock } from '../composables/useDragDock'\nimport type { Workspace } from '../core/workspace'\nimport { buildTaskbarMenu } from '../core/panelMenu'\nimport { useWorkspace } from '../composables/useWorkspace'\nimport VddWorkspaceGrid from './VddWorkspaceGrid.vue'\nimport VddPanelMount from './VddPanelMount.vue'\nimport VddFloatingWindow from './VddFloatingWindow.vue'\nimport VddEdgeZones from './VddEdgeZones.vue'\nimport VddDragGhost from './VddDragGhost.vue'\nimport VddTaskbar from './VddTaskbar.vue'\nimport type { TaskbarVisibility } from './VddTaskbar.vue'\n\nconst props = withDefaults(defineProps<{\n /** Built-in skin, or a name you scope your own tokens to. @default 'vscode' */\n skin?: string\n /** Enable the library's own transitions. Never affects the host app's animations. @default true */\n animations?: boolean\n /**\n * When the minimised-panel taskbar is shown.\n * - `'always'` — a permanent strip\n * - `'compact'` — only while something is minimised\n * - `'autohide'` — an overlay collapsed to an 8px peek strip, expanding on hover\n * @default 'always'\n */\n taskbar?: TaskbarVisibility\n /** Fallback icon for panels that register none. */\n defaultPanelIcon?: unknown\n}>(), { skin: 'vscode', animations: true, taskbar: 'always' })\n\nconst emit = defineEmits<{\n /**\n * A right-click or long press on a taskbar icon, emitted *after* the library has opened its\n * own menu — so an application can observe it without having to rebuild the standard items.\n */\n taskbarContextMenu: [panelId: string, event: PointerEvent | MouseEvent]\n}>()\n\nconst ws = useWorkspace()\nconst dom = new PanelDomCache()\nprovidePanelDom(dom)\nconst drag = provideDragDock(ws as unknown as Workspace<never>)\n\n/** Every panel that exists, so the port can render each exactly once. */\nconst mounted = computed(() => Object.keys(ws.state.panels))\n\n// A closed panel's element is dropped, or the cache would grow for the session's lifetime.\nwatch(mounted, (now, before) => {\n for (const id of before ?? []) if (!now.includes(id)) dom.release(id)\n})\n\n/**\n * The workspace's own measured size.\n *\n * Measured, not read from the window: the workspace is usually only part of a page — a\n * sidebar may expand, a taskbar may appear — and floating windows are positioned inside it.\n */\n/**\n * The two diagnostics that turn a silent black screen into a message.\n *\n * The stylesheet carries a `--vdd-styles-loaded: 1` sentinel; without the import the\n * workspace renders as an unstyled black rectangle with nothing in the console at all, which\n * is a genuinely hard first hour for someone new to the library. Checked once, on mount.\n */\nonMounted(() => {\n if (process.env.NODE_ENV === 'production') return\n try {\n const sentinel = getComputedStyle(document.documentElement)\n .getPropertyValue('--vdd-styles-loaded').trim()\n if (sentinel !== '1') {\n console.error(\n '[vue-dockable-desktop] the stylesheet is not imported.\\n' +\n 'Add this to your entry file (main.ts):\\n' +\n \" import 'vue-dockable-desktop/styles.css'\\n\" +\n 'Without it the workspace renders as a black rectangle, with no other error.',\n )\n }\n } catch { /* no getComputedStyle: SSR, or a very old environment */ }\n})\n\nconst viewportEl = useTemplateRef<HTMLDivElement>('viewport')\nconst viewport = ref({ width: 1024, height: 768 })\nlet observer: ResizeObserver | null = null\nlet warnedZeroHeight = false\n\nwatch(viewportEl, (el) => {\n observer?.disconnect()\n if (!el || typeof ResizeObserver === 'undefined') return\n observer = new ResizeObserver((entries) => {\n const rect = entries[0]?.contentRect\n if (!rect) return\n viewport.value = { width: Math.max(100, rect.width), height: Math.max(100, rect.height) }\n\n // A zero-height workspace is invisible with no error anywhere, and the cause is always\n // the same: a percentage-height chain broken by an ancestor with `height: auto`. Say so,\n // and name the ancestor, rather than letting the app look broken.\n if (process.env.NODE_ENV !== 'production' && rect.height < 10 && !warnedZeroHeight) {\n warnedZeroHeight = true\n let culprit: Element = el\n let cursor = el.parentElement\n while (cursor && cursor !== document.documentElement) {\n if (cursor.getBoundingClientRect().height < 10) culprit = cursor\n else break\n cursor = cursor.parentElement\n }\n const describe = culprit === el\n ? 'the workspace element itself'\n : `<${culprit.tagName.toLowerCase()}${culprit.id ? ` id=\"${culprit.id}\"` : ''}${culprit.className ? ` class=\"${culprit.className}\"` : ''}>`\n console.warn(\n `[vue-dockable-desktop] The workspace has no height, so it is invisible.\\n\\n` +\n `Zero height starts at: ${describe}\\n\\n` +\n `In CSS, height: 100% only resolves when every ancestor has a real height. If any one ` +\n `of them is height: auto (a div's default), the chain breaks.\\n\\n` +\n `Give the workspace's container a height — 100vh, or flex: 1 with min-height: 0 inside ` +\n `a flex column. The library deliberately does not style your page; .vdd-fill-viewport ` +\n `is available if you want the simple case.`,\n )\n }\n })\n observer.observe(el)\n}, { immediate: true, flush: 'post' })\n\n/**\n * Keep floating windows reachable when the workspace shrinks.\n *\n * The arithmetic is `clampFloatingRect`, which returns the same object when nothing changed —\n * this watcher is deep on the very list it writes to, so a write that changes nothing would\n * re-trigger it forever.\n */\nwatch([viewport, () => ws.state.floating], ([view, windows]) => {\n for (const w of windows) {\n const num = (v: number | string) => (typeof v === 'number' ? v : Number.parseFloat(String(v)))\n const current = { x: num(w.x), y: num(w.y), width: num(w.width), height: num(w.height) }\n const next = clampFloatingRect(current, view, w.anchor != null)\n if (next !== current) ws.updateFloatingPosition(w.id, next)\n }\n}, { deep: true })\n\n/** The menu for a minimised panel: restore, maximise (which rdd's never did), close. */\nfunction onTaskbarMenu(panelId: string, event: PointerEvent | MouseEvent): void {\n const panel = ws.state.panels[panelId]\n if (panel) {\n const options = ws.registry.get(panel.component)?.defaultOptions ?? {}\n const items = buildTaskbarMenu(ws, panelId, options, {\n float: () => ws.floatPanel(panelId),\n minimize: () => ws.minimizePanel(panelId),\n close: () => ws.requestClosePanel(panelId),\n restore: () => ws.restorePanel(panelId),\n maximize: () => ws.maximizePanel(panelId),\n })\n if (items.length > 0) ws.showContextMenu({ event, items })\n }\n emit('taskbarContextMenu', panelId, event)\n}\n\n/**\n * Skin, animation state and the stacking base are mirrored onto the document element, so\n * chrome that teleports to `document.body` — menus, toasts, flyouts, modals — inherits the\n * same tokens as the workspace itself.\n *\n * `--vdd-z-base` is the one that is easy to leave out and impossible to notice: every piece\n * of teleported chrome stacks with `calc(var(--vdd-z-base, 1000) + n)`, so without this the\n * fallback silently applies and `createWorkspace({ zIndexBase })` has no effect at all. The\n * option would exist, the stylesheet would read the variable, and nothing would connect them\n * — the same shape as the four dead CSS hookups this library has already shipped.\n */\nconst syncDocument = () => {\n const root = document.documentElement\n root.setAttribute('data-vdd-skin', props.skin)\n root.classList.toggle('vdd-no-animations', props.animations === false)\n root.style.setProperty('--vdd-z-base', String(ws.config.zIndexBase))\n}\nwatch(() => [props.skin, props.animations, ws.config.zIndexBase], syncDocument)\nonMounted(syncDocument)\n\nonBeforeUnmount(() => {\n observer?.disconnect()\n const root = document.documentElement\n root.removeAttribute('data-vdd-skin')\n root.classList.remove('vdd-no-animations')\n root.style.removeProperty('--vdd-z-base')\n dom.dispose()\n})\n</script>\n\n<template>\n <div\n class=\"vdd-workspace\"\n :class=\"{ 'vdd-no-animations': animations === false }\"\n :data-vdd-skin=\"skin\"\n :dir=\"ws.state.dir\"\n >\n <div ref=\"viewport\" class=\"vdd-workspace-viewport\">\n <VddEdgeZones v-if=\"drag.dragging.value\" />\n\n <VddWorkspaceGrid :node=\"ws.state.gridRoot\" :path=\"[]\" />\n\n <VddFloatingWindow\n v-for=\"w in ws.state.floating\"\n :key=\"w.id\"\n :window=\"w\"\n :viewport=\"viewport\"\n />\n </div>\n\n <VddTaskbar\n :visibility=\"taskbar\"\n :cache=\"dom\"\n :default-icon=\"defaultPanelIcon\"\n @context-menu=\"onTaskbarMenu\"\n />\n\n <VddDragGhost />\n\n <!--\n The persistence port. Every open panel is rendered here once, for as long as it is\n open, and teleported into its own cache element. Never conditional, never keyed on\n placement — that is the whole mechanism.\n -->\n <div class=\"vdd-panel-port\" aria-hidden=\"true\">\n <VddPanelMount v-for=\"id in mounted\" :key=\"id\" :panel-id=\"id\" :cache=\"dom\" />\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/**\n * The context-menu host.\n *\n * Renders whatever menu the workspace has pending, so `showContextMenu()` works from any\n * component — or from no component at all. rdd needed a provider, an adapter ref and a\n * registration handshake to make the menu reachable from both sides of the workspace in the\n * component tree; here the request is just state.\n *\n * Mount one of these once, alongside `<VddDesktop>`.\n */\nimport { computed, nextTick, onBeforeUnmount, ref, useTemplateRef, watch } from 'vue'\nimport type { ContextMenuItem, ContextMenuSimpleItem, ContextMenuSubMenu } from '../core/contextMenu'\nimport { clampToViewport, isSeparator, isSubMenu } from '../core/contextMenu'\nimport { useWorkspace } from '../composables/useWorkspace'\n\nwithDefaults(defineProps<{\n /** Visual theme hook, mirrored onto the menu's class. @default 'dark' */\n theme?: string\n}>(), { theme: 'dark' })\n\ndefineSlots<{\n /**\n * Render the whole menu yourself, with your own UI kit.\n *\n * Replaces rdd's `ContextMenuAdapter`, whose single field was a component to swap in — a\n * slot is the Vue spelling of exactly that (docs/decisions/0007-slots-over-render-props.md),\n * and it needs no provider, no ref handshake and no adapter object.\n *\n * `items` is the pending menu, `x`/`y` the requested position, and `close` dismisses it.\n * Positioning and dismissal are then yours: the built-in clamping, Escape handling and\n * outside-click handling belong to the built-in markup this replaces.\n */\n default?: (props: { items: ContextMenuItem[]; x: number; y: number; close: () => void }) => unknown\n}>()\n\n/** How long the pointer must rest on a submenu parent before it opens. */\nconst SUBMENU_OPEN_MS = 150\n/** Grace period before a submenu closes, so the pointer can travel into it. */\nconst SUBMENU_CLOSE_MS = 200\n\nconst ws = useWorkspace()\nconst root = useTemplateRef<HTMLDivElement>('root')\nconst submenuRoot = useTemplateRef<HTMLDivElement>('submenuRoot')\n\nconst request = computed(() => ws.contextMenu.value)\nconst position = ref({ x: 0, y: 0 })\nconst openSubmenu = ref<number | null>(null)\nconst submenuPosition = ref({ x: 0, y: 0 })\nconst itemEls = new Map<number, HTMLElement>()\n\nlet openTimer: ReturnType<typeof setTimeout> | undefined\nlet closeTimer: ReturnType<typeof setTimeout> | undefined\nconst clearTimers = () => { clearTimeout(openTimer); clearTimeout(closeTimer) }\n\nfunction close(): void {\n clearTimers()\n openSubmenu.value = null\n ws.closeContextMenu()\n}\n\n/** Position the menu, then pull it back inside the viewport once it has a size. */\nwatch(request, async (next) => {\n if (!next) { openSubmenu.value = null; return }\n position.value = { x: next.x, y: next.y }\n itemEls.clear()\n openSubmenu.value = null\n await nextTick()\n const el = root.value\n if (!el) return\n const box = el.getBoundingClientRect()\n position.value = clampToViewport(\n { x: next.x, y: next.y },\n { width: box.width, height: box.height },\n { width: window.innerWidth, height: window.innerHeight },\n )\n})\n\n/**\n * Dismissal.\n *\n * Two listeners, because one is not enough. `pointerdown` in the capture phase runs before\n * any canvas or map gesture handler can swallow it; a bubbled `click` on `window` survives a\n * `stopPropagation` on pointerdown, which WebGL canvases commonly do. rdd learned this the\n * hard way against real mapping libraries.\n */\nwatch(request, (next) => {\n if (next) {\n document.addEventListener('pointerdown', onOutside, { capture: true })\n window.addEventListener('click', onOutside)\n document.addEventListener('keydown', onKey)\n } else {\n document.removeEventListener('pointerdown', onOutside, { capture: true })\n window.removeEventListener('click', onOutside)\n document.removeEventListener('keydown', onKey)\n }\n})\n\nfunction onOutside(event: Event): void {\n const target = event.target\n // `Node.contains()` throws for anything that is not a Node, and an event target need not\n // be one — a click dispatched on `window` is the common case. If we cannot tell whether it\n // was inside, it was not the menu, so dismiss: closing is the safe default.\n if (!(target instanceof Node)) { close(); return }\n if (root.value?.contains(target) || submenuRoot.value?.contains(target)) return\n close()\n}\nfunction onKey(event: KeyboardEvent): void {\n if (event.key === 'Escape') { event.stopPropagation(); close() }\n}\n\nonBeforeUnmount(() => {\n clearTimers()\n document.removeEventListener('pointerdown', onOutside, { capture: true })\n window.removeEventListener('click', onOutside)\n document.removeEventListener('keydown', onKey)\n})\n\n// ── items ──────────────────────────────────────────────────────────────────\nconst simple = (item: ContextMenuItem) => item as ContextMenuSimpleItem\nconst showsCheckbox = (item: ContextMenuItem) => {\n const box = simple(item).checkbox\n return !!box && box.active !== false\n}\nconst isChecked = (item: ContextMenuItem) => showsCheckbox(item) && simple(item).checkbox!.value === true\nconst isDisabled = (item: ContextMenuItem) =>\n simple(item).disabled === true || (showsCheckbox(item) && simple(item).checkbox!.enabled === false)\n\nfunction keepEl(index: number, el: unknown): void {\n const element = el as HTMLElement | null\n if (element) itemEls.set(index, element)\n else itemEls.delete(index)\n}\n\nfunction activate(item: ContextMenuItem): void {\n if (isDisabled(item)) return\n simple(item).action?.()\n close()\n}\n\nfunction onItemEnter(index: number, item: ContextMenuItem): void {\n clearTimeout(closeTimer)\n if (openSubmenu.value !== null && openSubmenu.value !== index) {\n clearTimeout(openTimer)\n openSubmenu.value = null\n }\n if (isSubMenu(item) && item.items?.length) {\n clearTimeout(openTimer)\n openTimer = setTimeout(() => {\n const el = itemEls.get(index)\n if (el) {\n const box = el.getBoundingClientRect()\n // Opens away from the parent item, mirrored for reading direction.\n submenuPosition.value = ws.state.isRtl\n ? { x: window.innerWidth - box.left + 2, y: box.top }\n : { x: box.right + 2, y: box.top }\n }\n openSubmenu.value = index\n }, SUBMENU_OPEN_MS)\n } else {\n clearTimeout(openTimer)\n if (openSubmenu.value !== null) {\n closeTimer = setTimeout(() => { openSubmenu.value = null }, SUBMENU_CLOSE_MS)\n }\n }\n}\n\nfunction onItemLeave(item: ContextMenuItem): void {\n clearTimeout(openTimer)\n if (isSubMenu(item) && item.items?.length) {\n closeTimer = setTimeout(() => { openSubmenu.value = null }, SUBMENU_CLOSE_MS)\n }\n}\n\nconst submenuItems = computed<ContextMenuItem[]>(() => {\n const index = openSubmenu.value\n if (index === null || !request.value) return []\n const item = request.value.items[index]\n return item && isSubMenu(item) ? item.items ?? [] : []\n})\n\n/** The submenu opens rightwards under LTR and leftwards under RTL. */\nconst submenuStyle = computed(() => ws.state.isRtl\n ? { position: 'fixed' as const, right: `${submenuPosition.value.x}px`, top: `${submenuPosition.value.y}px` }\n : { position: 'fixed' as const, left: `${submenuPosition.value.x}px`, top: `${submenuPosition.value.y}px` })\n</script>\n\n<template>\n <Teleport v-if=\"request\" to=\"body\">\n <slot\n v-if=\"$slots.default\"\n :items=\"request.items\"\n :x=\"position.x\"\n :y=\"position.y\"\n :close=\"() => ws.closeContextMenu()\"\n />\n\n <div\n v-else\n ref=\"root\"\n class=\"vdd-context-menu\"\n :class=\"`vdd-context-menu--${theme}`\"\n :style=\"{ position: 'fixed', left: `${position.x}px`, top: `${position.y}px` }\"\n :dir=\"ws.state.dir\"\n data-vdd-menu\n role=\"menu\"\n aria-orientation=\"vertical\"\n >\n <template v-for=\"(item, index) in request.items\" :key=\"index\">\n <hr v-if=\"isSeparator(item)\" class=\"vdd-context-menu__separator\" role=\"separator\" >\n\n <button\n v-else-if=\"isSubMenu(item)\"\n :ref=\"(el) => keepEl(index, el)\"\n type=\"button\"\n class=\"vdd-context-menu__item vdd-context-menu__item--has-submenu\"\n :class=\"{ 'vdd-context-menu__item--submenu-open': openSubmenu === index }\"\n :title=\"(item as ContextMenuSubMenu).title ? ws.format((item as ContextMenuSubMenu).title) : undefined\"\n :data-vdd-menu-submenu=\"ws.format((item as ContextMenuSubMenu).label)\"\n role=\"menuitem\"\n aria-haspopup=\"true\"\n :aria-expanded=\"openSubmenu === index\"\n @pointerenter=\"onItemEnter(index, item)\"\n @pointerleave=\"onItemLeave(item)\"\n >\n <span class=\"vdd-context-menu__icon\" aria-hidden=\"true\" />\n <span class=\"vdd-context-menu__label\">{{ ws.format((item as ContextMenuSubMenu).label) }}</span>\n <span class=\"vdd-context-menu__chevron\" aria-hidden=\"true\">›</span>\n </button>\n\n <button\n v-else\n :ref=\"(el) => keepEl(index, el)\"\n type=\"button\"\n class=\"vdd-context-menu__item\"\n :class=\"{ 'vdd-context-menu__item--disabled': isDisabled(item) }\"\n :disabled=\"isDisabled(item)\"\n :title=\"simple(item).title ? ws.format(simple(item).title) : undefined\"\n :data-cy-action=\"simple(item).cyAction\"\n :data-vdd-menu-item=\"ws.format(simple(item).label)\"\n role=\"menuitem\"\n :aria-checked=\"showsCheckbox(item) ? isChecked(item) : undefined\"\n @click=\"activate(item)\"\n @pointerenter=\"onItemEnter(index, item)\"\n @pointerleave=\"onItemLeave(item)\"\n >\n <span class=\"vdd-context-menu__icon\" :aria-hidden=\"!simple(item).icon\">\n <component :is=\"simple(item).icon\" v-if=\"simple(item).icon\" />\n </span>\n <span class=\"vdd-context-menu__label\">{{ ws.format(simple(item).label) }}</span>\n <span\n v-if=\"showsCheckbox(item)\"\n class=\"vdd-context-menu__checkbox\"\n :class=\"{ 'vdd-context-menu__checkbox--checked': isChecked(item) }\"\n aria-hidden=\"true\"\n >\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" fill=\"none\">\n <rect x=\"0.75\" y=\"0.75\" width=\"10.5\" height=\"10.5\" rx=\"2\" :fill=\"isChecked(item) ? 'currentColor' : 'none'\" stroke=\"currentColor\" stroke-width=\"1.5\" />\n <path v-if=\"isChecked(item)\" d=\"M2.5 6 L4.5 8.5 L9.5 3.5\" stroke=\"white\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n </svg>\n </span>\n </button>\n </template>\n </div>\n\n <div\n v-if=\"openSubmenu !== null && submenuItems.length\"\n ref=\"submenuRoot\"\n class=\"vdd-context-menu vdd-context-menu--submenu\"\n :class=\"`vdd-context-menu--${theme}`\"\n :style=\"submenuStyle\"\n :dir=\"ws.state.dir\"\n data-vdd-submenu\n role=\"menu\"\n @pointerenter=\"clearTimers()\"\n @pointerleave=\"openSubmenu = null\"\n >\n <template v-for=\"(item, index) in submenuItems\" :key=\"index\">\n <hr v-if=\"isSeparator(item)\" class=\"vdd-context-menu__separator\" role=\"separator\" >\n <button\n v-else\n type=\"button\"\n class=\"vdd-context-menu__item\"\n :class=\"{ 'vdd-context-menu__item--disabled': isDisabled(item) }\"\n :disabled=\"isDisabled(item)\"\n :data-vdd-menu-item=\"ws.format(simple(item).label)\"\n role=\"menuitem\"\n @click=\"activate(item)\"\n >\n <span class=\"vdd-context-menu__icon\" :aria-hidden=\"!simple(item).icon\">\n <component :is=\"simple(item).icon\" v-if=\"simple(item).icon\" />\n </span>\n <span class=\"vdd-context-menu__label\">{{ ws.format(simple(item).label) }}</span>\n </button>\n </template>\n </div>\n </Teleport>\n</template>\n","<script setup lang=\"ts\">\n/**\n * The context-menu host.\n *\n * Renders whatever menu the workspace has pending, so `showContextMenu()` works from any\n * component — or from no component at all. rdd needed a provider, an adapter ref and a\n * registration handshake to make the menu reachable from both sides of the workspace in the\n * component tree; here the request is just state.\n *\n * Mount one of these once, alongside `<VddDesktop>`.\n */\nimport { computed, nextTick, onBeforeUnmount, ref, useTemplateRef, watch } from 'vue'\nimport type { ContextMenuItem, ContextMenuSimpleItem, ContextMenuSubMenu } from '../core/contextMenu'\nimport { clampToViewport, isSeparator, isSubMenu } from '../core/contextMenu'\nimport { useWorkspace } from '../composables/useWorkspace'\n\nwithDefaults(defineProps<{\n /** Visual theme hook, mirrored onto the menu's class. @default 'dark' */\n theme?: string\n}>(), { theme: 'dark' })\n\ndefineSlots<{\n /**\n * Render the whole menu yourself, with your own UI kit.\n *\n * Replaces rdd's `ContextMenuAdapter`, whose single field was a component to swap in — a\n * slot is the Vue spelling of exactly that (docs/decisions/0007-slots-over-render-props.md),\n * and it needs no provider, no ref handshake and no adapter object.\n *\n * `items` is the pending menu, `x`/`y` the requested position, and `close` dismisses it.\n * Positioning and dismissal are then yours: the built-in clamping, Escape handling and\n * outside-click handling belong to the built-in markup this replaces.\n */\n default?: (props: { items: ContextMenuItem[]; x: number; y: number; close: () => void }) => unknown\n}>()\n\n/** How long the pointer must rest on a submenu parent before it opens. */\nconst SUBMENU_OPEN_MS = 150\n/** Grace period before a submenu closes, so the pointer can travel into it. */\nconst SUBMENU_CLOSE_MS = 200\n\nconst ws = useWorkspace()\nconst root = useTemplateRef<HTMLDivElement>('root')\nconst submenuRoot = useTemplateRef<HTMLDivElement>('submenuRoot')\n\nconst request = computed(() => ws.contextMenu.value)\nconst position = ref({ x: 0, y: 0 })\nconst openSubmenu = ref<number | null>(null)\nconst submenuPosition = ref({ x: 0, y: 0 })\nconst itemEls = new Map<number, HTMLElement>()\n\nlet openTimer: ReturnType<typeof setTimeout> | undefined\nlet closeTimer: ReturnType<typeof setTimeout> | undefined\nconst clearTimers = () => { clearTimeout(openTimer); clearTimeout(closeTimer) }\n\nfunction close(): void {\n clearTimers()\n openSubmenu.value = null\n ws.closeContextMenu()\n}\n\n/** Position the menu, then pull it back inside the viewport once it has a size. */\nwatch(request, async (next) => {\n if (!next) { openSubmenu.value = null; return }\n position.value = { x: next.x, y: next.y }\n itemEls.clear()\n openSubmenu.value = null\n await nextTick()\n const el = root.value\n if (!el) return\n const box = el.getBoundingClientRect()\n position.value = clampToViewport(\n { x: next.x, y: next.y },\n { width: box.width, height: box.height },\n { width: window.innerWidth, height: window.innerHeight },\n )\n})\n\n/**\n * Dismissal.\n *\n * Two listeners, because one is not enough. `pointerdown` in the capture phase runs before\n * any canvas or map gesture handler can swallow it; a bubbled `click` on `window` survives a\n * `stopPropagation` on pointerdown, which WebGL canvases commonly do. rdd learned this the\n * hard way against real mapping libraries.\n */\nwatch(request, (next) => {\n if (next) {\n document.addEventListener('pointerdown', onOutside, { capture: true })\n window.addEventListener('click', onOutside)\n document.addEventListener('keydown', onKey)\n } else {\n document.removeEventListener('pointerdown', onOutside, { capture: true })\n window.removeEventListener('click', onOutside)\n document.removeEventListener('keydown', onKey)\n }\n})\n\nfunction onOutside(event: Event): void {\n const target = event.target\n // `Node.contains()` throws for anything that is not a Node, and an event target need not\n // be one — a click dispatched on `window` is the common case. If we cannot tell whether it\n // was inside, it was not the menu, so dismiss: closing is the safe default.\n if (!(target instanceof Node)) { close(); return }\n if (root.value?.contains(target) || submenuRoot.value?.contains(target)) return\n close()\n}\nfunction onKey(event: KeyboardEvent): void {\n if (event.key === 'Escape') { event.stopPropagation(); close() }\n}\n\nonBeforeUnmount(() => {\n clearTimers()\n document.removeEventListener('pointerdown', onOutside, { capture: true })\n window.removeEventListener('click', onOutside)\n document.removeEventListener('keydown', onKey)\n})\n\n// ── items ──────────────────────────────────────────────────────────────────\nconst simple = (item: ContextMenuItem) => item as ContextMenuSimpleItem\nconst showsCheckbox = (item: ContextMenuItem) => {\n const box = simple(item).checkbox\n return !!box && box.active !== false\n}\nconst isChecked = (item: ContextMenuItem) => showsCheckbox(item) && simple(item).checkbox!.value === true\nconst isDisabled = (item: ContextMenuItem) =>\n simple(item).disabled === true || (showsCheckbox(item) && simple(item).checkbox!.enabled === false)\n\nfunction keepEl(index: number, el: unknown): void {\n const element = el as HTMLElement | null\n if (element) itemEls.set(index, element)\n else itemEls.delete(index)\n}\n\nfunction activate(item: ContextMenuItem): void {\n if (isDisabled(item)) return\n simple(item).action?.()\n close()\n}\n\nfunction onItemEnter(index: number, item: ContextMenuItem): void {\n clearTimeout(closeTimer)\n if (openSubmenu.value !== null && openSubmenu.value !== index) {\n clearTimeout(openTimer)\n openSubmenu.value = null\n }\n if (isSubMenu(item) && item.items?.length) {\n clearTimeout(openTimer)\n openTimer = setTimeout(() => {\n const el = itemEls.get(index)\n if (el) {\n const box = el.getBoundingClientRect()\n // Opens away from the parent item, mirrored for reading direction.\n submenuPosition.value = ws.state.isRtl\n ? { x: window.innerWidth - box.left + 2, y: box.top }\n : { x: box.right + 2, y: box.top }\n }\n openSubmenu.value = index\n }, SUBMENU_OPEN_MS)\n } else {\n clearTimeout(openTimer)\n if (openSubmenu.value !== null) {\n closeTimer = setTimeout(() => { openSubmenu.value = null }, SUBMENU_CLOSE_MS)\n }\n }\n}\n\nfunction onItemLeave(item: ContextMenuItem): void {\n clearTimeout(openTimer)\n if (isSubMenu(item) && item.items?.length) {\n closeTimer = setTimeout(() => { openSubmenu.value = null }, SUBMENU_CLOSE_MS)\n }\n}\n\nconst submenuItems = computed<ContextMenuItem[]>(() => {\n const index = openSubmenu.value\n if (index === null || !request.value) return []\n const item = request.value.items[index]\n return item && isSubMenu(item) ? item.items ?? [] : []\n})\n\n/** The submenu opens rightwards under LTR and leftwards under RTL. */\nconst submenuStyle = computed(() => ws.state.isRtl\n ? { position: 'fixed' as const, right: `${submenuPosition.value.x}px`, top: `${submenuPosition.value.y}px` }\n : { position: 'fixed' as const, left: `${submenuPosition.value.x}px`, top: `${submenuPosition.value.y}px` })\n</script>\n\n<template>\n <Teleport v-if=\"request\" to=\"body\">\n <slot\n v-if=\"$slots.default\"\n :items=\"request.items\"\n :x=\"position.x\"\n :y=\"position.y\"\n :close=\"() => ws.closeContextMenu()\"\n />\n\n <div\n v-else\n ref=\"root\"\n class=\"vdd-context-menu\"\n :class=\"`vdd-context-menu--${theme}`\"\n :style=\"{ position: 'fixed', left: `${position.x}px`, top: `${position.y}px` }\"\n :dir=\"ws.state.dir\"\n data-vdd-menu\n role=\"menu\"\n aria-orientation=\"vertical\"\n >\n <template v-for=\"(item, index) in request.items\" :key=\"index\">\n <hr v-if=\"isSeparator(item)\" class=\"vdd-context-menu__separator\" role=\"separator\" >\n\n <button\n v-else-if=\"isSubMenu(item)\"\n :ref=\"(el) => keepEl(index, el)\"\n type=\"button\"\n class=\"vdd-context-menu__item vdd-context-menu__item--has-submenu\"\n :class=\"{ 'vdd-context-menu__item--submenu-open': openSubmenu === index }\"\n :title=\"(item as ContextMenuSubMenu).title ? ws.format((item as ContextMenuSubMenu).title) : undefined\"\n :data-vdd-menu-submenu=\"ws.format((item as ContextMenuSubMenu).label)\"\n role=\"menuitem\"\n aria-haspopup=\"true\"\n :aria-expanded=\"openSubmenu === index\"\n @pointerenter=\"onItemEnter(index, item)\"\n @pointerleave=\"onItemLeave(item)\"\n >\n <span class=\"vdd-context-menu__icon\" aria-hidden=\"true\" />\n <span class=\"vdd-context-menu__label\">{{ ws.format((item as ContextMenuSubMenu).label) }}</span>\n <span class=\"vdd-context-menu__chevron\" aria-hidden=\"true\">›</span>\n </button>\n\n <button\n v-else\n :ref=\"(el) => keepEl(index, el)\"\n type=\"button\"\n class=\"vdd-context-menu__item\"\n :class=\"{ 'vdd-context-menu__item--disabled': isDisabled(item) }\"\n :disabled=\"isDisabled(item)\"\n :title=\"simple(item).title ? ws.format(simple(item).title) : undefined\"\n :data-cy-action=\"simple(item).cyAction\"\n :data-vdd-menu-item=\"ws.format(simple(item).label)\"\n role=\"menuitem\"\n :aria-checked=\"showsCheckbox(item) ? isChecked(item) : undefined\"\n @click=\"activate(item)\"\n @pointerenter=\"onItemEnter(index, item)\"\n @pointerleave=\"onItemLeave(item)\"\n >\n <span class=\"vdd-context-menu__icon\" :aria-hidden=\"!simple(item).icon\">\n <component :is=\"simple(item).icon\" v-if=\"simple(item).icon\" />\n </span>\n <span class=\"vdd-context-menu__label\">{{ ws.format(simple(item).label) }}</span>\n <span\n v-if=\"showsCheckbox(item)\"\n class=\"vdd-context-menu__checkbox\"\n :class=\"{ 'vdd-context-menu__checkbox--checked': isChecked(item) }\"\n aria-hidden=\"true\"\n >\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" fill=\"none\">\n <rect x=\"0.75\" y=\"0.75\" width=\"10.5\" height=\"10.5\" rx=\"2\" :fill=\"isChecked(item) ? 'currentColor' : 'none'\" stroke=\"currentColor\" stroke-width=\"1.5\" />\n <path v-if=\"isChecked(item)\" d=\"M2.5 6 L4.5 8.5 L9.5 3.5\" stroke=\"white\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n </svg>\n </span>\n </button>\n </template>\n </div>\n\n <div\n v-if=\"openSubmenu !== null && submenuItems.length\"\n ref=\"submenuRoot\"\n class=\"vdd-context-menu vdd-context-menu--submenu\"\n :class=\"`vdd-context-menu--${theme}`\"\n :style=\"submenuStyle\"\n :dir=\"ws.state.dir\"\n data-vdd-submenu\n role=\"menu\"\n @pointerenter=\"clearTimers()\"\n @pointerleave=\"openSubmenu = null\"\n >\n <template v-for=\"(item, index) in submenuItems\" :key=\"index\">\n <hr v-if=\"isSeparator(item)\" class=\"vdd-context-menu__separator\" role=\"separator\" >\n <button\n v-else\n type=\"button\"\n class=\"vdd-context-menu__item\"\n :class=\"{ 'vdd-context-menu__item--disabled': isDisabled(item) }\"\n :disabled=\"isDisabled(item)\"\n :data-vdd-menu-item=\"ws.format(simple(item).label)\"\n role=\"menuitem\"\n @click=\"activate(item)\"\n >\n <span class=\"vdd-context-menu__icon\" :aria-hidden=\"!simple(item).icon\">\n <component :is=\"simple(item).icon\" v-if=\"simple(item).icon\" />\n </span>\n <span class=\"vdd-context-menu__label\">{{ ws.format(simple(item).label) }}</span>\n </button>\n </template>\n </div>\n </Teleport>\n</template>\n","/**\n * Sidebar configuration shapes.\n *\n * `renderContent` is gone: content comes from a `#tab-<id>` slot or a `component` field\n * (docs/decisions/0007-slots-over-render-props.md). Everything else keeps rdd's names.\n */\nimport type { Component, InjectionKey, Ref } from 'vue'\n\n/** One tab in the activity bar. */\nexport interface SidebarTab {\n id: string\n label: string\n /** Required unless `hidden` — a hidden tab renders no rail button, so it has no icon to show. */\n icon?: Component\n /**\n * Render no rail button, while the tab stays fully openable through `v-model:active-tab-id`\n * or `useSidebar().openTab()`. For menu-driven panels with no permanent icon.\n * @default false\n */\n hidden?: boolean\n /** Mount as soon as the sidebar renders, not on first open. Implies `preserveState`. @default false */\n eagerMount?: boolean\n /** Keep the content alive behind `display: none` when closed, instead of unmounting it. @default false */\n preserveState?: boolean\n /** Content, when it comes from data rather than a slot. A `#tab-<id>` slot wins over this. */\n component?: Component\n /** Props for `component`. */\n props?: Record<string, unknown>\n}\n\n/** A rail button that does not toggle the drawer — a hamburger, say. */\nexport interface SidebarActionButton {\n /** Needed only inside an array, for the key. */\n id?: string\n icon: Component\n /** Tooltip and accessible name. */\n label: string\n onClick: () => void\n disabled?: boolean\n}\n\n/** A rail entry the caller renders entirely themselves. */\nexport interface SidebarCustomEntry {\n id?: string\n /** Rendered as-is, unwrapped, so the caller's own styling and behaviour are untouched. */\n component: Component\n props?: Record<string, unknown>\n /** Marks this as a custom entry rather than a tab, since both carry `component`. */\n custom: true\n}\n\n/**\n * An entry in the header or footer area of the rail: a plain button, a caller-rendered\n * component, or a real tab that behaves exactly like one from the main list.\n */\nexport type SidebarRailEntry = SidebarTab | SidebarActionButton | SidebarCustomEntry\n\nexport const isCustomEntry = (entry: SidebarRailEntry): entry is SidebarCustomEntry =>\n 'custom' in entry && entry.custom === true\n\nexport const isTabEntry = (entry: SidebarRailEntry): entry is SidebarTab =>\n !isCustomEntry(entry) && !('onClick' in entry)\n\nexport const isActionButton = (entry: SidebarRailEntry): entry is SidebarActionButton =>\n !isCustomEntry(entry) && 'onClick' in entry\n\n/** Normalise the single-or-array shape both areas accept. */\nexport const toRailArray = (value: SidebarRailEntry | SidebarRailEntry[] | undefined): SidebarRailEntry[] =>\n value == null ? [] : Array.isArray(value) ? value : [value]\n\n/**\n * `<VddSidebar>`'s props, declared here so `<VddSecondarySidebar>` can forward them with\n * their types intact. Forwarding through `$attrs` alone works at runtime but throws the\n * types away, which is most of the point of declaring them.\n *\n * The four two-way values — the open tab, visibility, strip visibility and width — are\n * models rather than props, so they are not listed here.\n */\nexport interface SidebarProps {\n /** Which edge the bar and drawer sit on. @default 'right' */\n position?: 'left' | 'right'\n tabs: SidebarTab[]\n /** Entries above the tabs, in their own area. A single entry or an array. */\n headerAction?: SidebarRailEntry | SidebarRailEntry[]\n /** Entries pinned below the tabs. */\n footerAction?: SidebarRailEntry | SidebarRailEntry[]\n minWidth?: number\n maxWidth?: number\n /** Show an \"X\" in the drawer header. No effect once the default header is suppressed. @default false */\n showCloseButton?: boolean\n /** Suppress the library's own drawer header for every tab. @default false */\n hideDefaultHeader?: boolean\n /** @internal set by `<VddSecondarySidebar>`. */\n isSecondary?: boolean\n}\n\n// ── context shared with descendants ─────────────────────────────────────────\n\n/** What `useSidebar()` returns: control of the drawer from anywhere inside it. */\nexport interface SidebarContext {\n openTab: (id: string) => void\n closeDrawer: () => void\n /** The open tab, or `null`. Reactive. */\n activeTabId: Ref<string | null>\n position: 'left' | 'right'\n /** True for a `<VddSecondarySidebar>`. */\n isSecondary: boolean\n}\n\n/** What `useSidebarTab()` returns: control scoped to the tab you are inside. */\nexport interface SidebarTabContext {\n tabId: string\n open: () => void\n close: () => void\n /** Switch to another tab. */\n openTab: (id: string) => void\n}\n\nexport const SIDEBAR_KEY = Symbol('vdd-sidebar') as InjectionKey<SidebarContext>\nexport const SIDEBAR_TAB_KEY = Symbol('vdd-sidebar-tab') as InjectionKey<SidebarTabContext>\n","<script setup lang=\"ts\">\n/**\n * The activity strip: header entries, the tab buttons, footer entries.\n *\n * Its own component so the left and right arrangements share one implementation. Writing it\n * twice inside the parent is how the two sides drift apart.\n */\nimport type { SidebarRailEntry, SidebarTab } from '../core/sidebarTypes'\nimport { isActionButton, isCustomEntry } from '../core/sidebarTypes'\n\nconst props = defineProps<{\n tabs: SidebarTab[]\n header: SidebarRailEntry[]\n footer: SidebarRailEntry[]\n activeTabId: string | null\n position: 'left' | 'right'\n visible: boolean\n}>()\n\nconst emit = defineEmits<{ select: [id: string] }>()\n\n/** The strip's fixed width. The outer box animates between this and zero. */\nconst STRIP_PX = 56\nconst shownTabs = (list: SidebarTab[]) => list.filter(t => !t.hidden)\nvoid props\n</script>\n\n<template>\n <div\n class=\"vdd-sidebar-strip-outer\"\n :style=\"{ width: visible ? `${STRIP_PX}px` : '0px' }\"\n data-vdd-sidebar-strip\n >\n <div\n class=\"vdd-sidebar-tabs-strip\"\n :class=\"[`vdd-${position}`, {\n 'vdd-sidebar-tabs-strip--has-header-action': header.length > 0,\n 'vdd-sidebar-tabs-strip--has-footer-action': footer.length > 0,\n }]\"\n :style=\"{ width: `${STRIP_PX}px` }\"\n >\n <template v-for=\"area in (['header', 'tabs', 'footer'] as const)\" :key=\"area\">\n <div v-if=\"area === 'tabs'\" class=\"vdd-sidebar-tabs-list\">\n <button\n v-for=\"tab in shownTabs(tabs)\"\n :key=\"tab.id\"\n type=\"button\"\n class=\"vdd-sidebar-tab-btn\"\n :class=\"{ 'vdd-active': activeTabId === tab.id }\"\n :title=\"tab.label\"\n :aria-pressed=\"activeTabId === tab.id\"\n :data-vdd-sidebar-tab=\"tab.id\"\n @click=\"emit('select', tab.id)\"\n ><component :is=\"tab.icon\" /></button>\n </div>\n\n <div\n v-else-if=\"(area === 'header' ? header : footer).length\"\n :class=\"area === 'header' ? 'vdd-sidebar-header-area' : 'vdd-sidebar-footer-area'\"\n >\n <template v-for=\"(entry, index) in (area === 'header' ? header : footer)\" :key=\"index\">\n <!-- a caller-rendered entry goes out exactly as given, unwrapped -->\n <component :is=\"entry.component\" v-if=\"isCustomEntry(entry)\" v-bind=\"entry.props\" />\n\n <button\n v-else-if=\"isActionButton(entry)\"\n type=\"button\"\n class=\"vdd-sidebar-tab-btn vdd-sidebar-header-action-btn\"\n :disabled=\"entry.disabled\"\n :title=\"entry.label\"\n :aria-label=\"entry.label\"\n :data-vdd-rail-action=\"entry.id ?? entry.label\"\n @click=\"entry.onClick()\"\n ><component :is=\"entry.icon\" /></button>\n\n <!-- a tab pinned to an area behaves exactly like one from the main list -->\n <button\n v-else-if=\"!entry.hidden\"\n type=\"button\"\n class=\"vdd-sidebar-tab-btn\"\n :class=\"{ 'vdd-active': activeTabId === entry.id }\"\n :title=\"entry.label\"\n :aria-pressed=\"activeTabId === entry.id\"\n :data-vdd-sidebar-tab=\"entry.id\"\n @click=\"emit('select', entry.id)\"\n ><component :is=\"entry.icon\" /></button>\n </template>\n </div>\n </template>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/**\n * The activity strip: header entries, the tab buttons, footer entries.\n *\n * Its own component so the left and right arrangements share one implementation. Writing it\n * twice inside the parent is how the two sides drift apart.\n */\nimport type { SidebarRailEntry, SidebarTab } from '../core/sidebarTypes'\nimport { isActionButton, isCustomEntry } from '../core/sidebarTypes'\n\nconst props = defineProps<{\n tabs: SidebarTab[]\n header: SidebarRailEntry[]\n footer: SidebarRailEntry[]\n activeTabId: string | null\n position: 'left' | 'right'\n visible: boolean\n}>()\n\nconst emit = defineEmits<{ select: [id: string] }>()\n\n/** The strip's fixed width. The outer box animates between this and zero. */\nconst STRIP_PX = 56\nconst shownTabs = (list: SidebarTab[]) => list.filter(t => !t.hidden)\nvoid props\n</script>\n\n<template>\n <div\n class=\"vdd-sidebar-strip-outer\"\n :style=\"{ width: visible ? `${STRIP_PX}px` : '0px' }\"\n data-vdd-sidebar-strip\n >\n <div\n class=\"vdd-sidebar-tabs-strip\"\n :class=\"[`vdd-${position}`, {\n 'vdd-sidebar-tabs-strip--has-header-action': header.length > 0,\n 'vdd-sidebar-tabs-strip--has-footer-action': footer.length > 0,\n }]\"\n :style=\"{ width: `${STRIP_PX}px` }\"\n >\n <template v-for=\"area in (['header', 'tabs', 'footer'] as const)\" :key=\"area\">\n <div v-if=\"area === 'tabs'\" class=\"vdd-sidebar-tabs-list\">\n <button\n v-for=\"tab in shownTabs(tabs)\"\n :key=\"tab.id\"\n type=\"button\"\n class=\"vdd-sidebar-tab-btn\"\n :class=\"{ 'vdd-active': activeTabId === tab.id }\"\n :title=\"tab.label\"\n :aria-pressed=\"activeTabId === tab.id\"\n :data-vdd-sidebar-tab=\"tab.id\"\n @click=\"emit('select', tab.id)\"\n ><component :is=\"tab.icon\" /></button>\n </div>\n\n <div\n v-else-if=\"(area === 'header' ? header : footer).length\"\n :class=\"area === 'header' ? 'vdd-sidebar-header-area' : 'vdd-sidebar-footer-area'\"\n >\n <template v-for=\"(entry, index) in (area === 'header' ? header : footer)\" :key=\"index\">\n <!-- a caller-rendered entry goes out exactly as given, unwrapped -->\n <component :is=\"entry.component\" v-if=\"isCustomEntry(entry)\" v-bind=\"entry.props\" />\n\n <button\n v-else-if=\"isActionButton(entry)\"\n type=\"button\"\n class=\"vdd-sidebar-tab-btn vdd-sidebar-header-action-btn\"\n :disabled=\"entry.disabled\"\n :title=\"entry.label\"\n :aria-label=\"entry.label\"\n :data-vdd-rail-action=\"entry.id ?? entry.label\"\n @click=\"entry.onClick()\"\n ><component :is=\"entry.icon\" /></button>\n\n <!-- a tab pinned to an area behaves exactly like one from the main list -->\n <button\n v-else-if=\"!entry.hidden\"\n type=\"button\"\n class=\"vdd-sidebar-tab-btn\"\n :class=\"{ 'vdd-active': activeTabId === entry.id }\"\n :title=\"entry.label\"\n :aria-pressed=\"activeTabId === entry.id\"\n :data-vdd-sidebar-tab=\"entry.id\"\n @click=\"emit('select', entry.id)\"\n ><component :is=\"entry.icon\" /></button>\n </template>\n </div>\n </template>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/**\n * Provides one tab's context to its content.\n *\n * A component of its own because `provide` is per component instance: the drawer renders\n * every mounted pane inside a single instance, so it cannot provide a different tab id to\n * each of them.\n */\nimport { provide } from 'vue'\nimport { SIDEBAR_TAB_KEY } from '../core/sidebarTypes'\n\nconst props = defineProps<{ tabId: string }>()\nconst emit = defineEmits<{ close: []; open: [id: string] }>()\n\nprovide(SIDEBAR_TAB_KEY, {\n tabId: props.tabId,\n open: () => emit('open', props.tabId),\n close: () => emit('close'),\n openTab: (id: string) => emit('open', id),\n})\n</script>\n\n<template>\n <div><slot /></div>\n</template>\n","<script setup lang=\"ts\">\n/**\n * Provides one tab's context to its content.\n *\n * A component of its own because `provide` is per component instance: the drawer renders\n * every mounted pane inside a single instance, so it cannot provide a different tab id to\n * each of them.\n */\nimport { provide } from 'vue'\nimport { SIDEBAR_TAB_KEY } from '../core/sidebarTypes'\n\nconst props = defineProps<{ tabId: string }>()\nconst emit = defineEmits<{ close: []; open: [id: string] }>()\n\nprovide(SIDEBAR_TAB_KEY, {\n tabId: props.tabId,\n open: () => emit('open', props.tabId),\n close: () => emit('close'),\n openTab: (id: string) => emit('open', id),\n})\n</script>\n\n<template>\n <div><slot /></div>\n</template>\n","<script setup lang=\"ts\">\n/**\n * The drawer: one pane per mounted tab, only the open one displayed.\n *\n * Panes are hidden with `display: none` rather than unmounted, which is what makes\n * `preserveState` mean anything — and why a tab that asked for neither `eagerMount` nor\n * `preserveState` is dropped from the mounted set when the drawer closes.\n */\nimport type { SidebarTab } from '../core/sidebarTypes'\nimport VddSidebarTabScope from './VddSidebarTabScope.vue'\n\nconst props = defineProps<{\n tabs: SidebarTab[]\n activeTabId: string | null\n position: 'left' | 'right'\n open: boolean\n width: number\n minWidth: number\n maxWidth: number\n /** Suppress the library's own header for every tab. */\n headerOverridden: boolean\n showCloseButton: boolean\n /** No transition while a resize drag is in flight, or the drawer lags the pointer. */\n resizing: boolean\n}>()\n\nconst emit = defineEmits<{ close: []; open: [id: string] }>()\nvoid props\n</script>\n\n<template>\n <div\n class=\"vdd-sidebar-content-drawer\"\n :class=\"`vdd-${position}`\"\n data-vdd-sidebar-drawer\n :style=\"{\n flexBasis: open ? `${width}px` : '0px',\n minWidth: open ? `${minWidth}px` : '0px',\n maxWidth: open ? `${maxWidth}px` : '0px',\n ...(resizing ? { transition: 'none' } : {}),\n }\"\n >\n <div\n v-for=\"tab in tabs\"\n :key=\"tab.id\"\n class=\"vdd-sidebar-drawer-pane\"\n :style=\"{ display: activeTabId === tab.id ? 'flex' : 'none' }\"\n :data-vdd-sidebar-pane=\"tab.id\"\n >\n <slot v-if=\"headerOverridden\" name=\"header\" :tab=\"tab\" :close=\"() => emit('close')\" :open=\"() => emit('open', tab.id)\" />\n <div v-else class=\"vdd-sidebar-drawer-header\">\n <span class=\"vdd-sidebar-header-title\">{{ tab.label }}</span>\n <button\n v-if=\"showCloseButton\"\n type=\"button\"\n class=\"vdd-sidebar-drawer-close-button\"\n data-vdd-sidebar-close\n title=\"Close\"\n aria-label=\"Close\"\n @click=\"emit('close')\"\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\">\n <path d=\"M18 6L6 18M6 6l12 12\" />\n </svg>\n </button>\n </div>\n\n <!--\n Each pane provides its own tab context, so `useSidebarTab()` inside a tab's content\n knows which tab it is without being told. A wrapper component is needed because\n `provide` is per component instance, and these panes are siblings in one.\n -->\n <VddSidebarTabScope\n class=\"vdd-sidebar-drawer-body\"\n :tab-id=\"tab.id\"\n @close=\"emit('close')\"\n @open=\"emit('open', $event)\"\n >\n <slot\n :name=\"`tab-${tab.id}`\"\n :tab=\"tab\"\n :close=\"() => emit('close')\"\n :open=\"() => emit('open', tab.id)\"\n >\n <component :is=\"tab.component\" v-if=\"tab.component\" v-bind=\"tab.props\" />\n </slot>\n </VddSidebarTabScope>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/**\n * The drawer: one pane per mounted tab, only the open one displayed.\n *\n * Panes are hidden with `display: none` rather than unmounted, which is what makes\n * `preserveState` mean anything — and why a tab that asked for neither `eagerMount` nor\n * `preserveState` is dropped from the mounted set when the drawer closes.\n */\nimport type { SidebarTab } from '../core/sidebarTypes'\nimport VddSidebarTabScope from './VddSidebarTabScope.vue'\n\nconst props = defineProps<{\n tabs: SidebarTab[]\n activeTabId: string | null\n position: 'left' | 'right'\n open: boolean\n width: number\n minWidth: number\n maxWidth: number\n /** Suppress the library's own header for every tab. */\n headerOverridden: boolean\n showCloseButton: boolean\n /** No transition while a resize drag is in flight, or the drawer lags the pointer. */\n resizing: boolean\n}>()\n\nconst emit = defineEmits<{ close: []; open: [id: string] }>()\nvoid props\n</script>\n\n<template>\n <div\n class=\"vdd-sidebar-content-drawer\"\n :class=\"`vdd-${position}`\"\n data-vdd-sidebar-drawer\n :style=\"{\n flexBasis: open ? `${width}px` : '0px',\n minWidth: open ? `${minWidth}px` : '0px',\n maxWidth: open ? `${maxWidth}px` : '0px',\n ...(resizing ? { transition: 'none' } : {}),\n }\"\n >\n <div\n v-for=\"tab in tabs\"\n :key=\"tab.id\"\n class=\"vdd-sidebar-drawer-pane\"\n :style=\"{ display: activeTabId === tab.id ? 'flex' : 'none' }\"\n :data-vdd-sidebar-pane=\"tab.id\"\n >\n <slot v-if=\"headerOverridden\" name=\"header\" :tab=\"tab\" :close=\"() => emit('close')\" :open=\"() => emit('open', tab.id)\" />\n <div v-else class=\"vdd-sidebar-drawer-header\">\n <span class=\"vdd-sidebar-header-title\">{{ tab.label }}</span>\n <button\n v-if=\"showCloseButton\"\n type=\"button\"\n class=\"vdd-sidebar-drawer-close-button\"\n data-vdd-sidebar-close\n title=\"Close\"\n aria-label=\"Close\"\n @click=\"emit('close')\"\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\">\n <path d=\"M18 6L6 18M6 6l12 12\" />\n </svg>\n </button>\n </div>\n\n <!--\n Each pane provides its own tab context, so `useSidebarTab()` inside a tab's content\n knows which tab it is without being told. A wrapper component is needed because\n `provide` is per component instance, and these panes are siblings in one.\n -->\n <VddSidebarTabScope\n class=\"vdd-sidebar-drawer-body\"\n :tab-id=\"tab.id\"\n @close=\"emit('close')\"\n @open=\"emit('open', $event)\"\n >\n <slot\n :name=\"`tab-${tab.id}`\"\n :tab=\"tab\"\n :close=\"() => emit('close')\"\n :open=\"() => emit('open', tab.id)\"\n >\n <component :is=\"tab.component\" v-if=\"tab.component\" v-bind=\"tab.props\" />\n </slot>\n </VddSidebarTabScope>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/**\n * The activity bar and its resizable drawer.\n *\n * rdd exposed eight imperative methods for this through a ref — `openTab`, `closeDrawer`,\n * `getActiveTab`, `show`, `hide`, `toggle`, `setWidth`, `getWidth` — because React cannot\n * express two-way props. Every one of them was a getter or setter for state, so here they\n * are four models: `active-tab-id`, `visible`, `strip-visible`, `width`\n * (docs/decisions/0005-vmodel.md). Binding a model makes the caller the source of truth;\n * omitting it lets the component keep its own.\n */\nimport { computed, provide, ref, watch } from 'vue'\nimport type { SidebarProps, SidebarTab } from '../core/sidebarTypes'\nimport { SIDEBAR_KEY, isTabEntry, toRailArray } from '../core/sidebarTypes'\nimport { startPointerDrag } from '../core/dragResize'\nimport VddSidebarRail from './VddSidebarRail.vue'\nimport VddSidebarDrawer from './VddSidebarDrawer.vue'\n\nconst props = withDefaults(defineProps<SidebarProps>(), {\n position: 'right',\n minWidth: 150,\n maxWidth: 600,\n showCloseButton: false,\n hideDefaultHeader: false,\n isSecondary: false,\n})\n\nconst slots = defineSlots<{\n default?: () => unknown\n /** Replaces the library's drawer header, for whichever tab is open. */\n header?: (props: { tab: SidebarTab; close: () => void; open: () => void }) => unknown\n /** Content for one tab, by id: `#tab-layers`. */\n [key: `tab-${string}`]: (props: { tab: SidebarTab; close: () => void; open: () => void }) => unknown\n}>()\n\n/** The open tab, or `null` when the drawer is closed. */\nconst activeTabId = defineModel<string | null>('activeTabId', { default: null })\n/** Whether the whole sidebar — strip and drawer — is shown. */\nconst visible = defineModel<boolean>('visible', { default: true })\n/** Whether the activity strip is shown. The drawer is unaffected. */\nconst stripVisible = defineModel<boolean>('stripVisible', { default: true })\n/** Drawer width in pixels. */\nconst width = defineModel<number>('width', { default: 280 })\n\nconst headerEntries = computed(() => toRailArray(props.headerAction))\nconst footerEntries = computed(() => toRailArray(props.footerAction))\n/** A header or footer tab behaves exactly like a main-list tab, so they share one list. */\nconst allTabs = computed<SidebarTab[]>(() => [\n ...headerEntries.value.filter(isTabEntry),\n ...props.tabs,\n ...footerEntries.value.filter(isTabEntry),\n])\n\n/** Tabs mounted at least once, for lazy mounting and `preserveState`. */\nconst everMounted = ref(new Set<string>())\n\n/**\n * Which tabs are currently mounted.\n *\n * Derived rather than tracked in an effect: the accumulated set, plus the open tab (which\n * covers a controlled model changing without going through `openTab`), plus every eager tab.\n */\nconst mountedTabs = computed(() => {\n const result = new Set(everMounted.value)\n if (activeTabId.value) result.add(activeTabId.value)\n for (const tab of allTabs.value) if (tab.eagerMount) result.add(tab.id)\n return result\n})\n\nfunction setActive(id: string | null): void {\n if (id !== null) {\n if (!everMounted.value.has(id)) {\n const next = new Set(everMounted.value)\n next.add(id)\n everMounted.value = next\n }\n } else {\n // Closing: drop tabs that asked for neither eager mounting nor preserved state.\n const next = new Set(everMounted.value)\n let changed = false\n for (const id2 of everMounted.value) {\n const tab = allTabs.value.find(t => t.id === id2)\n if (tab && !tab.eagerMount && !tab.preserveState) { next.delete(id2); changed = true }\n }\n if (changed) everMounted.value = next\n }\n activeTabId.value = id\n}\n\n/**\n * If the open tab stops existing — its contributing panel closed, or the list changed — close\n * the drawer rather than leaving it open and empty with no button left to close it. Never\n * silently fall back to a different tab the user did not choose.\n */\nwatch([activeTabId, allTabs], ([id, tabs]) => {\n if (id != null && !tabs.some(t => t.id === id)) setActive(null)\n}, { immediate: true })\n\n/** Mounted tabs, in rail order, for the drawer. */\nconst mountedList = computed(() => allTabs.value.filter(t => mountedTabs.value.has(t.id)))\n\n\nconst isVisible = computed(() => visible.value !== false)\nconst isStripVisible = computed(() => isVisible.value && stripVisible.value !== false)\nconst isOpen = computed(() => isVisible.value && activeTabId.value != null)\nconst openTab = computed(() => allTabs.value.find(t => t.id === activeTabId.value) ?? null)\n\n/** A `#header` slot suppresses the default header on its own, as does the prop. */\nconst headerOverridden = computed(() => props.hideDefaultHeader || slots.header !== undefined)\n\nconst toggleTab = (id: string) => setActive(activeTabId.value === id ? null : id)\nconst close = () => setActive(null)\n\n// ── resizing ───────────────────────────────────────────────────────────────\nconst resizing = ref(false)\n\nfunction onResizeDown(event: PointerEvent): void {\n event.preventDefault()\n const el = event.currentTarget as HTMLElement\n resizing.value = true\n startPointerDrag({\n element: el,\n pointerId: event.pointerId,\n startClientX: event.clientX,\n startClientY: event.clientY,\n captureStart: () => width.value,\n activeClasses: [\n { el, classes: ['vdd-active'] },\n { el: document.body, classes: ['vdd-resizing-active', 'vdd-resizing-col-active'] },\n ],\n // A right-hand drawer grows when the pointer moves left, so the delta is inverted.\n onMove: (dx, _dy, start) => {\n const next = props.position === 'right' ? start - dx : start + dx\n width.value = Math.max(props.minWidth, Math.min(props.maxWidth, next))\n },\n onEnd: () => { resizing.value = false },\n })\n}\n\n// ── context for descendants ────────────────────────────────────────────────\nprovide(SIDEBAR_KEY, {\n openTab: (id: string) => setActive(id),\n closeDrawer: close,\n activeTabId,\n position: props.position,\n isSecondary: props.isSecondary,\n})\n\nconst dev = process.env.NODE_ENV !== 'production'\nlet warnedCloseButton = false\nwatch([() => props.showCloseButton, headerOverridden], ([show, overridden]) => {\n if (!dev || !show || !overridden || warnedCloseButton) return\n warnedCloseButton = true\n console.warn(\n '[vue-dockable-desktop] `showCloseButton` has no effect because the default drawer header ' +\n 'is suppressed (`hideDefaultHeader` is set, or a #header slot was provided). The button is ' +\n 'part of that header. Put your own close control in the #header slot, wired to its `close`.',\n )\n}, { immediate: true })\n\nlet warnedMissingContent = false\nwatch(openTab, (tab) => {\n if (!dev || !tab || warnedMissingContent) return\n if (slots[`tab-${tab.id}`] || tab.component) return\n warnedMissingContent = true\n console.warn(\n `[vue-dockable-desktop] Sidebar tab \"${tab.id}\" has no content: give it a #tab-${tab.id} ` +\n `slot or a \\`component\\`. The drawer will open empty.`,\n )\n})\n</script>\n\n<template>\n <div class=\"vdd-sidebar-layout\" :data-vdd-sidebar=\"position\">\n <!--\n Left and right are the same three pieces in opposite order. Rendered from one list so\n neither arrangement can drift from the other.\n -->\n <template v-for=\"piece in position === 'left' ? ['rail', 'drawer', 'resizer', 'main'] : ['main', 'resizer', 'drawer', 'rail']\" :key=\"piece\">\n <VddSidebarRail\n v-if=\"piece === 'rail'\"\n :tabs=\"tabs\"\n :header=\"headerEntries\"\n :footer=\"footerEntries\"\n :active-tab-id=\"activeTabId\"\n :position=\"position\"\n :visible=\"isStripVisible\"\n @select=\"toggleTab\"\n />\n\n <VddSidebarDrawer\n v-else-if=\"piece === 'drawer'\"\n :tabs=\"mountedList\"\n :active-tab-id=\"activeTabId\"\n :position=\"position\"\n :open=\"isOpen\"\n :width=\"width\"\n :min-width=\"minWidth\"\n :max-width=\"maxWidth\"\n :header-overridden=\"headerOverridden\"\n :show-close-button=\"showCloseButton\"\n :resizing=\"resizing\"\n @close=\"close\"\n @open=\"setActive\"\n >\n <template v-if=\"$slots.header\" #header=\"slotProps\">\n <slot name=\"header\" v-bind=\"slotProps\" />\n </template>\n <template v-for=\"tab in mountedList\" :key=\"tab.id\" #[`tab-${tab.id}`]=\"slotProps\">\n <slot :name=\"`tab-${tab.id}`\" v-bind=\"slotProps\" />\n </template>\n </VddSidebarDrawer>\n\n <div\n v-else-if=\"piece === 'resizer' && isOpen\"\n class=\"vdd-resizer-bar\"\n data-vdd-sidebar-resizer\n :style=\"{ cursor: 'col-resize', width: '1px', height: '100%', flexShrink: 0, zIndex: 20 }\"\n @pointerdown=\"onResizeDown\"\n />\n\n <div v-else-if=\"piece === 'main'\" class=\"vdd-sidebar-main\"><slot /></div>\n </template>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/**\n * The activity bar and its resizable drawer.\n *\n * rdd exposed eight imperative methods for this through a ref — `openTab`, `closeDrawer`,\n * `getActiveTab`, `show`, `hide`, `toggle`, `setWidth`, `getWidth` — because React cannot\n * express two-way props. Every one of them was a getter or setter for state, so here they\n * are four models: `active-tab-id`, `visible`, `strip-visible`, `width`\n * (docs/decisions/0005-vmodel.md). Binding a model makes the caller the source of truth;\n * omitting it lets the component keep its own.\n */\nimport { computed, provide, ref, watch } from 'vue'\nimport type { SidebarProps, SidebarTab } from '../core/sidebarTypes'\nimport { SIDEBAR_KEY, isTabEntry, toRailArray } from '../core/sidebarTypes'\nimport { startPointerDrag } from '../core/dragResize'\nimport VddSidebarRail from './VddSidebarRail.vue'\nimport VddSidebarDrawer from './VddSidebarDrawer.vue'\n\nconst props = withDefaults(defineProps<SidebarProps>(), {\n position: 'right',\n minWidth: 150,\n maxWidth: 600,\n showCloseButton: false,\n hideDefaultHeader: false,\n isSecondary: false,\n})\n\nconst slots = defineSlots<{\n default?: () => unknown\n /** Replaces the library's drawer header, for whichever tab is open. */\n header?: (props: { tab: SidebarTab; close: () => void; open: () => void }) => unknown\n /** Content for one tab, by id: `#tab-layers`. */\n [key: `tab-${string}`]: (props: { tab: SidebarTab; close: () => void; open: () => void }) => unknown\n}>()\n\n/** The open tab, or `null` when the drawer is closed. */\nconst activeTabId = defineModel<string | null>('activeTabId', { default: null })\n/** Whether the whole sidebar — strip and drawer — is shown. */\nconst visible = defineModel<boolean>('visible', { default: true })\n/** Whether the activity strip is shown. The drawer is unaffected. */\nconst stripVisible = defineModel<boolean>('stripVisible', { default: true })\n/** Drawer width in pixels. */\nconst width = defineModel<number>('width', { default: 280 })\n\nconst headerEntries = computed(() => toRailArray(props.headerAction))\nconst footerEntries = computed(() => toRailArray(props.footerAction))\n/** A header or footer tab behaves exactly like a main-list tab, so they share one list. */\nconst allTabs = computed<SidebarTab[]>(() => [\n ...headerEntries.value.filter(isTabEntry),\n ...props.tabs,\n ...footerEntries.value.filter(isTabEntry),\n])\n\n/** Tabs mounted at least once, for lazy mounting and `preserveState`. */\nconst everMounted = ref(new Set<string>())\n\n/**\n * Which tabs are currently mounted.\n *\n * Derived rather than tracked in an effect: the accumulated set, plus the open tab (which\n * covers a controlled model changing without going through `openTab`), plus every eager tab.\n */\nconst mountedTabs = computed(() => {\n const result = new Set(everMounted.value)\n if (activeTabId.value) result.add(activeTabId.value)\n for (const tab of allTabs.value) if (tab.eagerMount) result.add(tab.id)\n return result\n})\n\nfunction setActive(id: string | null): void {\n if (id !== null) {\n if (!everMounted.value.has(id)) {\n const next = new Set(everMounted.value)\n next.add(id)\n everMounted.value = next\n }\n } else {\n // Closing: drop tabs that asked for neither eager mounting nor preserved state.\n const next = new Set(everMounted.value)\n let changed = false\n for (const id2 of everMounted.value) {\n const tab = allTabs.value.find(t => t.id === id2)\n if (tab && !tab.eagerMount && !tab.preserveState) { next.delete(id2); changed = true }\n }\n if (changed) everMounted.value = next\n }\n activeTabId.value = id\n}\n\n/**\n * If the open tab stops existing — its contributing panel closed, or the list changed — close\n * the drawer rather than leaving it open and empty with no button left to close it. Never\n * silently fall back to a different tab the user did not choose.\n */\nwatch([activeTabId, allTabs], ([id, tabs]) => {\n if (id != null && !tabs.some(t => t.id === id)) setActive(null)\n}, { immediate: true })\n\n/** Mounted tabs, in rail order, for the drawer. */\nconst mountedList = computed(() => allTabs.value.filter(t => mountedTabs.value.has(t.id)))\n\n\nconst isVisible = computed(() => visible.value !== false)\nconst isStripVisible = computed(() => isVisible.value && stripVisible.value !== false)\nconst isOpen = computed(() => isVisible.value && activeTabId.value != null)\nconst openTab = computed(() => allTabs.value.find(t => t.id === activeTabId.value) ?? null)\n\n/** A `#header` slot suppresses the default header on its own, as does the prop. */\nconst headerOverridden = computed(() => props.hideDefaultHeader || slots.header !== undefined)\n\nconst toggleTab = (id: string) => setActive(activeTabId.value === id ? null : id)\nconst close = () => setActive(null)\n\n// ── resizing ───────────────────────────────────────────────────────────────\nconst resizing = ref(false)\n\nfunction onResizeDown(event: PointerEvent): void {\n event.preventDefault()\n const el = event.currentTarget as HTMLElement\n resizing.value = true\n startPointerDrag({\n element: el,\n pointerId: event.pointerId,\n startClientX: event.clientX,\n startClientY: event.clientY,\n captureStart: () => width.value,\n activeClasses: [\n { el, classes: ['vdd-active'] },\n { el: document.body, classes: ['vdd-resizing-active', 'vdd-resizing-col-active'] },\n ],\n // A right-hand drawer grows when the pointer moves left, so the delta is inverted.\n onMove: (dx, _dy, start) => {\n const next = props.position === 'right' ? start - dx : start + dx\n width.value = Math.max(props.minWidth, Math.min(props.maxWidth, next))\n },\n onEnd: () => { resizing.value = false },\n })\n}\n\n// ── context for descendants ────────────────────────────────────────────────\nprovide(SIDEBAR_KEY, {\n openTab: (id: string) => setActive(id),\n closeDrawer: close,\n activeTabId,\n position: props.position,\n isSecondary: props.isSecondary,\n})\n\nconst dev = process.env.NODE_ENV !== 'production'\nlet warnedCloseButton = false\nwatch([() => props.showCloseButton, headerOverridden], ([show, overridden]) => {\n if (!dev || !show || !overridden || warnedCloseButton) return\n warnedCloseButton = true\n console.warn(\n '[vue-dockable-desktop] `showCloseButton` has no effect because the default drawer header ' +\n 'is suppressed (`hideDefaultHeader` is set, or a #header slot was provided). The button is ' +\n 'part of that header. Put your own close control in the #header slot, wired to its `close`.',\n )\n}, { immediate: true })\n\nlet warnedMissingContent = false\nwatch(openTab, (tab) => {\n if (!dev || !tab || warnedMissingContent) return\n if (slots[`tab-${tab.id}`] || tab.component) return\n warnedMissingContent = true\n console.warn(\n `[vue-dockable-desktop] Sidebar tab \"${tab.id}\" has no content: give it a #tab-${tab.id} ` +\n `slot or a \\`component\\`. The drawer will open empty.`,\n )\n})\n</script>\n\n<template>\n <div class=\"vdd-sidebar-layout\" :data-vdd-sidebar=\"position\">\n <!--\n Left and right are the same three pieces in opposite order. Rendered from one list so\n neither arrangement can drift from the other.\n -->\n <template v-for=\"piece in position === 'left' ? ['rail', 'drawer', 'resizer', 'main'] : ['main', 'resizer', 'drawer', 'rail']\" :key=\"piece\">\n <VddSidebarRail\n v-if=\"piece === 'rail'\"\n :tabs=\"tabs\"\n :header=\"headerEntries\"\n :footer=\"footerEntries\"\n :active-tab-id=\"activeTabId\"\n :position=\"position\"\n :visible=\"isStripVisible\"\n @select=\"toggleTab\"\n />\n\n <VddSidebarDrawer\n v-else-if=\"piece === 'drawer'\"\n :tabs=\"mountedList\"\n :active-tab-id=\"activeTabId\"\n :position=\"position\"\n :open=\"isOpen\"\n :width=\"width\"\n :min-width=\"minWidth\"\n :max-width=\"maxWidth\"\n :header-overridden=\"headerOverridden\"\n :show-close-button=\"showCloseButton\"\n :resizing=\"resizing\"\n @close=\"close\"\n @open=\"setActive\"\n >\n <template v-if=\"$slots.header\" #header=\"slotProps\">\n <slot name=\"header\" v-bind=\"slotProps\" />\n </template>\n <template v-for=\"tab in mountedList\" :key=\"tab.id\" #[`tab-${tab.id}`]=\"slotProps\">\n <slot :name=\"`tab-${tab.id}`\" v-bind=\"slotProps\" />\n </template>\n </VddSidebarDrawer>\n\n <div\n v-else-if=\"piece === 'resizer' && isOpen\"\n class=\"vdd-resizer-bar\"\n data-vdd-sidebar-resizer\n :style=\"{ cursor: 'col-resize', width: '1px', height: '100%', flexShrink: 0, zIndex: 20 }\"\n @pointerdown=\"onResizeDown\"\n />\n\n <div v-else-if=\"piece === 'main'\" class=\"vdd-sidebar-main\"><slot /></div>\n </template>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/**\n * A second, independent sidebar on the opposite edge — the same component, no forked code.\n *\n * Must be rendered inside a primary `<VddSidebar>`'s default slot; it takes whichever side\n * that primary is not using, so the side is never specified directly.\n */\nimport { computed, inject } from 'vue'\nimport { SIDEBAR_KEY } from '../core/sidebarTypes'\nimport type { SidebarProps } from '../core/sidebarTypes'\nimport VddSidebar from './VddSidebar.vue'\n\n/** Everything the primary accepts, except the two it decides for itself. */\nconst props = defineProps<Omit<SidebarProps, 'position' | 'isSecondary'>>()\n\nconst primary = inject(SIDEBAR_KEY, null)\nif (!primary) {\n throw new Error(\n '[vue-dockable-desktop] <VddSecondarySidebar> must be rendered inside a <VddSidebar>. ' +\n 'It takes the edge the primary sidebar is not using, so it needs one to be nested in.',\n )\n}\nif (primary.isSecondary) {\n throw new Error(\n '[vue-dockable-desktop] <VddSecondarySidebar> cannot be nested inside another one. ' +\n 'The library supports one primary and one secondary sidebar, nothing deeper.',\n )\n}\n\nconst opposite = computed<'left' | 'right'>(() => (primary!.position === 'left' ? 'right' : 'left'))\n</script>\n\n<template>\n <!-- `$attrs` carries the model listeners (`onUpdate:activeTabId` and friends) through. -->\n <VddSidebar v-bind=\"{ ...props, ...$attrs }\" :position=\"opposite\" is-secondary>\n <template v-for=\"(_, name) in $slots\" :key=\"name\" #[name]=\"slotProps\">\n <slot :name=\"name\" v-bind=\"slotProps ?? {}\" />\n </template>\n </VddSidebar>\n</template>\n","<script setup lang=\"ts\">\n/**\n * A second, independent sidebar on the opposite edge — the same component, no forked code.\n *\n * Must be rendered inside a primary `<VddSidebar>`'s default slot; it takes whichever side\n * that primary is not using, so the side is never specified directly.\n */\nimport { computed, inject } from 'vue'\nimport { SIDEBAR_KEY } from '../core/sidebarTypes'\nimport type { SidebarProps } from '../core/sidebarTypes'\nimport VddSidebar from './VddSidebar.vue'\n\n/** Everything the primary accepts, except the two it decides for itself. */\nconst props = defineProps<Omit<SidebarProps, 'position' | 'isSecondary'>>()\n\nconst primary = inject(SIDEBAR_KEY, null)\nif (!primary) {\n throw new Error(\n '[vue-dockable-desktop] <VddSecondarySidebar> must be rendered inside a <VddSidebar>. ' +\n 'It takes the edge the primary sidebar is not using, so it needs one to be nested in.',\n )\n}\nif (primary.isSecondary) {\n throw new Error(\n '[vue-dockable-desktop] <VddSecondarySidebar> cannot be nested inside another one. ' +\n 'The library supports one primary and one secondary sidebar, nothing deeper.',\n )\n}\n\nconst opposite = computed<'left' | 'right'>(() => (primary!.position === 'left' ? 'right' : 'left'))\n</script>\n\n<template>\n <!-- `$attrs` carries the model listeners (`onUpdate:activeTabId` and friends) through. -->\n <VddSidebar v-bind=\"{ ...props, ...$attrs }\" :position=\"opposite\" is-secondary>\n <template v-for=\"(_, name) in $slots\" :key=\"name\" #[name]=\"slotProps\">\n <slot :name=\"name\" v-bind=\"slotProps ?? {}\" />\n </template>\n </VddSidebar>\n</template>\n","/**\n * Toolbar item shapes.\n *\n * Kept as data, with rdd's field names, so a React app's `items` array transfers verbatim —\n * a toolbar is configuration, not markup.\n */\nimport type { Component } from 'vue'\n\n/** A one-shot button. */\nexport interface ToolbarActionItem {\n type: 'action'\n id: string\n label: string\n icon: Component\n onClick: () => void\n disabled?: boolean\n}\n\n/** One of a set, of which exactly one is active. */\nexport interface ToolbarRadioItem {\n type: 'radio'\n id: string\n /** Items sharing a group are mutually exclusive. */\n group: string\n label: string\n icon: Component\n /** Shown in a group flyout; reserved for a richer tooltip. */\n shortcut?: string\n onActivate?: (id: string) => void\n disabled?: boolean\n}\n\n/**\n * An independent on/off modifier — snap-to-grid, say.\n *\n * Uncontrolled by default, with the state kept on the workspace and keyed by `id`. Supply\n * `active` and the caller becomes the source of truth, which is what lets two instances of\n * the same panel type report independent state instead of colliding on a shared id.\n */\nexport interface ToolbarToggleItem {\n type: 'toggle'\n id: string\n label: string\n icon: Component\n shortcut?: string\n /** Controlled state. Supplying it — **including as `false`** — makes the caller the owner. */\n active?: boolean\n onToggle?: (active: boolean) => void\n disabled?: boolean\n}\n\n/** One entry inside a group's flyout. */\nexport interface ToolbarGroupSubItem {\n id: string\n label: string\n icon: Component\n /** Displayed in the flyout. */\n shortcut?: string\n disabled?: boolean\n onActivate?: (id: string) => void\n}\n\nexport type ToolbarGroupEntry = ToolbarGroupSubItem | { type: 'separator' }\n\n/**\n * A collapsed family of tools: one button that opens a flyout, with radio semantics inside.\n *\n * The button's icon becomes the active sub-tool's, so the strip shows what is selected\n * without being expanded.\n */\nexport interface ToolbarGroupItem {\n type: 'group'\n /** Both the button id and the radio group key. */\n id: string\n /** Tooltip when nothing is selected. */\n label: string\n /** Icon when nothing is selected. */\n defaultIcon: Component\n items: ToolbarGroupEntry[]\n disabled?: boolean\n /** Controlled active sub-item. Supplying it — **including as `null`** — makes the caller the owner. */\n activeItemId?: string | null\n onActiveItemChange?: (id: string) => void\n}\n\nexport interface ToolbarSeparator {\n type: 'separator'\n}\n\nexport type ToolbarItem =\n | ToolbarActionItem\n | ToolbarRadioItem\n | ToolbarToggleItem\n | ToolbarGroupItem\n | ToolbarSeparator\n\nexport const isSubItem = (entry: ToolbarGroupEntry): entry is ToolbarGroupSubItem =>\n !('type' in entry)\n\n/**\n * Where a group's flyout opens: away from the strip, mirrored for reading direction.\n *\n * Pure, so the placement is testable without layout.\n */\nexport function flyoutPlacement(\n rect: { left: number; right: number; top: number; bottom: number },\n position: 'left' | 'right' | 'top' | 'bottom',\n viewport: { width: number; height: number },\n isRtl = false,\n gap = 8,\n): Record<string, number> {\n switch (position) {\n case 'left':\n // Under RTL a flex row reverses, so a 'left' strip sits on the right and opens leftwards.\n return isRtl\n ? { right: viewport.width - rect.left + gap, top: rect.top }\n : { left: rect.right + gap, top: rect.top }\n case 'right':\n return isRtl\n ? { left: rect.right + gap, top: rect.top }\n : { right: viewport.width - rect.left + gap, top: rect.top }\n case 'top':\n return isRtl\n ? { top: rect.bottom + gap, right: viewport.width - rect.right }\n : { top: rect.bottom + gap, left: rect.left }\n default:\n return isRtl\n ? { bottom: viewport.height - rect.top + gap, right: viewport.width - rect.right }\n : { bottom: viewport.height - rect.top + gap, left: rect.left }\n }\n}\n","<script setup lang=\"ts\">\n/**\n * A collapsed tool family: one button, and a flyout listing its sub-tools.\n *\n * The flyout is teleported to `document.body` so the strip's own `overflow: hidden` cannot\n * clip it, then clamped back inside the viewport once it has a size.\n */\nimport { computed, nextTick, onBeforeUnmount, ref, useTemplateRef } from 'vue'\nimport type { ToolbarGroupItem } from '../core/toolbarTypes'\nimport { flyoutPlacement, isSubItem } from '../core/toolbarTypes'\nimport { useWorkspace } from '../composables/useWorkspace'\n\nconst props = defineProps<{\n item: ToolbarGroupItem\n position: 'left' | 'right' | 'top' | 'bottom'\n}>()\n\nconst ws = useWorkspace()\nconst button = useTemplateRef<HTMLButtonElement>('button')\nconst flyout = useTemplateRef<HTMLDivElement>('flyout')\nconst open = ref(false)\nconst placement = ref<Record<string, number>>({})\n\n/** Controlled when the prop is present at all — `null` means \"nothing selected\", not \"unset\". */\nconst controlled = computed(() => props.item.activeItemId !== undefined)\nconst activeId = computed(() => controlled.value\n ? props.item.activeItemId ?? null\n : ws.toolbar.activeInGroup(props.item.id))\n\nconst activeSub = computed(() => props.item.items.find(e => isSubItem(e) && e.id === activeId.value))\n/** The button wears the selected tool's icon, so the strip shows the state while collapsed. */\nconst icon = computed(() => (activeSub.value && isSubItem(activeSub.value) ? activeSub.value.icon : props.item.defaultIcon))\nconst label = computed(() => (activeSub.value && isSubItem(activeSub.value) ? activeSub.value.label : props.item.label))\n\nasync function toggle(): Promise<void> {\n if (props.item.disabled) return\n if (open.value) { open.value = false; return }\n const rect = button.value?.getBoundingClientRect()\n if (rect) {\n placement.value = flyoutPlacement(rect, props.position,\n { width: window.innerWidth, height: window.innerHeight }, ws.state.isRtl)\n }\n open.value = true\n await nextTick()\n clampIntoView()\n document.addEventListener('pointerdown', onOutside, { capture: true })\n document.addEventListener('keydown', onKey)\n}\n\nfunction close(): void {\n open.value = false\n document.removeEventListener('pointerdown', onOutside, { capture: true })\n document.removeEventListener('keydown', onKey)\n}\n\n/** Nudge the flyout back on screen. It is positioned before it has a size, so this is a second pass. */\nfunction clampIntoView(): void {\n const el = flyout.value\n if (!el) return\n const rect = el.getBoundingClientRect()\n const pad = 8\n const next = { ...placement.value }\n if (rect.right > window.innerWidth - pad) { next.left = Math.max(pad, window.innerWidth - rect.width - pad); delete next.right }\n if (rect.left < pad) { next.left = pad; delete next.right }\n if (rect.bottom > window.innerHeight - pad) { next.top = Math.max(pad, window.innerHeight - rect.height - pad); delete next.bottom }\n if (rect.top < pad) { next.top = pad; delete next.bottom }\n placement.value = next\n}\n\nfunction onOutside(event: Event): void {\n const target = event.target\n if (!(target instanceof Node)) { close(); return }\n if (button.value?.contains(target) || flyout.value?.contains(target)) return\n close()\n}\nfunction onKey(event: KeyboardEvent): void {\n if (event.key === 'Escape') close()\n}\n\nfunction select(id: string, onActivate?: (id: string) => void): void {\n if (controlled.value) props.item.onActiveItemChange?.(id)\n else ws.toolbar.setActiveInGroup(props.item.id, id)\n onActivate?.(id)\n close()\n}\n\nonBeforeUnmount(close)\n\nconst style = computed(() => {\n const css: Record<string, string> = { position: 'fixed' }\n for (const [key, value] of Object.entries(placement.value)) css[key] = `${value}px`\n return css\n})\n</script>\n\n<template>\n <button\n ref=\"button\"\n type=\"button\"\n class=\"vdd-toolbar-btn vdd-toolbar-btn-group\"\n :class=\"{ 'vdd-active': activeId !== null }\"\n :title=\"label\"\n :aria-label=\"label\"\n :aria-expanded=\"open\"\n aria-haspopup=\"menu\"\n :disabled=\"item.disabled\"\n :data-vdd-toolbar-item=\"item.id\"\n @click=\"toggle\"\n ><component :is=\"icon\" /></button>\n\n <Teleport v-if=\"open\" to=\"body\">\n <div\n ref=\"flyout\"\n class=\"vdd-toolbar-group-flyout\"\n :class=\"`vdd-${position}`\"\n :style=\"style\"\n :dir=\"ws.state.dir\"\n :data-vdd-flyout=\"item.id\"\n role=\"menu\"\n >\n <template v-for=\"(entry, index) in item.items\" :key=\"index\">\n <div v-if=\"!isSubItem(entry)\" class=\"vdd-toolbar-group-flyout-sep\" role=\"separator\" />\n <button\n v-else\n type=\"button\"\n class=\"vdd-toolbar-group-flyout-item\"\n :class=\"{ 'vdd-active': activeId === entry.id }\"\n :disabled=\"entry.disabled\"\n role=\"menuitem\"\n :aria-pressed=\"activeId === entry.id\"\n :data-vdd-flyout-item=\"entry.id\"\n @click=\"select(entry.id, entry.onActivate)\"\n >\n <span class=\"vdd-toolbar-group-flyout-icon\"><component :is=\"entry.icon\" /></span>\n <span class=\"vdd-toolbar-group-flyout-label\">{{ entry.label }}</span>\n <span v-if=\"entry.shortcut\" class=\"vdd-toolbar-group-flyout-shortcut\">{{ entry.shortcut }}</span>\n </button>\n </template>\n </div>\n </Teleport>\n</template>\n","<script setup lang=\"ts\">\n/**\n * A collapsed tool family: one button, and a flyout listing its sub-tools.\n *\n * The flyout is teleported to `document.body` so the strip's own `overflow: hidden` cannot\n * clip it, then clamped back inside the viewport once it has a size.\n */\nimport { computed, nextTick, onBeforeUnmount, ref, useTemplateRef } from 'vue'\nimport type { ToolbarGroupItem } from '../core/toolbarTypes'\nimport { flyoutPlacement, isSubItem } from '../core/toolbarTypes'\nimport { useWorkspace } from '../composables/useWorkspace'\n\nconst props = defineProps<{\n item: ToolbarGroupItem\n position: 'left' | 'right' | 'top' | 'bottom'\n}>()\n\nconst ws = useWorkspace()\nconst button = useTemplateRef<HTMLButtonElement>('button')\nconst flyout = useTemplateRef<HTMLDivElement>('flyout')\nconst open = ref(false)\nconst placement = ref<Record<string, number>>({})\n\n/** Controlled when the prop is present at all — `null` means \"nothing selected\", not \"unset\". */\nconst controlled = computed(() => props.item.activeItemId !== undefined)\nconst activeId = computed(() => controlled.value\n ? props.item.activeItemId ?? null\n : ws.toolbar.activeInGroup(props.item.id))\n\nconst activeSub = computed(() => props.item.items.find(e => isSubItem(e) && e.id === activeId.value))\n/** The button wears the selected tool's icon, so the strip shows the state while collapsed. */\nconst icon = computed(() => (activeSub.value && isSubItem(activeSub.value) ? activeSub.value.icon : props.item.defaultIcon))\nconst label = computed(() => (activeSub.value && isSubItem(activeSub.value) ? activeSub.value.label : props.item.label))\n\nasync function toggle(): Promise<void> {\n if (props.item.disabled) return\n if (open.value) { open.value = false; return }\n const rect = button.value?.getBoundingClientRect()\n if (rect) {\n placement.value = flyoutPlacement(rect, props.position,\n { width: window.innerWidth, height: window.innerHeight }, ws.state.isRtl)\n }\n open.value = true\n await nextTick()\n clampIntoView()\n document.addEventListener('pointerdown', onOutside, { capture: true })\n document.addEventListener('keydown', onKey)\n}\n\nfunction close(): void {\n open.value = false\n document.removeEventListener('pointerdown', onOutside, { capture: true })\n document.removeEventListener('keydown', onKey)\n}\n\n/** Nudge the flyout back on screen. It is positioned before it has a size, so this is a second pass. */\nfunction clampIntoView(): void {\n const el = flyout.value\n if (!el) return\n const rect = el.getBoundingClientRect()\n const pad = 8\n const next = { ...placement.value }\n if (rect.right > window.innerWidth - pad) { next.left = Math.max(pad, window.innerWidth - rect.width - pad); delete next.right }\n if (rect.left < pad) { next.left = pad; delete next.right }\n if (rect.bottom > window.innerHeight - pad) { next.top = Math.max(pad, window.innerHeight - rect.height - pad); delete next.bottom }\n if (rect.top < pad) { next.top = pad; delete next.bottom }\n placement.value = next\n}\n\nfunction onOutside(event: Event): void {\n const target = event.target\n if (!(target instanceof Node)) { close(); return }\n if (button.value?.contains(target) || flyout.value?.contains(target)) return\n close()\n}\nfunction onKey(event: KeyboardEvent): void {\n if (event.key === 'Escape') close()\n}\n\nfunction select(id: string, onActivate?: (id: string) => void): void {\n if (controlled.value) props.item.onActiveItemChange?.(id)\n else ws.toolbar.setActiveInGroup(props.item.id, id)\n onActivate?.(id)\n close()\n}\n\nonBeforeUnmount(close)\n\nconst style = computed(() => {\n const css: Record<string, string> = { position: 'fixed' }\n for (const [key, value] of Object.entries(placement.value)) css[key] = `${value}px`\n return css\n})\n</script>\n\n<template>\n <button\n ref=\"button\"\n type=\"button\"\n class=\"vdd-toolbar-btn vdd-toolbar-btn-group\"\n :class=\"{ 'vdd-active': activeId !== null }\"\n :title=\"label\"\n :aria-label=\"label\"\n :aria-expanded=\"open\"\n aria-haspopup=\"menu\"\n :disabled=\"item.disabled\"\n :data-vdd-toolbar-item=\"item.id\"\n @click=\"toggle\"\n ><component :is=\"icon\" /></button>\n\n <Teleport v-if=\"open\" to=\"body\">\n <div\n ref=\"flyout\"\n class=\"vdd-toolbar-group-flyout\"\n :class=\"`vdd-${position}`\"\n :style=\"style\"\n :dir=\"ws.state.dir\"\n :data-vdd-flyout=\"item.id\"\n role=\"menu\"\n >\n <template v-for=\"(entry, index) in item.items\" :key=\"index\">\n <div v-if=\"!isSubItem(entry)\" class=\"vdd-toolbar-group-flyout-sep\" role=\"separator\" />\n <button\n v-else\n type=\"button\"\n class=\"vdd-toolbar-group-flyout-item\"\n :class=\"{ 'vdd-active': activeId === entry.id }\"\n :disabled=\"entry.disabled\"\n role=\"menuitem\"\n :aria-pressed=\"activeId === entry.id\"\n :data-vdd-flyout-item=\"entry.id\"\n @click=\"select(entry.id, entry.onActivate)\"\n >\n <span class=\"vdd-toolbar-group-flyout-icon\"><component :is=\"entry.icon\" /></span>\n <span class=\"vdd-toolbar-group-flyout-label\">{{ entry.label }}</span>\n <span v-if=\"entry.shortcut\" class=\"vdd-toolbar-group-flyout-shortcut\">{{ entry.shortcut }}</span>\n </button>\n </template>\n </div>\n </Teleport>\n</template>\n","<script setup lang=\"ts\">\n/**\n * A strip of tool buttons on any edge.\n *\n * rdd exposed `show`/`hide`/`toggle` through a ref; here visibility is a model. Items stay\n * data — a toolbar is configuration, not markup — so an rdd `items` array transfers verbatim\n * (docs/decisions/0007-slots-over-render-props.md).\n */\nimport { computed } from 'vue'\nimport type { ToolbarItem } from '../core/toolbarTypes'\nimport { useWorkspace } from '../composables/useWorkspace'\nimport VddToolbarGroupButton from './VddToolbarGroupButton.vue'\n\nconst props = withDefaults(defineProps<{\n /** Which edge to attach to; also decides the strip's orientation. @default 'left' */\n position?: 'left' | 'right' | 'top' | 'bottom'\n items: ToolbarItem[]\n}>(), { position: 'left' })\n\n/** Collapse the strip to nothing. State is kept — items are not unmounted. */\nconst visible = defineModel<boolean>('visible', { default: true })\n\nconst ws = useWorkspace()\nconst isVertical = computed(() => props.position === 'left' || props.position === 'right')\n\n/**\n * Only the collapsed size is set inline.\n *\n * The open size belongs to the stylesheet, including its coarse-pointer override — hard-coding\n * it here would quietly undo the larger touch targets.\n */\nconst collapseStyle = computed(() => visible.value !== false\n ? {}\n : isVertical.value ? { width: '0px' } : { height: '0px' })\n\nconst isRadioActive = (group: string, id: string) => ws.toolbar.activeInGroup(group) === id\n\nfunction activateRadio(group: string, id: string, onActivate?: (id: string) => void): void {\n ws.toolbar.setActiveInGroup(group, id)\n onActivate?.(id)\n}\n\n/** A toggle is controlled when `active` is present at all, `false` included. */\nconst isToggleOn = (item: Extract<ToolbarItem, { type: 'toggle' }>) =>\n item.active !== undefined ? item.active : ws.toolbar.isToggled(item.id)\n\nfunction flipToggle(item: Extract<ToolbarItem, { type: 'toggle' }>): void {\n const next = !isToggleOn(item)\n if (item.active === undefined) ws.toolbar.setToggled(item.id, next)\n item.onToggle?.(next)\n}\n</script>\n\n<template>\n <div\n class=\"vdd-toolbar-strip\"\n :class=\"`vdd-${position}`\"\n :style=\"collapseStyle\"\n :data-vdd-toolbar=\"position\"\n role=\"toolbar\"\n :aria-orientation=\"isVertical ? 'vertical' : 'horizontal'\"\n >\n <template v-for=\"(item, index) in items\" :key=\"item.type === 'separator' ? `sep-${index}` : item.id\">\n <div v-if=\"item.type === 'separator'\" class=\"vdd-toolbar-separator\" role=\"separator\" />\n\n <button\n v-else-if=\"item.type === 'action'\"\n type=\"button\"\n class=\"vdd-toolbar-btn vdd-toolbar-btn-action\"\n :title=\"item.label\"\n :aria-label=\"item.label\"\n :disabled=\"item.disabled\"\n :data-vdd-toolbar-item=\"item.id\"\n @click=\"item.onClick()\"\n ><component :is=\"item.icon\" /></button>\n\n <button\n v-else-if=\"item.type === 'radio'\"\n type=\"button\"\n class=\"vdd-toolbar-btn vdd-toolbar-btn-radio\"\n :class=\"{ 'vdd-active': isRadioActive(item.group, item.id) }\"\n :title=\"item.label\"\n :aria-label=\"item.label\"\n :aria-pressed=\"isRadioActive(item.group, item.id)\"\n :disabled=\"item.disabled\"\n :data-vdd-toolbar-item=\"item.id\"\n @click=\"activateRadio(item.group, item.id, item.onActivate)\"\n ><component :is=\"item.icon\" /></button>\n\n <button\n v-else-if=\"item.type === 'toggle'\"\n type=\"button\"\n class=\"vdd-toolbar-btn vdd-toolbar-btn-toggle\"\n :class=\"{ 'vdd-active': isToggleOn(item) }\"\n :title=\"item.label\"\n :aria-label=\"item.label\"\n :aria-pressed=\"isToggleOn(item)\"\n :disabled=\"item.disabled\"\n :data-vdd-toolbar-item=\"item.id\"\n @click=\"flipToggle(item)\"\n ><component :is=\"item.icon\" /></button>\n\n <VddToolbarGroupButton\n v-else-if=\"item.type === 'group'\"\n :item=\"item\"\n :position=\"position\"\n />\n </template>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/**\n * A strip of tool buttons on any edge.\n *\n * rdd exposed `show`/`hide`/`toggle` through a ref; here visibility is a model. Items stay\n * data — a toolbar is configuration, not markup — so an rdd `items` array transfers verbatim\n * (docs/decisions/0007-slots-over-render-props.md).\n */\nimport { computed } from 'vue'\nimport type { ToolbarItem } from '../core/toolbarTypes'\nimport { useWorkspace } from '../composables/useWorkspace'\nimport VddToolbarGroupButton from './VddToolbarGroupButton.vue'\n\nconst props = withDefaults(defineProps<{\n /** Which edge to attach to; also decides the strip's orientation. @default 'left' */\n position?: 'left' | 'right' | 'top' | 'bottom'\n items: ToolbarItem[]\n}>(), { position: 'left' })\n\n/** Collapse the strip to nothing. State is kept — items are not unmounted. */\nconst visible = defineModel<boolean>('visible', { default: true })\n\nconst ws = useWorkspace()\nconst isVertical = computed(() => props.position === 'left' || props.position === 'right')\n\n/**\n * Only the collapsed size is set inline.\n *\n * The open size belongs to the stylesheet, including its coarse-pointer override — hard-coding\n * it here would quietly undo the larger touch targets.\n */\nconst collapseStyle = computed(() => visible.value !== false\n ? {}\n : isVertical.value ? { width: '0px' } : { height: '0px' })\n\nconst isRadioActive = (group: string, id: string) => ws.toolbar.activeInGroup(group) === id\n\nfunction activateRadio(group: string, id: string, onActivate?: (id: string) => void): void {\n ws.toolbar.setActiveInGroup(group, id)\n onActivate?.(id)\n}\n\n/** A toggle is controlled when `active` is present at all, `false` included. */\nconst isToggleOn = (item: Extract<ToolbarItem, { type: 'toggle' }>) =>\n item.active !== undefined ? item.active : ws.toolbar.isToggled(item.id)\n\nfunction flipToggle(item: Extract<ToolbarItem, { type: 'toggle' }>): void {\n const next = !isToggleOn(item)\n if (item.active === undefined) ws.toolbar.setToggled(item.id, next)\n item.onToggle?.(next)\n}\n</script>\n\n<template>\n <div\n class=\"vdd-toolbar-strip\"\n :class=\"`vdd-${position}`\"\n :style=\"collapseStyle\"\n :data-vdd-toolbar=\"position\"\n role=\"toolbar\"\n :aria-orientation=\"isVertical ? 'vertical' : 'horizontal'\"\n >\n <template v-for=\"(item, index) in items\" :key=\"item.type === 'separator' ? `sep-${index}` : item.id\">\n <div v-if=\"item.type === 'separator'\" class=\"vdd-toolbar-separator\" role=\"separator\" />\n\n <button\n v-else-if=\"item.type === 'action'\"\n type=\"button\"\n class=\"vdd-toolbar-btn vdd-toolbar-btn-action\"\n :title=\"item.label\"\n :aria-label=\"item.label\"\n :disabled=\"item.disabled\"\n :data-vdd-toolbar-item=\"item.id\"\n @click=\"item.onClick()\"\n ><component :is=\"item.icon\" /></button>\n\n <button\n v-else-if=\"item.type === 'radio'\"\n type=\"button\"\n class=\"vdd-toolbar-btn vdd-toolbar-btn-radio\"\n :class=\"{ 'vdd-active': isRadioActive(item.group, item.id) }\"\n :title=\"item.label\"\n :aria-label=\"item.label\"\n :aria-pressed=\"isRadioActive(item.group, item.id)\"\n :disabled=\"item.disabled\"\n :data-vdd-toolbar-item=\"item.id\"\n @click=\"activateRadio(item.group, item.id, item.onActivate)\"\n ><component :is=\"item.icon\" /></button>\n\n <button\n v-else-if=\"item.type === 'toggle'\"\n type=\"button\"\n class=\"vdd-toolbar-btn vdd-toolbar-btn-toggle\"\n :class=\"{ 'vdd-active': isToggleOn(item) }\"\n :title=\"item.label\"\n :aria-label=\"item.label\"\n :aria-pressed=\"isToggleOn(item)\"\n :disabled=\"item.disabled\"\n :data-vdd-toolbar-item=\"item.id\"\n @click=\"flipToggle(item)\"\n ><component :is=\"item.icon\" /></button>\n\n <VddToolbarGroupButton\n v-else-if=\"item.type === 'group'\"\n :item=\"item\"\n :position=\"position\"\n />\n </template>\n </div>\n</template>\n","import { computed, onBeforeUnmount, onMounted, ref, toValue } from 'vue'\nimport type { ComputedRef, Component, MaybeRefOrGetter } from 'vue'\nimport { useWorkspace } from './useWorkspace'\nimport { providePanel } from './usePanel'\nimport type { OverlayInstance } from '../core/overlays'\n\n/**\n * Everything a rendered side panel or modal needs, in one place.\n *\n * rdd's `SidePanelRenderer` and `ModalStackRenderer` each carried their own copy of this —\n * the close sequence, the dirty-confirmation modal, the title asterisk, the `setDirty` and\n * `setTitle` plumbing, the Escape handler. Thirty-five of those lines were character-for-\n * character identical, which is how two containers come to disagree about what closing a\n * dirty panel does. Here the two components differ only in their chrome.\n *\n * **Escape routing** lives here too, and is the reason it can be stated once: Escape belongs\n * to the topmost modal, and reaches a drawer only when no modal is open. rdd expressed the\n * same rule twice, in two different shapes (`modals.length === 0` in one,\n * `isTopmost` in the other).\n */\nexport function useOverlayHost(\n instance: MaybeRefOrGetter<OverlayInstance>,\n): {\n /** The instance's title, resolved through the formatter, with a trailing `*` when dirty. */\n displayTitle: ComputedRef<string>\n /** Whether Escape and a backdrop click should close this instance. */\n dismissible: ComputedRef<boolean>\n /** The icon to show in the header: the live one set from inside, else the open-time one. */\n icon: ComputedRef<Component | null>\n /** Close, honouring the close guard and dirty state. */\n close: (options?: { force?: boolean }) => Promise<void>\n /** `padding` for the body, or `undefined` to leave it to the stylesheet. */\n bodyPadding: ComputedRef<string | undefined>\n} {\n const ws = useWorkspace()\n const { overlays } = ws\n const current = () => toValue(instance)\n\n /** Set from inside the panel via `usePanel().setIcon()`; falls back to the open-time icon. */\n const liveIcon = ref<Component | null>(null)\n\n const dismissible = computed(() =>\n current().kind === 'modal' ? current().options.closable !== false : true)\n\n /**\n * Closing asks the same unsaved-changes question a docked panel does — `<VddModals>` owns\n * the one implementation, so a drawer and a tab cannot answer differently. The question is\n * itself a modal, so it stacks on top of whatever is closing, including on top of another\n * question.\n */\n const close = (options?: { force?: boolean }): Promise<void> =>\n overlays.requestClose(current().id, {\n ...options,\n confirm: (target) => overlays.confirmDiscard({\n title: ws.format(target.options.title),\n dirtyOptions: target.dirtyOptions,\n }),\n })\n\n function onKeydown(event: KeyboardEvent): void {\n if (event.key !== 'Escape' || !dismissible.value) return\n const self = current()\n if (self.kind === 'modal') {\n // Only the topmost modal answers, and it stops the event so the drawers below —\n // which are listening on the same document — do not close as well.\n if (overlays.topmostModal()?.id !== self.id) return\n event.stopPropagation()\n } else if (overlays.state.modals.length > 0) {\n return\n }\n void close()\n }\n\n onMounted(() => document.addEventListener('keydown', onKeydown))\n onBeforeUnmount(() => document.removeEventListener('keydown', onKeydown))\n\n // The panel inside gets the same `usePanel()` contract a docked panel gets, so one\n // component can be opened as a tab, a floating window or a modal without knowing which.\n providePanel({\n id: current().id,\n containerType: computed(() => current().kind),\n close,\n setTitle: (title) => overlays.updateInstance(current().id, {\n options: { ...current().options, title },\n }),\n setIcon: (component) => { liveIcon.value = (component ?? null) as Component | null },\n setDirty: (dirty, dirtyOptions) => overlays.setDirty(current().id, dirty, dirtyOptions),\n })\n\n return {\n displayTitle: computed(() => {\n const base = ws.format(current().options.title)\n return current().dirty ? `${base} *` : base\n }),\n dismissible,\n icon: computed(() => liveIcon.value ?? current().options.icon ?? null),\n close,\n bodyPadding: computed(() => {\n const value = current().options.bodyPadding\n if (value == null) return undefined\n return typeof value === 'number' ? `${value}px` : value\n }),\n }\n}\n","<script setup lang=\"ts\">\n/**\n * The header-and-body chrome shared by a side panel and a modal.\n *\n * Both have an icon, a title carrying the dirty asterisk, a close button and a body whose\n * padding the caller may set. rdd wrote that markup twice, once per renderer, with only the\n * class prefix differing — and the two had already drifted apart in whether the close button\n * could be suppressed.\n *\n * The class names are picked from a literal map rather than built by interpolation: a\n * consumer searching devtools for `vdd-modal-header` must find it in the source, and the\n * css-prefix gate reads literals.\n */\nimport { computed } from 'vue'\nimport type { Component } from 'vue'\nimport { useWorkspace } from '../composables/useWorkspace'\n\nconst props = defineProps<{\n block: 'side-panel' | 'modal'\n title: string\n icon: Component | null\n /** `false` hides the close button entirely — a modal opened with `closable: false`. */\n closable: boolean\n closeLabel: string\n /** Body padding, or `undefined` to leave it to the stylesheet. */\n bodyPadding?: string\n}>()\n\nconst emit = defineEmits<{ close: [] }>()\n\nconst MODAL = {\n header: 'vdd-modal-header',\n icon: 'vdd-modal-icon',\n title: 'vdd-modal-title',\n closeButton: 'vdd-modal-close-button',\n body: 'vdd-modal-body',\n} as const\n\nconst SIDE_PANEL = {\n header: 'vdd-side-panel-header',\n icon: 'vdd-side-panel-icon',\n title: 'vdd-side-panel-title',\n closeButton: 'vdd-side-panel-close-button',\n body: 'vdd-side-panel-body',\n} as const\n\nconst c = computed(() => (props.block === 'modal' ? MODAL : SIDE_PANEL))\n\n/** The consumer's own class for this body, from `createWorkspace({ classes })`. */\nconst ws = useWorkspace()\nconst hostBodyClass = computed(() =>\n props.block === 'modal' ? ws.classes.modalBody : ws.classes.sidePanelBody)\n</script>\n\n<template>\n <div :class=\"c.header\">\n <div v-if=\"icon\" :class=\"c.icon\"><component :is=\"icon\" /></div>\n <h4 :class=\"c.title\">{{ title }}</h4>\n <button\n v-if=\"closable\"\n type=\"button\"\n :class=\"c.closeButton\"\n :title=\"closeLabel\"\n :aria-label=\"closeLabel\"\n data-vdd-overlay-close\n @click=\"emit('close')\"\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\">\n <path d=\"M18 6L6 18M6 6l12 12\" />\n </svg>\n </button>\n </div>\n <div :class=\"[c.body, hostBodyClass]\" :style=\"bodyPadding != null ? { padding: bodyPadding } : undefined\">\n <slot />\n </div>\n</template>\n","<script setup lang=\"ts\">\n/**\n * The header-and-body chrome shared by a side panel and a modal.\n *\n * Both have an icon, a title carrying the dirty asterisk, a close button and a body whose\n * padding the caller may set. rdd wrote that markup twice, once per renderer, with only the\n * class prefix differing — and the two had already drifted apart in whether the close button\n * could be suppressed.\n *\n * The class names are picked from a literal map rather than built by interpolation: a\n * consumer searching devtools for `vdd-modal-header` must find it in the source, and the\n * css-prefix gate reads literals.\n */\nimport { computed } from 'vue'\nimport type { Component } from 'vue'\nimport { useWorkspace } from '../composables/useWorkspace'\n\nconst props = defineProps<{\n block: 'side-panel' | 'modal'\n title: string\n icon: Component | null\n /** `false` hides the close button entirely — a modal opened with `closable: false`. */\n closable: boolean\n closeLabel: string\n /** Body padding, or `undefined` to leave it to the stylesheet. */\n bodyPadding?: string\n}>()\n\nconst emit = defineEmits<{ close: [] }>()\n\nconst MODAL = {\n header: 'vdd-modal-header',\n icon: 'vdd-modal-icon',\n title: 'vdd-modal-title',\n closeButton: 'vdd-modal-close-button',\n body: 'vdd-modal-body',\n} as const\n\nconst SIDE_PANEL = {\n header: 'vdd-side-panel-header',\n icon: 'vdd-side-panel-icon',\n title: 'vdd-side-panel-title',\n closeButton: 'vdd-side-panel-close-button',\n body: 'vdd-side-panel-body',\n} as const\n\nconst c = computed(() => (props.block === 'modal' ? MODAL : SIDE_PANEL))\n\n/** The consumer's own class for this body, from `createWorkspace({ classes })`. */\nconst ws = useWorkspace()\nconst hostBodyClass = computed(() =>\n props.block === 'modal' ? ws.classes.modalBody : ws.classes.sidePanelBody)\n</script>\n\n<template>\n <div :class=\"c.header\">\n <div v-if=\"icon\" :class=\"c.icon\"><component :is=\"icon\" /></div>\n <h4 :class=\"c.title\">{{ title }}</h4>\n <button\n v-if=\"closable\"\n type=\"button\"\n :class=\"c.closeButton\"\n :title=\"closeLabel\"\n :aria-label=\"closeLabel\"\n data-vdd-overlay-close\n @click=\"emit('close')\"\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\">\n <path d=\"M18 6L6 18M6 6l12 12\" />\n </svg>\n </button>\n </div>\n <div :class=\"[c.body, hostBodyClass]\" :style=\"bodyPadding != null ? { padding: bodyPadding } : undefined\">\n <slot />\n </div>\n</template>\n","<script setup lang=\"ts\">\n/**\n * One rendered side panel. Its own component so each instance gets its own\n * `useOverlayHost()` — the composable provides a panel context, and `provide` is per\n * instance, so the left and right drawers cannot share one.\n */\nimport { computed } from 'vue'\nimport { useWorkspace } from '../composables/useWorkspace'\nimport { useOverlayHost } from '../composables/useOverlayHost'\nimport VddOverlayFrame from './VddOverlayFrame.vue'\nimport type { OverlayInstance } from '../core/overlays'\n\nconst props = defineProps<{\n instance: OverlayInstance\n position: 'left' | 'right'\n /** Used when the instance did not ask for a width. */\n defaultWidth: number | string\n}>()\n\nconst ws = useWorkspace()\nconst { displayTitle, icon, close, bodyPadding } = useOverlayHost(() => props.instance)\n\nconst width = computed(() => {\n const value = props.instance.options.width ?? props.defaultWidth\n return typeof value === 'number' ? `${value}px` : value\n})\n</script>\n\n<template>\n <div\n class=\"vdd-side-panel vdd-side-panel-visible\"\n :class=\"position === 'left' ? 'vdd-side-panel-left' : 'vdd-side-panel-right'\"\n :style=\"{ width }\"\n :dir=\"ws.state.dir\"\n :data-vdd-side-panel=\"position\"\n >\n <div class=\"vdd-side-panel-window\" :class=\"ws.classes.sidePanel\">\n <VddOverlayFrame\n block=\"side-panel\"\n :title=\"displayTitle\"\n :icon=\"icon\"\n :closable=\"true\"\n :close-label=\"ws.format(ws.messages.closeTooltip)\"\n :body-padding=\"bodyPadding\"\n @close=\"close()\"\n >\n <component :is=\"instance.component\" v-bind=\"{ ...instance.props, panelId: instance.id }\" />\n </VddOverlayFrame>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/**\n * One rendered side panel. Its own component so each instance gets its own\n * `useOverlayHost()` — the composable provides a panel context, and `provide` is per\n * instance, so the left and right drawers cannot share one.\n */\nimport { computed } from 'vue'\nimport { useWorkspace } from '../composables/useWorkspace'\nimport { useOverlayHost } from '../composables/useOverlayHost'\nimport VddOverlayFrame from './VddOverlayFrame.vue'\nimport type { OverlayInstance } from '../core/overlays'\n\nconst props = defineProps<{\n instance: OverlayInstance\n position: 'left' | 'right'\n /** Used when the instance did not ask for a width. */\n defaultWidth: number | string\n}>()\n\nconst ws = useWorkspace()\nconst { displayTitle, icon, close, bodyPadding } = useOverlayHost(() => props.instance)\n\nconst width = computed(() => {\n const value = props.instance.options.width ?? props.defaultWidth\n return typeof value === 'number' ? `${value}px` : value\n})\n</script>\n\n<template>\n <div\n class=\"vdd-side-panel vdd-side-panel-visible\"\n :class=\"position === 'left' ? 'vdd-side-panel-left' : 'vdd-side-panel-right'\"\n :style=\"{ width }\"\n :dir=\"ws.state.dir\"\n :data-vdd-side-panel=\"position\"\n >\n <div class=\"vdd-side-panel-window\" :class=\"ws.classes.sidePanel\">\n <VddOverlayFrame\n block=\"side-panel\"\n :title=\"displayTitle\"\n :icon=\"icon\"\n :closable=\"true\"\n :close-label=\"ws.format(ws.messages.closeTooltip)\"\n :body-padding=\"bodyPadding\"\n @close=\"close()\"\n >\n <component :is=\"instance.component\" v-bind=\"{ ...instance.props, panelId: instance.id }\" />\n </VddOverlayFrame>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/**\n * Renders whichever side drawers are open.\n *\n * Place it once, anywhere inside the app — the panels are `position: fixed`, so where it\n * sits in the tree does not affect where they appear.\n *\n * `sides` narrows it to one edge, which is what rdd offered as two extra exported\n * components (`LeftPanelRenderer`, `RightPanelRenderer`). A prop rather than three\n * components: the three shared an implementation and differed only in this filter.\n */\nimport { computed } from 'vue'\nimport { useWorkspace } from '../composables/useWorkspace'\nimport VddSidePanelHost from './VddSidePanelHost.vue'\n\nconst props = withDefaults(defineProps<{\n /** Applied when `openLeftPanel`/`openRightPanel` did not specify a width. @default 400 */\n defaultWidth?: number | string\n /** Which edges this instance is responsible for. @default 'both' */\n sides?: 'both' | 'left' | 'right'\n}>(), { defaultWidth: 400, sides: 'both' })\n\nconst { overlays } = useWorkspace()\nconst left = computed(() => (props.sides === 'right' ? null : overlays.state.leftPanel))\nconst right = computed(() => (props.sides === 'left' ? null : overlays.state.rightPanel))\n</script>\n\n<template>\n <VddSidePanelHost\n v-if=\"left\"\n :key=\"left.id\"\n :instance=\"left\"\n position=\"left\"\n :default-width=\"defaultWidth\"\n />\n <VddSidePanelHost\n v-if=\"right\"\n :key=\"right.id\"\n :instance=\"right\"\n position=\"right\"\n :default-width=\"defaultWidth\"\n />\n</template>\n","<script setup lang=\"ts\">\n/**\n * Renders whichever side drawers are open.\n *\n * Place it once, anywhere inside the app — the panels are `position: fixed`, so where it\n * sits in the tree does not affect where they appear.\n *\n * `sides` narrows it to one edge, which is what rdd offered as two extra exported\n * components (`LeftPanelRenderer`, `RightPanelRenderer`). A prop rather than three\n * components: the three shared an implementation and differed only in this filter.\n */\nimport { computed } from 'vue'\nimport { useWorkspace } from '../composables/useWorkspace'\nimport VddSidePanelHost from './VddSidePanelHost.vue'\n\nconst props = withDefaults(defineProps<{\n /** Applied when `openLeftPanel`/`openRightPanel` did not specify a width. @default 400 */\n defaultWidth?: number | string\n /** Which edges this instance is responsible for. @default 'both' */\n sides?: 'both' | 'left' | 'right'\n}>(), { defaultWidth: 400, sides: 'both' })\n\nconst { overlays } = useWorkspace()\nconst left = computed(() => (props.sides === 'right' ? null : overlays.state.leftPanel))\nconst right = computed(() => (props.sides === 'left' ? null : overlays.state.rightPanel))\n</script>\n\n<template>\n <VddSidePanelHost\n v-if=\"left\"\n :key=\"left.id\"\n :instance=\"left\"\n position=\"left\"\n :default-width=\"defaultWidth\"\n />\n <VddSidePanelHost\n v-if=\"right\"\n :key=\"right.id\"\n :instance=\"right\"\n position=\"right\"\n :default-width=\"defaultWidth\"\n />\n</template>\n","<script setup lang=\"ts\">\n/**\n * One rendered modal. Its own component for the same reason as the side panel host: the\n * panel context it provides is per instance, and a stack has several at once.\n */\nimport { computed } from 'vue'\nimport { useWorkspace } from '../composables/useWorkspace'\nimport { useOverlayHost } from '../composables/useOverlayHost'\nimport VddOverlayFrame from './VddOverlayFrame.vue'\nimport type { OverlayInstance } from '../core/overlays'\n\nconst props = defineProps<{\n instance: OverlayInstance\n /** Depth in the stack, which is what separates the modals' z-indexes. */\n index: number\n}>()\n\nconst ws = useWorkspace()\nconst { displayTitle, icon, dismissible, close, bodyPadding } = useOverlayHost(() => props.instance)\n\nconst sizeClass = computed(() => `vdd-modal-size-${props.instance.options.size ?? 'auto'}`)\n\n/**\n * Stacks against `--vdd-z-base`, which `createWorkspace({ zIndexBase })` mirrors onto the\n * document element — so raising the base moves the modals with everything else. rdd set this\n * inline against the same variable; the variable is the part that matters.\n */\nconst zIndex = computed(() => `calc(var(--vdd-z-base, 1000) + 9000 + ${props.index * 10})`)\n</script>\n\n<template>\n <div\n class=\"vdd-modal-overlay\"\n :style=\"{ zIndex }\"\n :dir=\"ws.state.dir\"\n :data-vdd-modal=\"instance.id\"\n >\n <!-- Clicking the backdrop dismisses, unless the modal was opened with closable: false. -->\n <div class=\"vdd-modal-curtain\" data-vdd-modal-curtain @click=\"dismissible && close()\" />\n <div class=\"vdd-modal-window\" :class=\"[sizeClass, ws.classes.modal]\">\n <VddOverlayFrame\n block=\"modal\"\n :title=\"displayTitle\"\n :icon=\"icon\"\n :closable=\"dismissible\"\n :close-label=\"ws.format(ws.messages.closeTooltip)\"\n :body-padding=\"bodyPadding\"\n @close=\"close()\"\n >\n <component :is=\"instance.component\" v-bind=\"{ ...instance.props, panelId: instance.id }\" />\n </VddOverlayFrame>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/**\n * One rendered modal. Its own component for the same reason as the side panel host: the\n * panel context it provides is per instance, and a stack has several at once.\n */\nimport { computed } from 'vue'\nimport { useWorkspace } from '../composables/useWorkspace'\nimport { useOverlayHost } from '../composables/useOverlayHost'\nimport VddOverlayFrame from './VddOverlayFrame.vue'\nimport type { OverlayInstance } from '../core/overlays'\n\nconst props = defineProps<{\n instance: OverlayInstance\n /** Depth in the stack, which is what separates the modals' z-indexes. */\n index: number\n}>()\n\nconst ws = useWorkspace()\nconst { displayTitle, icon, dismissible, close, bodyPadding } = useOverlayHost(() => props.instance)\n\nconst sizeClass = computed(() => `vdd-modal-size-${props.instance.options.size ?? 'auto'}`)\n\n/**\n * Stacks against `--vdd-z-base`, which `createWorkspace({ zIndexBase })` mirrors onto the\n * document element — so raising the base moves the modals with everything else. rdd set this\n * inline against the same variable; the variable is the part that matters.\n */\nconst zIndex = computed(() => `calc(var(--vdd-z-base, 1000) + 9000 + ${props.index * 10})`)\n</script>\n\n<template>\n <div\n class=\"vdd-modal-overlay\"\n :style=\"{ zIndex }\"\n :dir=\"ws.state.dir\"\n :data-vdd-modal=\"instance.id\"\n >\n <!-- Clicking the backdrop dismisses, unless the modal was opened with closable: false. -->\n <div class=\"vdd-modal-curtain\" data-vdd-modal-curtain @click=\"dismissible && close()\" />\n <div class=\"vdd-modal-window\" :class=\"[sizeClass, ws.classes.modal]\">\n <VddOverlayFrame\n block=\"modal\"\n :title=\"displayTitle\"\n :icon=\"icon\"\n :closable=\"dismissible\"\n :close-label=\"ws.format(ws.messages.closeTooltip)\"\n :body-padding=\"bodyPadding\"\n @close=\"close()\"\n >\n <component :is=\"instance.component\" v-bind=\"{ ...instance.props, panelId: instance.id }\" />\n </VddOverlayFrame>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/**\n * A confirm/cancel dialog, meant to be opened as a modal.\n *\n * Also what the library itself opens when a dirty panel is closed, which is why `onSettled`\n * exists: the caller needs to know the answer however the dialog went away — the buttons,\n * Escape, the backdrop, or the ×. Resolving only on the buttons would leave a\n * `requestClose()` awaiting forever if the user pressed Escape.\n */\nimport { onBeforeUnmount, onMounted, useTemplateRef } from 'vue'\nimport { useWorkspace } from '../composables/useWorkspace'\nimport { usePanel } from '../composables/usePanel'\nimport type { AlertType, Label } from '../types'\n\nconst props = withDefaults(defineProps<{\n message: Label\n /** An extra banner above the message — which fields are invalid, say. */\n alert?: string\n alertType?: AlertType\n /** Label the buttons \"Yes\"/\"No\" rather than \"OK\"/\"Cancel\". */\n yesNo?: boolean\n onOk?: () => void\n onCancel?: () => void\n /** Called exactly once, whichever way the dialog closes. `ok` says which. */\n onSettled?: (ok: boolean) => void\n}>(), { alertType: 'info', yesNo: false })\n\nconst ws = useWorkspace()\nconst panel = usePanel()\nconst confirmButton = useTemplateRef<HTMLButtonElement>('confirmButton')\n\nlet settled = false\n/** Runs on every exit path, including an unmount the dialog did not initiate. */\nfunction settle(ok: boolean): void {\n if (settled) return\n settled = true\n props.onSettled?.(ok)\n}\n\nfunction confirm(): void {\n settle(true)\n props.onOk?.()\n void panel.close({ force: true })\n}\n\nfunction cancel(): void {\n settle(false)\n props.onCancel?.()\n void panel.close({ force: true })\n}\n\nonMounted(() => {\n panel.setIcon(undefined)\n // Focus the confirm button so Enter answers and a screen reader announces the choice.\n confirmButton.value?.focus({ preventScroll: true })\n})\n// Escape, the backdrop and the × all unmount us without going through cancel().\nonBeforeUnmount(() => settle(false))\n</script>\n\n<template>\n <form class=\"vdd-confirmation-form-body\" @submit.prevent=\"confirm\">\n <div v-if=\"alert\" class=\"vdd-confirmation-alert\" :class=\"`vdd-confirmation-alert-${alertType}`\">\n <span>{{ alert }}</span>\n </div>\n\n <div class=\"vdd-confirmation-message\">{{ ws.format(message) }}</div>\n\n <div class=\"vdd-confirmation-actions\">\n <button type=\"button\" class=\"vdd-btn vdd-btn-sm vdd-btn-outline\" data-vdd-confirm-cancel @click=\"cancel\">\n {{ ws.format(yesNo ? ws.messages.no : ws.messages.cancel) }}\n </button>\n <button ref=\"confirmButton\" type=\"submit\" class=\"vdd-btn vdd-btn-sm vdd-btn-primary\" data-vdd-confirm-ok>\n {{ ws.format(yesNo ? ws.messages.yes : ws.messages.ok) }}\n </button>\n </div>\n </form>\n</template>\n","<script setup lang=\"ts\">\n/**\n * A confirm/cancel dialog, meant to be opened as a modal.\n *\n * Also what the library itself opens when a dirty panel is closed, which is why `onSettled`\n * exists: the caller needs to know the answer however the dialog went away — the buttons,\n * Escape, the backdrop, or the ×. Resolving only on the buttons would leave a\n * `requestClose()` awaiting forever if the user pressed Escape.\n */\nimport { onBeforeUnmount, onMounted, useTemplateRef } from 'vue'\nimport { useWorkspace } from '../composables/useWorkspace'\nimport { usePanel } from '../composables/usePanel'\nimport type { AlertType, Label } from '../types'\n\nconst props = withDefaults(defineProps<{\n message: Label\n /** An extra banner above the message — which fields are invalid, say. */\n alert?: string\n alertType?: AlertType\n /** Label the buttons \"Yes\"/\"No\" rather than \"OK\"/\"Cancel\". */\n yesNo?: boolean\n onOk?: () => void\n onCancel?: () => void\n /** Called exactly once, whichever way the dialog closes. `ok` says which. */\n onSettled?: (ok: boolean) => void\n}>(), { alertType: 'info', yesNo: false })\n\nconst ws = useWorkspace()\nconst panel = usePanel()\nconst confirmButton = useTemplateRef<HTMLButtonElement>('confirmButton')\n\nlet settled = false\n/** Runs on every exit path, including an unmount the dialog did not initiate. */\nfunction settle(ok: boolean): void {\n if (settled) return\n settled = true\n props.onSettled?.(ok)\n}\n\nfunction confirm(): void {\n settle(true)\n props.onOk?.()\n void panel.close({ force: true })\n}\n\nfunction cancel(): void {\n settle(false)\n props.onCancel?.()\n void panel.close({ force: true })\n}\n\nonMounted(() => {\n panel.setIcon(undefined)\n // Focus the confirm button so Enter answers and a screen reader announces the choice.\n confirmButton.value?.focus({ preventScroll: true })\n})\n// Escape, the backdrop and the × all unmount us without going through cancel().\nonBeforeUnmount(() => settle(false))\n</script>\n\n<template>\n <form class=\"vdd-confirmation-form-body\" @submit.prevent=\"confirm\">\n <div v-if=\"alert\" class=\"vdd-confirmation-alert\" :class=\"`vdd-confirmation-alert-${alertType}`\">\n <span>{{ alert }}</span>\n </div>\n\n <div class=\"vdd-confirmation-message\">{{ ws.format(message) }}</div>\n\n <div class=\"vdd-confirmation-actions\">\n <button type=\"button\" class=\"vdd-btn vdd-btn-sm vdd-btn-outline\" data-vdd-confirm-cancel @click=\"cancel\">\n {{ ws.format(yesNo ? ws.messages.no : ws.messages.cancel) }}\n </button>\n <button ref=\"confirmButton\" type=\"submit\" class=\"vdd-btn vdd-btn-sm vdd-btn-primary\" data-vdd-confirm-ok>\n {{ ws.format(yesNo ? ws.messages.yes : ws.messages.ok) }}\n </button>\n </div>\n </form>\n</template>\n","<script setup lang=\"ts\">\n/**\n * Renders the modal stack, bottom to top.\n *\n * Place it once. Modals are `position: fixed` with their own stacking, so its position in\n * the tree is irrelevant — including relative to the workspace, which is the point: a modal\n * opened by a panel must not be clipped by that panel's box.\n */\nimport { onBeforeUnmount, onMounted } from 'vue'\nimport { useWorkspace } from '../composables/useWorkspace'\nimport VddModalHost from './VddModalHost.vue'\nimport VddConfirm from './VddConfirm.vue'\nimport type { DiscardRequest } from '../core/overlays'\n\nconst ws = useWorkspace()\nconst { overlays } = ws\n\n/**\n * The library's own unsaved-changes question, registered here because asking needs somewhere\n * to render — mount no `<VddModals>` and a dirty close refuses rather than discarding.\n *\n * Every close path in the library resolves through this one promise, so the answer cannot\n * differ between a tab, a window, a drawer and a modal.\n */\nfunction confirmDiscard(request: DiscardRequest): Promise<boolean> {\n return new Promise<boolean>((resolve) => {\n overlays.openModal(\n VddConfirm,\n {\n message: request.dirtyOptions?.message ?? {\n ...ws.messages.unsavedChangesMessage,\n values: { title: request.title },\n },\n alert: request.dirtyOptions?.alert,\n alertType: request.dirtyOptions?.alertType ?? 'danger',\n yesNo: true,\n // Resolving on settle rather than only on the buttons is what makes Escape, the\n // backdrop and the × all count as a refusal — and stops an awaiting close from\n // hanging forever when the user dismisses the question instead of answering it.\n onSettled: (ok: boolean) => resolve(ok),\n },\n { title: request.dirtyOptions?.title ?? ws.messages.unsavedChangesTitle, size: 'small' },\n )\n })\n}\n\nonMounted(() => overlays.setConfirmRenderer(confirmDiscard))\nonBeforeUnmount(() => overlays.setConfirmRenderer(null))\n</script>\n\n<template>\n <VddModalHost\n v-for=\"(modal, index) in overlays.state.modals\"\n :key=\"modal.id\"\n :instance=\"modal\"\n :index=\"index\"\n />\n</template>\n","<script setup lang=\"ts\">\n/**\n * Renders the modal stack, bottom to top.\n *\n * Place it once. Modals are `position: fixed` with their own stacking, so its position in\n * the tree is irrelevant — including relative to the workspace, which is the point: a modal\n * opened by a panel must not be clipped by that panel's box.\n */\nimport { onBeforeUnmount, onMounted } from 'vue'\nimport { useWorkspace } from '../composables/useWorkspace'\nimport VddModalHost from './VddModalHost.vue'\nimport VddConfirm from './VddConfirm.vue'\nimport type { DiscardRequest } from '../core/overlays'\n\nconst ws = useWorkspace()\nconst { overlays } = ws\n\n/**\n * The library's own unsaved-changes question, registered here because asking needs somewhere\n * to render — mount no `<VddModals>` and a dirty close refuses rather than discarding.\n *\n * Every close path in the library resolves through this one promise, so the answer cannot\n * differ between a tab, a window, a drawer and a modal.\n */\nfunction confirmDiscard(request: DiscardRequest): Promise<boolean> {\n return new Promise<boolean>((resolve) => {\n overlays.openModal(\n VddConfirm,\n {\n message: request.dirtyOptions?.message ?? {\n ...ws.messages.unsavedChangesMessage,\n values: { title: request.title },\n },\n alert: request.dirtyOptions?.alert,\n alertType: request.dirtyOptions?.alertType ?? 'danger',\n yesNo: true,\n // Resolving on settle rather than only on the buttons is what makes Escape, the\n // backdrop and the × all count as a refusal — and stops an awaiting close from\n // hanging forever when the user dismisses the question instead of answering it.\n onSettled: (ok: boolean) => resolve(ok),\n },\n { title: request.dirtyOptions?.title ?? ws.messages.unsavedChangesTitle, size: 'small' },\n )\n })\n}\n\nonMounted(() => overlays.setConfirmRenderer(confirmDiscard))\nonBeforeUnmount(() => overlays.setConfirmRenderer(null))\n</script>\n\n<template>\n <VddModalHost\n v-for=\"(modal, index) in overlays.state.modals\"\n :key=\"modal.id\"\n :instance=\"modal\"\n :index=\"index\"\n />\n</template>\n","/**\n * The toast singleton.\n *\n * rdd routed every `toast.*` call through an event emitter that `<ToastContainer>`\n * subscribed to, because React state cannot live at module scope — the emitter existed\n * purely to get a call made outside React into a component's `useState`.\n *\n * In Vue the queue *is* module-level reactive state, so there is nothing to emit: `toast()`\n * pushes, `<VddToasts>` renders. One less indirection, and two consequences worth having —\n * two containers mounted at once show the same toasts instead of racing for the subscription,\n * and a test can assert the store directly without rendering anything.\n *\n * `maxVisible` follows from that too. rdd kept a separate queue array and imperatively\n * shifted from it when a toast finished exiting; here every toast goes in one list and the\n * container renders a `computed` slice of it, so promotion is not code at all.\n */\nimport { reactive } from 'vue'\nimport type { Component } from 'vue'\n\nexport type ToastType = 'info' | 'success' | 'warning' | 'error'\nexport type ToastPosition = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'\n\n/** Per-toast options. Anything omitted falls back to the `<VddToasts>` defaults. */\nexport interface ToastOptions {\n /** @default 'info' — set for you by the `toast.info`/`success`/… shorthands. */\n type?: ToastType\n /** Auto-dismiss delay in ms. `0` never auto-dismisses. @default from the container */\n duration?: number\n /** Supply to deduplicate: calling again with the same id updates that toast in place. */\n id?: string\n /** @default from the container */\n closable?: boolean\n /** Replaces the built-in type icon. */\n icon?: Component\n /** Replaces the message string with a component. */\n content?: Component\n /** Props for `content`. */\n contentProps?: Record<string, unknown>\n /** Called on dismissal by timer, close button or `toast.dismiss()`. */\n onClose?: () => void\n}\n\n/** A toast's options with every default filled in. What an adapter receives. */\nexport interface ResolvedToastOptions extends Required<Pick<ToastOptions, 'id' | 'type' | 'duration' | 'closable'>> {\n icon?: Component\n content?: Component\n contentProps?: Record<string, unknown>\n onClose?: () => void\n}\n\n/** One toast in the store. */\nexport interface ToastRecord {\n id: string\n message: string\n options: ToastOptions & { id: string }\n /** Set while the exit transition runs; an exiting toast no longer counts as visible. */\n exiting: boolean\n /** Bumped whenever the toast is updated, so a watcher can restart its timer. */\n revision: number\n}\n\n/** Messages for `toast.promise()`. The settled forms may be functions of the value. */\nexport interface ToastPromiseMessages<T> {\n pending: string\n success: string | ((result: T) => string)\n error: string | ((error: unknown) => string)\n}\n\n/**\n * Hand every `toast.*` call to another notification library instead.\n *\n * `component` is rendered by `<VddToasts>` in place of the built-in list; `null` means the\n * adapter owns its own DOM and `<VddToasts>` renders nothing at all.\n */\nexport interface ToastAdapter {\n show(id: string, message: string, options: ResolvedToastOptions): void\n update(id: string, message: string, patch: Partial<ResolvedToastOptions>): void\n /** With no id, dismiss everything. */\n dismiss(id?: string): void\n component: Component | null\n}\n\n/** The live queue. Exported for `<VddToasts>` and for tests; applications use `toast`. */\nexport const toastQueue = reactive<{ items: ToastRecord[]; adapter: ToastAdapter | null }>({\n items: [],\n adapter: null,\n})\n\nlet counter = 0\n\nfunction show(message: string, options: ToastOptions = {}): string {\n const id = options.id ?? `vdd-toast-${++counter}`\n const resolved = { ...options, id }\n\n const existing = toastQueue.items.find(t => t.id === id)\n if (existing) {\n // Dedup: update in place rather than stacking a second copy. Also the path\n // `toast.promise()` takes when the promise settles.\n existing.message = message\n existing.options = { ...existing.options, ...resolved }\n existing.exiting = false\n existing.revision++\n toastQueue.adapter?.update(id, message, resolved as Partial<ResolvedToastOptions>)\n return id\n }\n\n toastQueue.items.push({ id, message, options: resolved, exiting: false, revision: 0 })\n return id\n}\n\n/** Begin the exit transition. `<VddToasts>` removes the record when it finishes. */\nexport function startExit(id: string): void {\n const item = toastQueue.items.find(t => t.id === id)\n if (!item || item.exiting) return\n item.exiting = true\n item.options.onClose?.()\n}\n\n/** Drop a record outright — called once its exit transition has ended. */\nexport function removeToast(id: string): void {\n const index = toastQueue.items.findIndex(t => t.id === id)\n if (index !== -1) toastQueue.items.splice(index, 1)\n}\n\n/**\n * Dismiss one toast, or all of them.\n *\n * This only *marks* — the exit transition and the record's eventual removal belong to\n * `<VddToasts>`, which is the only thing that knows whether a given toast is on screen at\n * all or still queued behind `maxVisible`. A queued toast has nothing to animate away, so\n * the container drops it outright.\n */\nfunction dismiss(id?: string): void {\n if (toastQueue.adapter) { toastQueue.adapter.dismiss(id); return }\n if (id === undefined) {\n for (const t of [...toastQueue.items]) startExit(t.id)\n return\n }\n startExit(id)\n}\n\nexport interface ToastFunction {\n (message: string, options?: ToastOptions): string\n info(message: string, options?: ToastOptions): string\n success(message: string, options?: ToastOptions): string\n warning(message: string, options?: ToastOptions): string\n error(message: string, options?: ToastOptions): string\n /** Dismiss one toast, or all of them when called with no id. */\n dismiss(id?: string): void\n /**\n * Track a promise: a sticky \"pending\" toast immediately, updated in place on settlement.\n * Returns the same promise, so it can be dropped into an existing chain.\n */\n promise<T>(promise: Promise<T>, messages: ToastPromiseMessages<T>, options?: ToastOptions): Promise<T>\n}\n\n/**\n * Show a notification, from anywhere — a component, a store, a service, an interceptor.\n *\n * Mount `<VddToasts>` once to render them. Calling with none mounted is not an error: the\n * toast sits in the queue and appears if a container mounts later.\n *\n * ```ts\n * toast.success('Layout saved')\n * toast.error('Upload failed', { duration: 0 }) // sticky\n * await toast.promise(save(), { pending: 'Saving…', success: 'Saved', error: 'Failed' })\n * ```\n */\nexport const toast: ToastFunction = Object.assign(\n (message: string, options?: ToastOptions) => show(message, options),\n {\n info: (message: string, options?: ToastOptions) => show(message, { ...options, type: 'info' }),\n success: (message: string, options?: ToastOptions) => show(message, { ...options, type: 'success' }),\n warning: (message: string, options?: ToastOptions) => show(message, { ...options, type: 'warning' }),\n error: (message: string, options?: ToastOptions) => show(message, { ...options, type: 'error' }),\n dismiss,\n promise<T>(promise: Promise<T>, messages: ToastPromiseMessages<T>, options?: ToastOptions): Promise<T> {\n const id = show(messages.pending, { ...options, type: 'info', duration: 0 })\n promise.then(\n (result) => {\n const text = typeof messages.success === 'function' ? messages.success(result) : messages.success\n show(text, { ...options, id, type: 'success', duration: options?.duration ?? 5000 })\n },\n (error: unknown) => {\n const text = typeof messages.error === 'function' ? messages.error(error) : messages.error\n show(text, { ...options, id, type: 'error', duration: options?.duration ?? 5000 })\n },\n )\n return promise\n },\n },\n)\n\n/** Empty the queue with no animation. For tests and for a full application teardown. */\nexport function resetToasts(): void {\n toastQueue.items.splice(0)\n toastQueue.adapter = null\n}\n","<script setup lang=\"ts\">\n/** The built-in per-type icons. Replaced per toast by `toast(msg, { icon })`. */\nimport type { ToastType } from '../core/toast'\n\ndefineProps<{ type: ToastType }>()\n</script>\n\n<template>\n <svg class=\"vdd-toast__icon\" viewBox=\"0 0 16 16\" fill=\"none\" aria-hidden=\"true\">\n <template v-if=\"type === 'warning'\">\n <path d=\"M8 2.5L14 13.5H2L8 2.5z\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linejoin=\"round\" />\n <path d=\"M8 7v2.5M8 11.5v.01\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" />\n </template>\n <template v-else>\n <circle cx=\"8\" cy=\"8\" r=\"7\" stroke=\"currentColor\" stroke-width=\"1.5\" />\n <path\n v-if=\"type === 'success'\"\n d=\"M5 8l2 2 4-4\"\n stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\n />\n <path\n v-else-if=\"type === 'error'\"\n d=\"M5.5 5.5l5 5M10.5 5.5l-5 5\"\n stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\"\n />\n <path v-else d=\"M8 5v.01M8 7.5v3.5\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" />\n </template>\n </svg>\n</template>\n","<script setup lang=\"ts\">\n/** The built-in per-type icons. Replaced per toast by `toast(msg, { icon })`. */\nimport type { ToastType } from '../core/toast'\n\ndefineProps<{ type: ToastType }>()\n</script>\n\n<template>\n <svg class=\"vdd-toast__icon\" viewBox=\"0 0 16 16\" fill=\"none\" aria-hidden=\"true\">\n <template v-if=\"type === 'warning'\">\n <path d=\"M8 2.5L14 13.5H2L8 2.5z\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linejoin=\"round\" />\n <path d=\"M8 7v2.5M8 11.5v.01\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" />\n </template>\n <template v-else>\n <circle cx=\"8\" cy=\"8\" r=\"7\" stroke=\"currentColor\" stroke-width=\"1.5\" />\n <path\n v-if=\"type === 'success'\"\n d=\"M5 8l2 2 4-4\"\n stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\n />\n <path\n v-else-if=\"type === 'error'\"\n d=\"M5.5 5.5l5 5M10.5 5.5l-5 5\"\n stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\"\n />\n <path v-else d=\"M8 5v.01M8 7.5v3.5\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" />\n </template>\n </svg>\n</template>\n","<script setup lang=\"ts\">\n/**\n * One toast card: its entry class, its auto-dismiss timer, and its exit.\n *\n * The `max-height` bookkeeping is the subtle part and is inherited wholesale from rdd,\n * where it was arrived at the hard way. The card is clipped to its own content height so the\n * exit can animate that height to zero. Content that grows *after* mount — which is exactly\n * what `toast.promise()` does when a one-line \"Saving…\" becomes a three-line error — would\n * then be clipped by a stale cap. So a ResizeObserver watches the *inner body*, which is\n * unconstrained and therefore does report growth, and reads the *outer card's* `scrollHeight`,\n * which reports true content height even while a stale cap is clipping it (`offsetHeight`\n * would just return the stale cap). Frozen once exiting starts, where the stylesheet's\n * `max-height: 0` takes over.\n */\nimport { computed, onBeforeUnmount, onMounted, ref, useTemplateRef, watch } from 'vue'\nimport type { Component } from 'vue'\nimport type { ToastRecord, ToastType } from '../core/toast'\nimport { startExit } from '../core/toast'\nimport VddToastIcon from './VddToastIcon.vue'\n\nconst props = defineProps<{\n toast: ToastRecord\n /** A left-hand container slides in from the other side. */\n isLeft: boolean\n showProgress: boolean\n pauseOnHover: boolean\n animation: 'slide' | 'fade' | 'none'\n defaultDuration: number\n defaultClosable: boolean\n}>()\n\nconst emit = defineEmits<{ exited: [id: string] }>()\n\nconst card = useTemplateRef<HTMLDivElement>('card')\nconst body = useTemplateRef<HTMLDivElement>('body')\n\nconst type = computed<ToastType>(() => props.toast.options.type ?? 'info')\nconst duration = computed(() => props.toast.options.duration ?? props.defaultDuration)\nconst closable = computed(() => props.toast.options.closable ?? props.defaultClosable)\nconst icon = computed<Component | null>(() => props.toast.options.icon ?? null)\n\n// ── entry ──────────────────────────────────────────────────────────────────\nconst entryClass = ref(\n props.animation === 'none' ? 'vdd-toast--visible'\n : props.animation === 'fade' ? 'vdd-toast--fade-entering'\n : props.isLeft ? 'vdd-toast--entering-left'\n : 'vdd-toast--entering',\n)\n\n// ── auto-dismiss ───────────────────────────────────────────────────────────\nconst paused = ref(false)\nlet timer: ReturnType<typeof setTimeout> | null = null\nlet remaining = 0\nlet startedAt = 0\n\nfunction schedule(ms: number): void {\n if (ms <= 0) return\n startedAt = Date.now()\n timer = setTimeout(() => startExit(props.toast.id), ms)\n}\n\nfunction clear(): void {\n if (timer) { clearTimeout(timer); timer = null }\n}\n\n/** `revision` is in the key so an in-place update restarts the timer, as rdd's effect did. */\nwatch(\n [duration, () => props.toast.revision],\n () => {\n clear()\n remaining = duration.value\n schedule(duration.value)\n },\n { immediate: true },\n)\n\nfunction onEnter(): void {\n if (!props.pauseOnHover || duration.value === 0 || !timer) return\n clear()\n remaining = Math.max(0, remaining - (Date.now() - startedAt))\n paused.value = true\n}\n\nfunction onLeave(): void {\n if (!props.pauseOnHover || duration.value === 0) return\n paused.value = false\n schedule(remaining)\n}\n\n// ── height sync ────────────────────────────────────────────────────────────\nlet observer: ResizeObserver | null = null\n\nfunction applyHeight(): void {\n const el = card.value\n if (el && !props.toast.exiting) el.style.maxHeight = `${el.scrollHeight}px`\n}\n\nonMounted(() => {\n applyHeight()\n // jsdom has no ResizeObserver; the height sync is a browser refinement, not behaviour.\n if (typeof ResizeObserver !== 'undefined' && body.value) {\n observer = new ResizeObserver(applyHeight)\n observer.observe(body.value)\n }\n if (props.animation !== 'none') {\n requestAnimationFrame(() => { entryClass.value = 'vdd-toast--visible' })\n }\n})\n\n// ── exit ───────────────────────────────────────────────────────────────────\nlet exitFallback: ReturnType<typeof setTimeout> | null = null\n\nwatch(() => props.toast.exiting, (exiting) => {\n if (!exiting) return\n clear()\n observer?.disconnect()\n observer = null\n if (props.animation === 'none' || !card.value) { emit('exited', props.toast.id); return }\n // A transition that never runs — a hidden tab, `prefers-reduced-motion`, a test — would\n // strand the record, so the timeout is a floor, not an optimisation.\n exitFallback = setTimeout(() => emit('exited', props.toast.id), 520)\n}, { immediate: true })\n\nfunction onTransitionEnd(event: TransitionEvent): void {\n if (props.toast.exiting && event.propertyName === 'max-height') emit('exited', props.toast.id)\n}\n\nonBeforeUnmount(() => {\n clear()\n if (exitFallback) clearTimeout(exitFallback)\n observer?.disconnect()\n})\n</script>\n\n<template>\n <div\n ref=\"card\"\n role=\"status\"\n aria-live=\"polite\"\n class=\"vdd-toast\"\n :class=\"[\n `vdd-toast--${type}`,\n entryClass,\n { 'vdd-toast--exiting': toast.exiting, 'vdd-toast--paused': paused },\n ]\"\n :data-vdd-toast=\"toast.id\"\n @mouseenter=\"onEnter\"\n @mouseleave=\"onLeave\"\n @transitionend=\"onTransitionEnd\"\n >\n <component :is=\"icon\" v-if=\"icon\" />\n <VddToastIcon v-else :type=\"type\" />\n\n <div ref=\"body\" class=\"vdd-toast__body\">\n <component :is=\"toast.options.content\" v-if=\"toast.options.content\" v-bind=\"toast.options.contentProps\" />\n <template v-else>{{ toast.message }}</template>\n </div>\n\n <button\n v-if=\"closable\"\n type=\"button\"\n class=\"vdd-toast__close\"\n aria-label=\"Close notification\"\n data-vdd-toast-close\n @click=\"startExit(toast.id)\"\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 16 16\" fill=\"none\" aria-hidden=\"true\">\n <path d=\"M4 4l8 8M12 4l-8 8\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" />\n </svg>\n </button>\n\n <div\n v-if=\"showProgress && duration > 0\"\n class=\"vdd-toast__progress\"\n :style=\"{ animationDuration: `${duration}ms` }\"\n />\n </div>\n</template>\n","<script setup lang=\"ts\">\n/**\n * One toast card: its entry class, its auto-dismiss timer, and its exit.\n *\n * The `max-height` bookkeeping is the subtle part and is inherited wholesale from rdd,\n * where it was arrived at the hard way. The card is clipped to its own content height so the\n * exit can animate that height to zero. Content that grows *after* mount — which is exactly\n * what `toast.promise()` does when a one-line \"Saving…\" becomes a three-line error — would\n * then be clipped by a stale cap. So a ResizeObserver watches the *inner body*, which is\n * unconstrained and therefore does report growth, and reads the *outer card's* `scrollHeight`,\n * which reports true content height even while a stale cap is clipping it (`offsetHeight`\n * would just return the stale cap). Frozen once exiting starts, where the stylesheet's\n * `max-height: 0` takes over.\n */\nimport { computed, onBeforeUnmount, onMounted, ref, useTemplateRef, watch } from 'vue'\nimport type { Component } from 'vue'\nimport type { ToastRecord, ToastType } from '../core/toast'\nimport { startExit } from '../core/toast'\nimport VddToastIcon from './VddToastIcon.vue'\n\nconst props = defineProps<{\n toast: ToastRecord\n /** A left-hand container slides in from the other side. */\n isLeft: boolean\n showProgress: boolean\n pauseOnHover: boolean\n animation: 'slide' | 'fade' | 'none'\n defaultDuration: number\n defaultClosable: boolean\n}>()\n\nconst emit = defineEmits<{ exited: [id: string] }>()\n\nconst card = useTemplateRef<HTMLDivElement>('card')\nconst body = useTemplateRef<HTMLDivElement>('body')\n\nconst type = computed<ToastType>(() => props.toast.options.type ?? 'info')\nconst duration = computed(() => props.toast.options.duration ?? props.defaultDuration)\nconst closable = computed(() => props.toast.options.closable ?? props.defaultClosable)\nconst icon = computed<Component | null>(() => props.toast.options.icon ?? null)\n\n// ── entry ──────────────────────────────────────────────────────────────────\nconst entryClass = ref(\n props.animation === 'none' ? 'vdd-toast--visible'\n : props.animation === 'fade' ? 'vdd-toast--fade-entering'\n : props.isLeft ? 'vdd-toast--entering-left'\n : 'vdd-toast--entering',\n)\n\n// ── auto-dismiss ───────────────────────────────────────────────────────────\nconst paused = ref(false)\nlet timer: ReturnType<typeof setTimeout> | null = null\nlet remaining = 0\nlet startedAt = 0\n\nfunction schedule(ms: number): void {\n if (ms <= 0) return\n startedAt = Date.now()\n timer = setTimeout(() => startExit(props.toast.id), ms)\n}\n\nfunction clear(): void {\n if (timer) { clearTimeout(timer); timer = null }\n}\n\n/** `revision` is in the key so an in-place update restarts the timer, as rdd's effect did. */\nwatch(\n [duration, () => props.toast.revision],\n () => {\n clear()\n remaining = duration.value\n schedule(duration.value)\n },\n { immediate: true },\n)\n\nfunction onEnter(): void {\n if (!props.pauseOnHover || duration.value === 0 || !timer) return\n clear()\n remaining = Math.max(0, remaining - (Date.now() - startedAt))\n paused.value = true\n}\n\nfunction onLeave(): void {\n if (!props.pauseOnHover || duration.value === 0) return\n paused.value = false\n schedule(remaining)\n}\n\n// ── height sync ────────────────────────────────────────────────────────────\nlet observer: ResizeObserver | null = null\n\nfunction applyHeight(): void {\n const el = card.value\n if (el && !props.toast.exiting) el.style.maxHeight = `${el.scrollHeight}px`\n}\n\nonMounted(() => {\n applyHeight()\n // jsdom has no ResizeObserver; the height sync is a browser refinement, not behaviour.\n if (typeof ResizeObserver !== 'undefined' && body.value) {\n observer = new ResizeObserver(applyHeight)\n observer.observe(body.value)\n }\n if (props.animation !== 'none') {\n requestAnimationFrame(() => { entryClass.value = 'vdd-toast--visible' })\n }\n})\n\n// ── exit ───────────────────────────────────────────────────────────────────\nlet exitFallback: ReturnType<typeof setTimeout> | null = null\n\nwatch(() => props.toast.exiting, (exiting) => {\n if (!exiting) return\n clear()\n observer?.disconnect()\n observer = null\n if (props.animation === 'none' || !card.value) { emit('exited', props.toast.id); return }\n // A transition that never runs — a hidden tab, `prefers-reduced-motion`, a test — would\n // strand the record, so the timeout is a floor, not an optimisation.\n exitFallback = setTimeout(() => emit('exited', props.toast.id), 520)\n}, { immediate: true })\n\nfunction onTransitionEnd(event: TransitionEvent): void {\n if (props.toast.exiting && event.propertyName === 'max-height') emit('exited', props.toast.id)\n}\n\nonBeforeUnmount(() => {\n clear()\n if (exitFallback) clearTimeout(exitFallback)\n observer?.disconnect()\n})\n</script>\n\n<template>\n <div\n ref=\"card\"\n role=\"status\"\n aria-live=\"polite\"\n class=\"vdd-toast\"\n :class=\"[\n `vdd-toast--${type}`,\n entryClass,\n { 'vdd-toast--exiting': toast.exiting, 'vdd-toast--paused': paused },\n ]\"\n :data-vdd-toast=\"toast.id\"\n @mouseenter=\"onEnter\"\n @mouseleave=\"onLeave\"\n @transitionend=\"onTransitionEnd\"\n >\n <component :is=\"icon\" v-if=\"icon\" />\n <VddToastIcon v-else :type=\"type\" />\n\n <div ref=\"body\" class=\"vdd-toast__body\">\n <component :is=\"toast.options.content\" v-if=\"toast.options.content\" v-bind=\"toast.options.contentProps\" />\n <template v-else>{{ toast.message }}</template>\n </div>\n\n <button\n v-if=\"closable\"\n type=\"button\"\n class=\"vdd-toast__close\"\n aria-label=\"Close notification\"\n data-vdd-toast-close\n @click=\"startExit(toast.id)\"\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 16 16\" fill=\"none\" aria-hidden=\"true\">\n <path d=\"M4 4l8 8M12 4l-8 8\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" />\n </svg>\n </button>\n\n <div\n v-if=\"showProgress && duration > 0\"\n class=\"vdd-toast__progress\"\n :style=\"{ animationDuration: `${duration}ms` }\"\n />\n </div>\n</template>\n","<script setup lang=\"ts\">\n/**\n * The notification host. Mount once, anywhere.\n *\n * `maxVisible` is a `computed` slice of the one queue, not a second array that has to be\n * shifted from. rdd kept the overflow in a separate `queueRef` and promoted from it\n * imperatively when a toast finished exiting; here a toast leaving the list means the next\n * one is simply inside the slice, so promotion is not code.\n */\nimport { computed, onBeforeUnmount, watch } from 'vue'\nimport { removeToast, toastQueue } from '../core/toast'\nimport type { ToastAdapter, ToastPosition } from '../core/toast'\nimport VddToastItem from './VddToastItem.vue'\n\nconst props = withDefaults(defineProps<{\n /** @default 'top-right' */\n position?: ToastPosition\n /** How many show at once. The rest wait. @default 3 */\n maxVisible?: number\n /** Auto-dismiss delay in ms; `0` makes every toast sticky. @default 5000 */\n defaultDuration?: number\n /** @default true */\n defaultClosable?: boolean\n /** Hold the timer while the pointer is over a toast. @default true */\n pauseOnHover?: boolean\n /** @default 'slide' */\n animation?: 'slide' | 'fade' | 'none'\n /** Put the newest toast at the top of the stack. @default false */\n newestOnTop?: boolean\n /** A countdown bar along the bottom of each toast. @default false */\n progressBar?: boolean\n /** Card width in pixels. @default 320 */\n width?: number\n /** Hand every `toast.*` call to another library instead. */\n adapter?: ToastAdapter\n}>(), {\n position: 'top-right',\n maxVisible: 3,\n defaultDuration: 5000,\n defaultClosable: true,\n pauseOnHover: true,\n animation: 'slide',\n newestOnTop: false,\n progressBar: false,\n width: 320,\n})\n\n// An adapter is registered on the store, so `toast.*` reaches it even when called from\n// outside any component — the whole point of the singleton.\nwatch(() => props.adapter, (adapter) => { toastQueue.adapter = adapter ?? null }, { immediate: true })\nonBeforeUnmount(() => { if (toastQueue.adapter === props.adapter) toastQueue.adapter = null })\n\n/**\n * What is on screen: the oldest `maxVisible` toasts, with the ones still animating out kept\n * in place so they can finish rather than being yanked as the next one arrives.\n */\nconst visible = computed(() => {\n const result = []\n let shown = 0\n for (const item of toastQueue.items) {\n if (item.exiting) { result.push(item); continue }\n if (shown >= props.maxVisible) break\n result.push(item)\n shown++\n }\n return props.newestOnTop ? [...result].reverse() : result\n})\n\n/**\n * A toast dismissed while still queued has never been rendered, so nothing will ever report\n * its transition ending. Drop it here — this component is the only thing that knows whether\n * a given toast was on screen at all.\n */\nwatch(() => toastQueue.items.map(t => `${t.id}:${t.exiting}`).join(','), () => {\n const onScreen = new Set(visible.value.map(t => t.id))\n for (const item of [...toastQueue.items]) {\n if (item.exiting && !onScreen.has(item.id)) removeToast(item.id)\n }\n}, { immediate: true })\n\nconst isLeft = computed(() => props.position.endsWith('left'))\n</script>\n\n<template>\n <Teleport v-if=\"adapter\" to=\"body\">\n <component :is=\"adapter.component\" v-if=\"adapter.component\" :position=\"position\" />\n </Teleport>\n\n <Teleport v-else to=\"body\">\n <div\n class=\"vdd-toast-container\"\n :class=\"[\n `vdd-toast-container--${position}`,\n newestOnTop ? 'vdd-toast-container--newest-top' : 'vdd-toast-container--newest-bottom',\n ]\"\n :style=\"{ width: `${width}px` }\"\n aria-label=\"Notifications\"\n aria-live=\"polite\"\n data-vdd-toasts\n >\n <VddToastItem\n v-for=\"item in visible\"\n :key=\"item.id\"\n :toast=\"item\"\n :is-left=\"isLeft\"\n :show-progress=\"progressBar\"\n :pause-on-hover=\"pauseOnHover\"\n :animation=\"animation\"\n :default-duration=\"defaultDuration\"\n :default-closable=\"defaultClosable\"\n @exited=\"removeToast\"\n />\n </div>\n </Teleport>\n</template>\n","<script setup lang=\"ts\">\n/**\n * The notification host. Mount once, anywhere.\n *\n * `maxVisible` is a `computed` slice of the one queue, not a second array that has to be\n * shifted from. rdd kept the overflow in a separate `queueRef` and promoted from it\n * imperatively when a toast finished exiting; here a toast leaving the list means the next\n * one is simply inside the slice, so promotion is not code.\n */\nimport { computed, onBeforeUnmount, watch } from 'vue'\nimport { removeToast, toastQueue } from '../core/toast'\nimport type { ToastAdapter, ToastPosition } from '../core/toast'\nimport VddToastItem from './VddToastItem.vue'\n\nconst props = withDefaults(defineProps<{\n /** @default 'top-right' */\n position?: ToastPosition\n /** How many show at once. The rest wait. @default 3 */\n maxVisible?: number\n /** Auto-dismiss delay in ms; `0` makes every toast sticky. @default 5000 */\n defaultDuration?: number\n /** @default true */\n defaultClosable?: boolean\n /** Hold the timer while the pointer is over a toast. @default true */\n pauseOnHover?: boolean\n /** @default 'slide' */\n animation?: 'slide' | 'fade' | 'none'\n /** Put the newest toast at the top of the stack. @default false */\n newestOnTop?: boolean\n /** A countdown bar along the bottom of each toast. @default false */\n progressBar?: boolean\n /** Card width in pixels. @default 320 */\n width?: number\n /** Hand every `toast.*` call to another library instead. */\n adapter?: ToastAdapter\n}>(), {\n position: 'top-right',\n maxVisible: 3,\n defaultDuration: 5000,\n defaultClosable: true,\n pauseOnHover: true,\n animation: 'slide',\n newestOnTop: false,\n progressBar: false,\n width: 320,\n})\n\n// An adapter is registered on the store, so `toast.*` reaches it even when called from\n// outside any component — the whole point of the singleton.\nwatch(() => props.adapter, (adapter) => { toastQueue.adapter = adapter ?? null }, { immediate: true })\nonBeforeUnmount(() => { if (toastQueue.adapter === props.adapter) toastQueue.adapter = null })\n\n/**\n * What is on screen: the oldest `maxVisible` toasts, with the ones still animating out kept\n * in place so they can finish rather than being yanked as the next one arrives.\n */\nconst visible = computed(() => {\n const result = []\n let shown = 0\n for (const item of toastQueue.items) {\n if (item.exiting) { result.push(item); continue }\n if (shown >= props.maxVisible) break\n result.push(item)\n shown++\n }\n return props.newestOnTop ? [...result].reverse() : result\n})\n\n/**\n * A toast dismissed while still queued has never been rendered, so nothing will ever report\n * its transition ending. Drop it here — this component is the only thing that knows whether\n * a given toast was on screen at all.\n */\nwatch(() => toastQueue.items.map(t => `${t.id}:${t.exiting}`).join(','), () => {\n const onScreen = new Set(visible.value.map(t => t.id))\n for (const item of [...toastQueue.items]) {\n if (item.exiting && !onScreen.has(item.id)) removeToast(item.id)\n }\n}, { immediate: true })\n\nconst isLeft = computed(() => props.position.endsWith('left'))\n</script>\n\n<template>\n <Teleport v-if=\"adapter\" to=\"body\">\n <component :is=\"adapter.component\" v-if=\"adapter.component\" :position=\"position\" />\n </Teleport>\n\n <Teleport v-else to=\"body\">\n <div\n class=\"vdd-toast-container\"\n :class=\"[\n `vdd-toast-container--${position}`,\n newestOnTop ? 'vdd-toast-container--newest-top' : 'vdd-toast-container--newest-bottom',\n ]\"\n :style=\"{ width: `${width}px` }\"\n aria-label=\"Notifications\"\n aria-live=\"polite\"\n data-vdd-toasts\n >\n <VddToastItem\n v-for=\"item in visible\"\n :key=\"item.id\"\n :toast=\"item\"\n :is-left=\"isLeft\"\n :show-progress=\"progressBar\"\n :pause-on-hover=\"pauseOnHover\"\n :animation=\"animation\"\n :default-duration=\"defaultDuration\"\n :default-closable=\"defaultClosable\"\n @exited=\"removeToast\"\n />\n </div>\n </Teleport>\n</template>\n","/**\n * The stretch algebra for docked inner widgets.\n *\n * A docked widget normally pins one end of each axis and carries an explicit size. A\n * *stretched* axis pins **both** ends and carries no size at all, so the widget tracks its\n * panel as the panel resizes — with no ResizeObserver and no JavaScript, because CSS\n * already does exactly this. \"Full width\" is not a width; it is a second pin.\n *\n * Pure functions over two values, so the nine placements (three inline states × three\n * block states, minus the impossible) are testable without a DOM.\n */\nimport type { FloatAnchor } from '../types'\n\n/** Which axes span the panel instead of carrying a size. */\nexport type Stretch = 'width' | 'height' | 'both'\n\n/** Where a docked widget sits: its corner, plus which axes span. Reported as one unit\n * because a single gesture can change both, and reporting separately would surface a\n * state that is never valid. */\nexport interface PanelFloatPlacement {\n anchor: FloatAnchor\n stretch: Stretch | null\n}\n\nexport const stretchesInline = (s: Stretch | null): boolean => s === 'width' || s === 'both'\nexport const stretchesBlock = (s: Stretch | null): boolean => s === 'height' || s === 'both'\n\n/** Add one axis, keeping whatever was already stretched. */\nexport const addAxis = (s: Stretch | null, axis: 'inline' | 'block'): Stretch =>\n axis === 'inline'\n ? (stretchesBlock(s) ? 'both' : 'width')\n : (stretchesInline(s) ? 'both' : 'height')\n\n/** Drop one axis, keeping the other. */\nexport const releaseAxis = (s: Stretch | null, axis: 'inline' | 'block'): Stretch | null =>\n axis === 'inline'\n ? (s === 'both' ? 'height' : stretchesInline(s) ? null : s)\n : (s === 'both' ? 'width' : stretchesBlock(s) ? null : s)\n\nexport const ANCHORS: readonly FloatAnchor[] = ['top-left', 'top-right', 'bottom-left', 'bottom-right']\n\n/**\n * Which stacking buckets a placement occupies.\n *\n * The four corner buckets are a proxy for *\"do these overlap on the inline axis?\"* — two\n * widgets in one corner overlap and stack; opposite corners sit side by side and do not. A\n * full-width strip overlaps everything on its edge, so it belongs to **both** buckets of\n * that edge and pushes whatever is stacked in either.\n *\n * A block-stretched widget spans the very axis stacking uses to separate siblings, so it\n * cannot participate at all and occupies no bucket — z-order decides any overlap.\n *\n * Computing true inline overlap was considered and rejected: widths change continuously\n * during a resize drag, so widgets would reshuffle mid-gesture.\n */\nexport const bucketsFor = (anchor: FloatAnchor, stretch: Stretch | null): FloatAnchor[] => {\n if (stretchesBlock(stretch)) return []\n if (stretchesInline(stretch)) {\n return anchor.startsWith('top-') ? ['top-left', 'top-right'] : ['bottom-left', 'bottom-right']\n }\n return [anchor]\n}\n\n/** Replace the inline half of a corner, leaving the block half alone. */\nexport const withInlineHalf = (a: FloatAnchor, half: 'left' | 'right'): FloatAnchor =>\n `${a.startsWith('top-') ? 'top' : 'bottom'}-${half}` as FloatAnchor\n\n/** Replace the block half of a corner, leaving the inline half alone. */\nexport const withBlockHalf = (a: FloatAnchor, half: 'top' | 'bottom'): FloatAnchor =>\n `${half}-${a.endsWith('-right') ? 'right' : 'left'}` as FloatAnchor\n","/**\n * Geometry and rules for the panel overlay — the toolbars and floating widgets that live\n * *inside* one panel, over its content.\n *\n * Everything here is pure, so the parts that were hardest to get right in rdd — which resize\n * handles a placement offers, and where a docked widget is allowed to grow to — are testable\n * without a DOM. The stretch algebra they build on is in `core/stretch.ts`.\n */\nimport type { FloatAnchor } from '../types'\nimport type { ResizeDir } from './dragResize'\nimport { stretchesBlock, stretchesInline } from './stretch'\nimport type { Stretch } from './stretch'\n\n/** Which edge of the panel a toolbar attaches to. */\nexport type ToolbarPosition = 'top' | 'bottom' | 'left' | 'right'\n/** Background treatment of a toolbar strip. */\nexport type ToolbarVariant = 'transparent' | 'frosted' | 'solid'\n/** Button treatment, inherited by the toolbar's own buttons. */\nexport type ButtonVariant = 'ghost' | 'soft' | 'outlined' | 'filled'\n\n/** Smallest a widget may be resized to. */\nexport const MIN_W = 120\nexport const MIN_H = 60\n/** Gutter between a docked widget and the panel's inline edge. */\nexport const DOCK_INSET = 8\n/** Gap between widgets stacked in the same corner. */\nexport const DOCK_GAP = 8\n/** Side of the corner drop zones, in pixels. */\nexport const DROP_ZONE_SIZE = 80\n/** How far the header must move before a click becomes a drag. */\nexport const DRAG_THRESHOLD = 4\n\n/**\n * Resize-to-stretch snapping thresholds, asymmetric on purpose.\n *\n * A drag arms within `SNAP_IN` of the full extent but only disarms once it pulls back past the\n * wider `SNAP_OUT`. Without that hysteresis, releasing a stretched axis by dragging a few\n * pixels inward immediately re-arms and snaps straight back on release, which makes the\n * gesture feel broken.\n */\nexport const SNAP_IN = 16\nexport const SNAP_OUT = 40\n\n/** Space claimed by toolbars. Block values are physical; inline values are logical. */\nexport interface ToolbarInsets {\n top: number\n bottom: number\n /** Inline-start, so `left` under LTR and `right` under RTL. */\n inlineStart: number\n inlineEnd: number\n}\n\nexport const NO_INSETS: ToolbarInsets = { top: 0, bottom: 0, inlineStart: 0, inlineEnd: 0 }\n\n/** The four corners. */\nexport const ANCHORS: readonly FloatAnchor[] = ['top-left', 'top-right', 'bottom-left', 'bottom-right']\n\n/** Which corner zone a pointer is over, or `null` for the middle. */\nexport function hoveredZone(\n rect: { left: number; top: number; width: number; height: number },\n clientX: number,\n clientY: number,\n): FloatAnchor | null {\n const x = clientX - rect.left\n const y = clientY - rect.top\n if (x < DROP_ZONE_SIZE && y < DROP_ZONE_SIZE) return 'top-left'\n if (x > rect.width - DROP_ZONE_SIZE && y < DROP_ZONE_SIZE) return 'top-right'\n if (x < DROP_ZONE_SIZE && y > rect.height - DROP_ZONE_SIZE) return 'bottom-left'\n if (x > rect.width - DROP_ZONE_SIZE && y > rect.height - DROP_ZONE_SIZE) return 'bottom-right'\n return null\n}\n\n/** A drop zone is named by its physical corner, so under RTL the inline half mirrors. */\nexport const flipZoneHorizontal = (zone: FloatAnchor): FloatAnchor =>\n zone.endsWith('-left')\n ? (zone.replace('-left', '-right') as FloatAnchor)\n : (zone.replace('-right', '-left') as FloatAnchor)\n\n/**\n * The band a docked widget may occupy, in **physical** pixels from the container's edges.\n *\n * The block axis keeps clear of top/bottom toolbars only. The inline axis also adds the\n * `DOCK_INSET` gutter, matching how a docked widget is positioned in the first place. rdd\n * fixed docked widgets *positioning* themselves over a toolbar in 5.x and never gave the\n * resize path the same treatment, so a docked widget could still be resized straight over\n * the toolbar on the far side.\n *\n * Inline is converted from logical to physical here because handle directions and measured\n * rects are physical, while a toolbar claims its space logically.\n */\nexport function dockedBand(insets: ToolbarInsets, isRtl: boolean): { left: number; right: number; top: number; bottom: number } {\n return {\n left: (isRtl ? insets.inlineEnd : insets.inlineStart) + DOCK_INSET,\n right: (isRtl ? insets.inlineStart : insets.inlineEnd) + DOCK_INSET,\n top: insets.top,\n bottom: insets.bottom,\n }\n}\n\n/**\n * Which resize handles a placement offers.\n *\n * **Free-floating:** all eight. Nothing is pinned.\n *\n * **Docked:** only the edges that can actually move. A docked widget pins one edge per axis,\n * so a handle on a pinned side would move the *opposite* edge instead of the one under the\n * cursor — an inert stub with a resize cursor on it. rdd hardcoded the five non-northern\n * directions regardless of anchor, which looked harmless for top anchors but left every\n * bottom-anchored widget with no working vertical resize at all: `n` was not rendered, and\n * `s` was the stub.\n *\n * **Stretched:** a stretched axis has both ends pinned, but both are *releasable* — dragging\n * either moves that edge and pins the opposite one. So it offers handles on both ends, which\n * is also what keeps the fully-stretched state from being a dead end with nothing to grab.\n * The corner belongs only to the all-pinned state; in a stretched state it would mix a\n * resize and a release into one gesture.\n *\n * The block axis is direction-agnostic; the inline axis is not. The pin is a logical property\n * (`inset-inline-end`) while the handle classes are physical (`.vdd-resize-e { right: 0 }`),\n * so which *physical* side is pinned depends on the reading direction.\n */\nexport function handleDirs(\n mode: 'docked' | 'free',\n anchor: FloatAnchor,\n stretch: Stretch | null,\n isRtl: boolean,\n): ResizeDir[] {\n if (mode === 'free') return ['n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw']\n\n const freeBlock: ResizeDir = anchor.startsWith('top-') ? 's' : 'n'\n const pinsPhysicalRight = anchor.endsWith('-right') !== isRtl\n const freeInline: ResizeDir = pinsPhysicalRight ? 'w' : 'e'\n\n const inlineStretched = stretchesInline(stretch)\n const blockStretched = stretchesBlock(stretch)\n\n const dirs: ResizeDir[] = []\n dirs.push(...(inlineStretched ? (['e', 'w'] as ResizeDir[]) : [freeInline]))\n dirs.push(...(blockStretched ? (['n', 's'] as ResizeDir[]) : [freeBlock]))\n if (!inlineStretched && !blockStretched) dirs.push(`${freeBlock}${freeInline}` as ResizeDir)\n return dirs\n}\n\n/**\n * Where a released axis re-pins.\n *\n * The edge under the pointer becomes the moving one and the opposite end becomes the new pin,\n * so the gesture reads exactly like an ordinary resize. Handle directions are physical and\n * anchors are logical, hence the direction term on the inline half.\n */\nexport function anchorAfterRelease(\n anchor: FloatAnchor,\n dir: ResizeDir,\n axes: { inline: boolean; block: boolean },\n isRtl: boolean,\n): FloatAnchor {\n let next = anchor\n if (axes.inline) {\n const pinsPhysicalLeft = dir.includes('e')\n next = `${next.startsWith('top-') ? 'top' : 'bottom'}-${(pinsPhysicalLeft !== isRtl) ? 'left' : 'right'}` as FloatAnchor\n }\n if (axes.block) {\n next = `${dir.includes('s') ? 'top' : 'bottom'}-${next.endsWith('-right') ? 'right' : 'left'}` as FloatAnchor\n }\n return next\n}\n\n/**\n * How far down its edge a docked widget sits, given what it is stacked behind.\n *\n * The largest offset across every bucket the widget occupies, so a strip spanning an edge\n * clears whatever is stacked in *both* of that edge's corners rather than only the one it is\n * anchored to.\n */\nexport function stackOffset(\n id: string,\n buckets: FloatAnchor[],\n stacks: Record<FloatAnchor, string[]>,\n sizes: Record<string, number>,\n fallbackHeight: number,\n): { offset: number; registered: boolean } {\n // A widget in no bucket (block-stretched) is never *in* a stack, so it must not be held\n // invisible by the not-yet-registered guard its caller applies.\n let registered = buckets.length === 0\n let offset = 0\n for (const bucket of buckets) {\n const stack = stacks[bucket] ?? []\n const index = stack.indexOf(id)\n if (index === -1) continue\n registered = true\n let own = 0\n for (let i = 0; i < index; i++) own += (sizes[stack[i]!] ?? fallbackHeight) + DOCK_GAP\n offset = Math.max(offset, own)\n }\n return { offset, registered }\n}\n","/**\n * The state one `<VddPanelOverlay>` shares with its toolbars and widgets.\n *\n * rdd split this across **three** React contexts — `PanelToolbarCtx`, `PanelManagerCtx` and\n * `PanelOverlayCtx` — not because they were three concerns but to stop a toolbar re-rendering\n * every time a widget gained focus: a React context value is one object, so any change to it\n * re-renders every consumer. Vue tracks each ref separately, so a toolbar that reads only the\n * insets is untouched when `topId` changes. One store, and the isolation is a property of the\n * reactivity rather than of how the state was carved up.\n */\nimport { markRaw, reactive, ref, shallowRef } from 'vue'\nimport type { Component, InjectionKey, Ref } from 'vue'\nimport type { FloatAnchor, Label } from '../types'\nimport type { Stretch, PanelFloatPlacement } from './stretch'\nimport { bucketsFor } from './stretch'\nimport { ANCHORS } from './panelOverlay'\nimport type { ToolbarInsets, ToolbarPosition } from './panelOverlay'\n\n/** A widget opened through `useFloatingWidgets()` rather than placed in the template. */\nexport interface ManagedWidget {\n /**\n * The widget's header text: plain, or a localisable descriptor.\n *\n * A descriptor matters most here of all the title surfaces, because this object is *stored*:\n * a resolved string handed to `openManaged` is frozen at the language that was current when\n * the widget opened, while a descriptor is resolved on every render and so follows a locale\n * change with no reopen.\n */\n title: Label\n icon?: Component\n /** Rendered as the widget's content. */\n component: Component\n props?: Record<string, unknown>\n anchor?: FloatAnchor\n width?: number\n height?: number\n stretch?: Stretch | null\n}\n\nexport interface OverlayStacks {\n 'top-left': string[]\n 'top-right': string[]\n 'bottom-left': string[]\n 'bottom-right': string[]\n}\n\nexport interface PanelOverlayStore {\n /** The overlay root element, for measuring drop zones. */\n container: Ref<HTMLElement | null>\n /** Space each toolbar edge claims. */\n insets: ToolbarInsets\n /** The widget on top, or `null`. */\n topId: Ref<string | null>\n zOrders: Record<string, number>\n /** Which widgets are stacked in each corner bucket. */\n stacks: OverlayStacks\n /** Each docked widget's block size, so its stack peers can offset past it. */\n dockedSizes: Record<string, number>\n /** The widget being dragged, or `null`. */\n draggingId: Ref<string | null>\n /** The corner the drag is currently over, or `null`. */\n hovered: Ref<FloatAnchor | null>\n /** Widgets opened through `useFloatingWidgets()`. */\n managed: Map<string, ManagedWidget>\n /** Bumped whenever `managed` changes, since a `Map` is not reactive by itself. */\n managedVersion: Ref<number>\n\n registerToolbar(position: ToolbarPosition, size: number): void\n unregisterToolbar(position: ToolbarPosition): void\n focus(id: string): void\n dock(id: string, anchor: FloatAnchor, stretch: Stretch | null): void\n undock(id: string): void\n reportSize(id: string, size: number): void\n openManaged(id: string, widget: ManagedWidget): void\n closeManaged(id: string): void\n closeAllManaged(): void\n managedIds(): string[]\n}\n\nexport const PANEL_OVERLAY_KEY = Symbol('vdd-panel-overlay') as InjectionKey<PanelOverlayStore>\n\nexport function createPanelOverlayStore(): PanelOverlayStore {\n const container = shallowRef<HTMLElement | null>(null)\n const insets = reactive<ToolbarInsets>({ top: 0, bottom: 0, inlineStart: 0, inlineEnd: 0 })\n const topId = ref<string | null>(null)\n const zOrders = reactive<Record<string, number>>({})\n const stacks = reactive<OverlayStacks>({\n 'top-left': [], 'top-right': [], 'bottom-left': [], 'bottom-right': [],\n })\n const dockedSizes = reactive<Record<string, number>>({})\n const draggingId = ref<string | null>(null)\n const hovered = ref<FloatAnchor | null>(null)\n const managed = new Map<string, ManagedWidget>()\n const managedVersion = ref(0)\n\n let zCounter = 100\n\n /** Which inset field an edge claims. `left`/`right` are logical, matching how a toolbar positions itself. */\n const field = (position: ToolbarPosition): keyof ToolbarInsets =>\n position === 'top' ? 'top' : position === 'bottom' ? 'bottom'\n : position === 'left' ? 'inlineStart' : 'inlineEnd'\n\n return {\n container, insets, topId, zOrders, stacks, dockedSizes, draggingId, hovered,\n managed, managedVersion,\n\n registerToolbar: (position, size) => { insets[field(position)] = size },\n unregisterToolbar: (position) => { insets[field(position)] = 0 },\n\n focus: (id) => {\n zCounter += 1\n zOrders[id] = zCounter\n topId.value = id\n },\n\n /**\n * Put a widget in the buckets its placement occupies, removing it from the others.\n *\n * Bucket membership depends on the *whole* placement, not just the anchor — a full-width\n * strip belongs to both buckets of its edge — so this runs whenever either changes.\n */\n dock: (id, anchor, stretch) => {\n const buckets = bucketsFor(anchor, stretch)\n for (const a of ANCHORS) {\n const without = stacks[a].filter(x => x !== id)\n const next = buckets.includes(a) ? [...without, id] : without\n // Replacing an identical list churns every consumer of `stacks`, so bail when\n // nothing actually moved.\n const same = next.length === stacks[a].length && next.every((x, i) => x === stacks[a][i])\n if (!same) stacks[a] = next\n }\n },\n\n undock: (id) => {\n for (const a of ANCHORS) {\n if (stacks[a].includes(id)) stacks[a] = stacks[a].filter(x => x !== id)\n }\n },\n\n reportSize: (id, size) => { if (dockedSizes[id] !== size) dockedSizes[id] = size },\n\n openManaged: (id, widget) => {\n managed.set(id, { ...widget, component: markRaw(widget.component) })\n managedVersion.value++\n },\n closeManaged: (id) => { if (managed.delete(id)) managedVersion.value++ },\n closeAllManaged: () => {\n if (managed.size === 0) return\n managed.clear()\n managedVersion.value++\n },\n managedIds: () => {\n void managedVersion.value // tracked, so a computed sees opens and closes\n return Array.from(managed.keys())\n },\n }\n}\n\nexport type { PanelFloatPlacement }\n","import { computed, inject } from 'vue'\nimport type { ComputedRef } from 'vue'\nimport { PANEL_OVERLAY_KEY } from '../core/overlayState'\nimport type { ManagedWidget, PanelOverlayStore } from '../core/overlayState'\n\n/**\n * The overlay a widget or toolbar is inside.\n *\n * @throws if used outside a `<VddPanelOverlay>`.\n * @internal — applications use {@link useFloatingWidgets}.\n */\nexport function usePanelOverlay(): PanelOverlayStore {\n const store = inject(PANEL_OVERLAY_KEY, null)\n if (!store) {\n throw new Error(\n '[vue-dockable-desktop] This component must be used inside a <VddPanelOverlay>. ' +\n 'Wrap your panel content in one to enable toolbars and floating widgets.',\n )\n }\n return store\n}\n\n/** Same, but `null` outside an overlay — for components that work either way. */\nexport function usePanelOverlayOptional(): PanelOverlayStore | null {\n return inject(PANEL_OVERLAY_KEY, null)\n}\n\n/**\n * Open and close floating widgets by id, without declaring each one in the template.\n *\n * For widgets whose existence is data — one per selected feature, one per running job — where\n * writing a `<VddFloatingWidget>` per case is not possible.\n *\n * ```ts\n * const widgets = useFloatingWidgets()\n * widgets.open('feature-42', {\n * title: 'Feature 42', component: FeatureInfo, props: { id: 42 }, anchor: 'top-right',\n * })\n * ```\n *\n * A widget placed in the template with `<VddFloatingWidget>` is the simpler option and\n * behaves identically otherwise.\n */\nexport function useFloatingWidgets(): {\n /** Ids of the currently open managed widgets, in the order they were opened. */\n openIds: ComputedRef<string[]>\n open: (id: string, widget: ManagedWidget) => void\n close: (id: string) => void\n closeAll: () => void\n isOpen: (id: string) => boolean\n} {\n const store = usePanelOverlay()\n return {\n openIds: computed(() => store.managedIds()),\n open: store.openManaged,\n close: store.closeManaged,\n closeAll: store.closeAllManaged,\n isOpen: (id) => {\n void store.managedVersion.value\n return store.managed.has(id)\n },\n }\n}\n","<script setup lang=\"ts\">\n/**\n * A floating widget inside a panel: docked to a corner, dragged free of it, dropped back onto\n * one — and optionally spanning the panel on either axis instead of carrying a size.\n *\n * rdd's `PanelFloatingWindow` needed **nine** refs mirroring state purely to avoid stale\n * closures in its pointer handlers, plus a `key` remount trick and a controlled/uncontrolled\n * branch for `stretch`. None of that is here: a Vue ref read inside a handler is already the\n * current value, and `v-model:placement` makes controlled and uncontrolled the same code path\n * (docs/decisions/0005-vmodel.md).\n *\n * Anchor and stretch are **one** model, because one gesture can change both — releasing a\n * stretched axis re-pins the anchor — and reporting them separately would surface a state\n * that is never valid.\n */\nimport { computed, onBeforeUnmount, ref, useTemplateRef, watch } from 'vue'\nimport type { Component } from 'vue'\nimport { useWorkspace } from '../composables/useWorkspace'\nimport { usePanelOverlayOptional } from '../composables/usePanelOverlay'\nimport { computeResizedRect, startPointerDrag } from '../core/dragResize'\nimport type { ResizeDir } from '../core/dragResize'\nimport {\n DOCK_INSET, DRAG_THRESHOLD, MIN_H, MIN_W, SNAP_IN, SNAP_OUT,\n anchorAfterRelease, dockedBand, flipZoneHorizontal, handleDirs, hoveredZone, stackOffset,\n} from '../core/panelOverlay'\nimport { addAxis, bucketsFor, releaseAxis, stretchesBlock, stretchesInline } from '../core/stretch'\nimport type { PanelFloatPlacement, Stretch } from '../core/stretch'\nimport type { FloatAnchor, Label } from '../types'\n\nconst props = withDefaults(defineProps<{\n /** Unique within this panel's overlay. Drives z-order and stack membership. */\n widgetId: string\n /** Header text: plain, or a localisable descriptor resolved on every render. */\n title: Label\n icon?: Component\n /** Width in pixels. Ignored while the inline axis is stretched, and returned to on release. */\n width?: number\n /** Height in pixels. Ignored while the block axis is stretched, and returned to on release. */\n height?: number\n /** `false` disables resize-to-stretch snapping, for content that needs a bounded size. @default true */\n stretchable?: boolean\n}>(), { width: 320, height: 240, stretchable: true })\n\n/** Whether the widget is mounted. `v-model:open` replaces rdd's `usePanelFloatingWindow()`. */\nconst open = defineModel<boolean>('open', { default: true })\n\n/**\n * Where the widget sits: its corner, and which axes span the panel.\n *\n * Bind it and the caller owns placement — which is also the only way to persist it, since the\n * library serialises nothing about inner widgets. Leave it off and the widget keeps its own.\n */\nconst placement = defineModel<PanelFloatPlacement>('placement', {\n default: () => ({ anchor: 'top-right' as FloatAnchor, stretch: null }),\n})\n\nconst ws = useWorkspace()\nconst store = usePanelOverlayOptional()\nconst el = useTemplateRef<HTMLDivElement>('el')\n\nconst isRtl = computed(() => ws.state.isRtl)\nconst anchor = computed(() => placement.value.anchor)\nconst stretch = computed(() => placement.value.stretch)\n\n/** `docked` tracks a corner; `free` positions from an explicit box. */\nconst mode = ref<'docked' | 'free'>('docked')\nconst freePos = ref<{ x: number; y: number } | null>(null)\n\n/**\n * The widget's own size.\n *\n * Left untouched while an axis is stretched — the render simply stops reading it, exactly as a\n * maximised workspace window keeps its rect. Releasing the axis therefore restores the\n * previous size with no snapshot and no bookkeeping.\n */\nconst size = ref({ w: props.width, h: props.height })\n\n/** Which axes would snap to stretched if the drag ended now. Drives the visual cue. */\nconst snapArmed = ref({ inline: false, block: false })\n\n/** The single write path for placement, so a listener never sees a half-applied transition. */\nfunction applyPlacement(nextAnchor: FloatAnchor, nextStretch: Stretch | null): void {\n placement.value = { anchor: nextAnchor, stretch: nextStretch }\n}\n\n// ── stack membership ───────────────────────────────────────────────────────\n/**\n * Depends on the whole placement, not only the anchor — a full-width strip belongs to both\n * buckets of its edge — so this re-runs whenever either changes, not just on mount.\n *\n * `watch` with explicit sources rather than `watchEffect`: registering also *reads* the\n * stacks (to skip a no-op write), so an effect that tracked its own body would depend on the\n * very state it writes and re-trigger itself forever. Naming the sources is the fix, and it\n * also says exactly what a re-registration is supposed to follow.\n */\nwatch(\n [() => open.value, mode, anchor, stretch],\n ([isOpen, currentMode]) => {\n if (!store || !isOpen) return\n if (currentMode !== 'docked') { store.undock(props.widgetId); return }\n store.dock(props.widgetId, anchor.value, stretch.value)\n },\n { immediate: true },\n)\n\nwatch(() => size.value.h, (h) => store?.reportSize(props.widgetId, h), { immediate: true })\nonBeforeUnmount(() => store?.undock(props.widgetId))\n\n// A block-stretched widget spans the very axis stacking uses to separate siblings, so it\n// cannot stack — it overlaps them and z-order decides. Worth saying once.\nlet warnedBlockStretch = false\n// Also an explicit watch, for the same reason: it reads the stacks, which the effect above\n// writes, so as a `watchEffect` the two would chase each other.\nwatch([mode, anchor, stretch], () => {\n if (process.env.NODE_ENV === 'production' || warnedBlockStretch) return\n if (!store || mode.value !== 'docked' || !stretchesBlock(stretch.value)) return\n const half = anchor.value.endsWith('-right') ? 'right' : 'left'\n const neighbours = ([`top-${half}`, `bottom-${half}`] as FloatAnchor[])\n .flatMap(bucket => store.stacks[bucket] ?? [])\n .filter(other => other !== props.widgetId)\n if (neighbours.length === 0) return\n warnedBlockStretch = true\n console.warn(\n `[vue-dockable-desktop] <VddFloatingWidget> \"${props.widgetId}\" stretches the block axis ` +\n `(\"${stretch.value}\") while ${neighbours.length} other widget(s) are anchored to the same ` +\n `side (${neighbours.join(', ')}). A block-stretched widget spans the axis stacking uses to ` +\n `separate siblings, so it cannot stack and will overlap them — z-order decides which is on ` +\n `top. Give it a fixed height, or move the others to the opposite side.`,\n )\n})\n\n// ── geometry ───────────────────────────────────────────────────────────────\nconst zOrder = computed(() => store?.zOrders[props.widgetId] ?? 101)\nconst isActive = computed(() => !store || store.topId.value === props.widgetId)\nconst band = () => dockedBand(store?.insets ?? { top: 0, bottom: 0, inlineStart: 0, inlineEnd: 0 }, isRtl.value)\n\nfunction containerBounds(): { cw: number; ch: number } {\n const parent = el.value?.offsetParent as HTMLElement | null\n return { cw: parent?.clientWidth ?? 9999, ch: parent?.clientHeight ?? 9999 }\n}\n\nconst stack = computed(() => {\n if (!store || mode.value !== 'docked') return { offset: 0, registered: true }\n return stackOffset(\n props.widgetId,\n bucketsFor(anchor.value, stretch.value),\n store.stacks,\n store.dockedSizes,\n props.height,\n )\n})\n\n/**\n * Inline axis: one inset plus an explicit width, or **both** insets and no width at all.\n * Setting both ends is the whole stretch mechanism — CSS then keeps the widget spanning the\n * panel for free, with no observer and no JavaScript.\n */\nconst widgetStyle = computed<Record<string, string | number | undefined>>(() => {\n if (mode.value !== 'docked' || !store) {\n return {\n left: `${freePos.value?.x ?? 0}px`,\n top: `${freePos.value?.y ?? 0}px`,\n width: `${size.value.w}px`,\n height: `${size.value.h}px`,\n zIndex: zOrder.value,\n }\n }\n\n const b = band()\n const style: Record<string, string | number | undefined> = {\n zIndex: zOrder.value,\n // Hidden until its first stack registration lands, so it never paints at the wrong offset.\n opacity: stack.value.registered ? undefined : 0,\n pointerEvents: stack.value.registered ? undefined : 'none',\n }\n\n if (stretchesInline(stretch.value)) {\n style.insetInlineStart = `${store.insets.inlineStart + DOCK_INSET}px`\n style.insetInlineEnd = `${store.insets.inlineEnd + DOCK_INSET}px`\n } else {\n style[anchor.value.endsWith('-right') ? 'insetInlineEnd' : 'insetInlineStart'] = `${DOCK_INSET}px`\n style.width = `${size.value.w}px`\n }\n\n // The block insets carry no gutter, matching how a docked widget sits flush against a\n // top or bottom toolbar rather than inset from it.\n if (stretchesBlock(stretch.value)) {\n style.top = `${b.top}px`\n style.bottom = `${b.bottom}px`\n } else if (anchor.value.startsWith('top-')) {\n style.top = `${b.top + stack.value.offset}px`\n style.height = `${size.value.h}px`\n } else {\n style.bottom = `${b.bottom + stack.value.offset}px`\n style.height = `${size.value.h}px`\n }\n\n return style\n})\n\nconst dirs = computed(() => handleDirs(mode.value, anchor.value, stretch.value, isRtl.value))\n\n// ── header drag ────────────────────────────────────────────────────────────\nlet drag: { mouseX: number; mouseY: number; posX: number; posY: number; moved: boolean } | null = null\n\nfunction onHeaderDown(event: PointerEvent): void {\n if (event.button !== 0) return\n event.preventDefault()\n\n let startX: number\n let startY: number\n if (mode.value === 'docked') {\n // Snapshot the rendered position so that *if* this becomes a real drag, switching to free\n // positioning causes no visual jump. Undocking is deferred to the threshold below: doing\n // it here meant a plain click on the header silently tore the widget off its anchor — it\n // looked unchanged, but its stacked siblings reflowed to close the gap and it stopped\n // tracking the corner on every later panel resize.\n const node = el.value\n const container = store?.container.value\n if (node && container) {\n const r = node.getBoundingClientRect()\n const c = container.getBoundingClientRect()\n startX = r.left - c.left\n startY = r.top - c.top\n } else {\n startX = DOCK_INSET\n startY = store?.insets.top ?? 0\n }\n } else {\n startX = freePos.value?.x ?? 0\n startY = freePos.value?.y ?? 0\n }\n\n drag = { mouseX: event.clientX, mouseY: event.clientY, posX: startX, posY: startY, moved: false }\n el.value?.setPointerCapture(event.pointerId)\n}\n\nfunction onPointerMove(event: PointerEvent): void {\n if (!drag) return\n if (!drag.moved) {\n if (Math.abs(event.clientX - drag.mouseX) + Math.abs(event.clientY - drag.mouseY) < DRAG_THRESHOLD) return\n drag.moved = true\n if (mode.value === 'docked') {\n // A stretched axis carries no size, so free mode — which positions from an explicit box\n // — would snap back to whatever the size was before stretching. Materialise what is\n // actually on screen, then clear stretch: free and spanning are mutually exclusive.\n if (stretch.value) {\n const r = el.value?.getBoundingClientRect()\n if (r) size.value = { w: Math.round(r.width), h: Math.round(r.height) }\n applyPlacement(anchor.value, null)\n }\n store?.undock(props.widgetId)\n mode.value = 'free'\n }\n document.body.classList.add('vdd-dragging-active')\n if (store) store.draggingId.value = props.widgetId\n }\n\n const { cw, ch } = containerBounds()\n freePos.value = {\n x: Math.max(0, Math.min(drag.posX + event.clientX - drag.mouseX, cw - size.value.w)),\n y: Math.max(0, Math.min(drag.posY + event.clientY - drag.mouseY, ch - size.value.h)),\n }\n\n const container = store?.container.value\n if (container && store) {\n const raw = hoveredZone(container.getBoundingClientRect(), event.clientX, event.clientY)\n store.hovered.value = raw && isRtl.value ? flipZoneHorizontal(raw) : raw\n }\n}\n\nfunction onPointerUp(): void {\n if (drag?.moved && store) {\n const zone = store.hovered.value\n if (zone) {\n mode.value = 'docked'\n freePos.value = null\n applyPlacement(zone, stretch.value)\n }\n store.hovered.value = null\n store.draggingId.value = null\n }\n document.body.classList.remove('vdd-dragging-active')\n drag = null\n}\n\nfunction onPointerCancel(): void {\n if (drag?.moved && store) {\n store.hovered.value = null\n store.draggingId.value = null\n }\n document.body.classList.remove('vdd-dragging-active')\n drag = null\n}\n\n// ── resize ─────────────────────────────────────────────────────────────────\nfunction onResizeDown(dir: ResizeDir, event: PointerEvent): void {\n if (event.button !== 0) return\n event.preventDefault()\n event.stopPropagation()\n\n let startX = 0\n let startY = 0\n if (mode.value === 'free') {\n startX = freePos.value?.x ?? 0\n startY = freePos.value?.y ?? 0\n } else {\n const node = el.value\n const parent = node?.offsetParent as HTMLElement | null\n if (node && parent) {\n const r = node.getBoundingClientRect()\n const p = parent.getBoundingClientRect()\n startX = r.left - p.left\n startY = r.top - p.top\n }\n }\n\n // A stretched axis's stored size is stale by design, so the drag has to start from the\n // *measured* extent or the widget jumps on the first move.\n const measured = el.value?.getBoundingClientRect()\n const start = {\n x: startX,\n y: startY,\n w: stretchesInline(stretch.value) && measured ? measured.width : size.value.w,\n h: stretchesBlock(stretch.value) && measured ? measured.height : size.value.h,\n }\n\n const dragsInline = dir.includes('e') || dir.includes('w')\n const dragsBlock = dir.includes('n') || dir.includes('s')\n let released = false\n let armed = { inline: false, block: false }\n\n /**\n * Dragging an end of a stretched axis releases it: the edge under the pointer becomes the\n * moving one and the opposite end becomes the new pin, so it reads exactly like an ordinary\n * resize. Once per drag — `released` guards repeat moves before the next render.\n */\n function releaseIfNeeded(): void {\n if (released || mode.value !== 'docked') return\n const current = stretch.value\n const axes = {\n inline: dragsInline && stretchesInline(current),\n block: dragsBlock && stretchesBlock(current),\n }\n if (!axes.inline && !axes.block) return\n released = true\n\n let next = current\n if (axes.inline) next = releaseAxis(next, 'inline')\n if (axes.block) next = releaseAxis(next, 'block')\n applyPlacement(anchorAfterRelease(anchor.value, dir, axes, isRtl.value), next)\n }\n\n startPointerDrag({\n element: event.currentTarget as HTMLElement,\n pointerId: event.pointerId,\n startClientX: event.clientX,\n startClientY: event.clientY,\n captureStart: () => start,\n activeClasses: [{ el: document.body, classes: ['vdd-resizing-active'] }],\n onMove: (dx, dy, from) => {\n // Re-measured every move: the container can change size mid-drag.\n const { cw, ch } = containerBounds()\n // Docked widgets stop at the toolbar band. Free ones are unconstrained beyond the\n // container itself, since free means free.\n const b = mode.value === 'docked' ? band() : { left: 0, right: 0, top: 0, bottom: 0 }\n const rect = computeResizedRect(dir, dx, dy, from, {\n minW: MIN_W, minH: MIN_H,\n maxW: (cw - b.right) - from.x, maxH: (ch - b.bottom) - from.y,\n minX: b.left, minY: b.top,\n })\n releaseIfNeeded()\n\n // ── resize-to-stretch snapping ──\n // The clamps above already stop growth exactly where a stretched axis would sit, so an\n // armed drag is visually at its target already; the cue is an outline, not a ghost.\n if (mode.value === 'docked' && props.stretchable) {\n const fullInline = cw - b.left - b.right\n const fullBlock = ch - b.top - b.bottom - stack.value.offset\n const current = stretch.value\n const next = { ...armed }\n if (dragsInline && !stretchesInline(current)) {\n if (rect.w >= fullInline - SNAP_IN) next.inline = true\n else if (armed.inline && rect.w < fullInline - SNAP_OUT) next.inline = false\n }\n if (dragsBlock && !stretchesBlock(current)) {\n if (rect.h >= fullBlock - SNAP_IN) next.block = true\n else if (armed.block && rect.h < fullBlock - SNAP_OUT) next.block = false\n }\n if (next.inline !== armed.inline || next.block !== armed.block) {\n armed = next\n snapArmed.value = next\n }\n }\n\n // Only write an axis that carries a size. A still-stretched axis keeps its stored\n // value, so releasing it later restores the size it had before stretching.\n const effective = released\n ? releaseAxis(releaseAxis(stretch.value, dragsInline ? 'inline' : 'block'), dragsBlock ? 'block' : 'inline')\n : stretch.value\n size.value = {\n w: stretchesInline(effective) ? size.value.w : rect.w,\n h: stretchesBlock(effective) ? size.value.h : rect.h,\n }\n if (mode.value === 'free') freePos.value = { x: rect.x, y: rect.y }\n },\n onEnd: (from) => {\n if (!armed.inline && !armed.block) {\n if (snapArmed.value.inline || snapArmed.value.block) snapArmed.value = { inline: false, block: false }\n return\n }\n // Restore the size the axis had *before* this drag: while an axis is stretched its\n // stored size is what releasing it returns to, so it should be the size the user last\n // chose deliberately — not the full-bleed value the drag happened to pass through.\n size.value = {\n w: armed.inline ? from.w : size.value.w,\n h: armed.block ? from.h : size.value.h,\n }\n let next = stretch.value\n if (armed.inline) next = addAxis(next, 'inline')\n if (armed.block) next = addAxis(next, 'block')\n applyPlacement(anchor.value, next)\n snapArmed.value = { inline: false, block: false }\n },\n })\n}\n</script>\n\n<template>\n <div\n v-if=\"open\"\n ref=\"el\"\n class=\"vdd-panel-float\"\n :class=\"{\n 'vdd-panel-float--active': isActive,\n 'vdd-panel-float--snapping': snapArmed.inline || snapArmed.block,\n }\"\n :style=\"widgetStyle\"\n :dir=\"ws.state.dir\"\n :data-vdd-widget=\"widgetId\"\n @pointerdown=\"store?.focus(widgetId)\"\n @pointermove=\"onPointerMove\"\n @pointerup=\"onPointerUp\"\n @pointercancel=\"onPointerCancel\"\n >\n <div class=\"vdd-panel-float__header\" data-vdd-widget-header @pointerdown=\"onHeaderDown\">\n <span v-if=\"icon\" class=\"vdd-panel-float__icon\"><component :is=\"icon\" /></span>\n <span class=\"vdd-panel-float__title\">{{ ws.format(title) }}</span>\n <button\n type=\"button\"\n class=\"vdd-panel-float__close\"\n :title=\"ws.format(ws.messages.close)\"\n :aria-label=\"ws.format(ws.messages.close)\"\n data-vdd-widget-close\n @click=\"open = false\"\n @pointerdown.stop\n >\n <svg width=\"8\" height=\"8\" viewBox=\"0 0 10 10\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\">\n <line x1=\"1\" y1=\"1\" x2=\"9\" y2=\"9\" />\n <line x1=\"9\" y1=\"1\" x2=\"1\" y2=\"9\" />\n </svg>\n </button>\n </div>\n\n <div class=\"vdd-panel-float__body\"><slot /></div>\n\n <div\n v-for=\"dir in dirs\"\n :key=\"dir\"\n class=\"vdd-resize-handle\"\n :class=\"`vdd-resize-${dir}`\"\n :data-vdd-widget-resize=\"dir\"\n @pointerdown=\"onResizeDown(dir, $event)\"\n />\n </div>\n</template>\n","<script setup lang=\"ts\">\n/**\n * A floating widget inside a panel: docked to a corner, dragged free of it, dropped back onto\n * one — and optionally spanning the panel on either axis instead of carrying a size.\n *\n * rdd's `PanelFloatingWindow` needed **nine** refs mirroring state purely to avoid stale\n * closures in its pointer handlers, plus a `key` remount trick and a controlled/uncontrolled\n * branch for `stretch`. None of that is here: a Vue ref read inside a handler is already the\n * current value, and `v-model:placement` makes controlled and uncontrolled the same code path\n * (docs/decisions/0005-vmodel.md).\n *\n * Anchor and stretch are **one** model, because one gesture can change both — releasing a\n * stretched axis re-pins the anchor — and reporting them separately would surface a state\n * that is never valid.\n */\nimport { computed, onBeforeUnmount, ref, useTemplateRef, watch } from 'vue'\nimport type { Component } from 'vue'\nimport { useWorkspace } from '../composables/useWorkspace'\nimport { usePanelOverlayOptional } from '../composables/usePanelOverlay'\nimport { computeResizedRect, startPointerDrag } from '../core/dragResize'\nimport type { ResizeDir } from '../core/dragResize'\nimport {\n DOCK_INSET, DRAG_THRESHOLD, MIN_H, MIN_W, SNAP_IN, SNAP_OUT,\n anchorAfterRelease, dockedBand, flipZoneHorizontal, handleDirs, hoveredZone, stackOffset,\n} from '../core/panelOverlay'\nimport { addAxis, bucketsFor, releaseAxis, stretchesBlock, stretchesInline } from '../core/stretch'\nimport type { PanelFloatPlacement, Stretch } from '../core/stretch'\nimport type { FloatAnchor, Label } from '../types'\n\nconst props = withDefaults(defineProps<{\n /** Unique within this panel's overlay. Drives z-order and stack membership. */\n widgetId: string\n /** Header text: plain, or a localisable descriptor resolved on every render. */\n title: Label\n icon?: Component\n /** Width in pixels. Ignored while the inline axis is stretched, and returned to on release. */\n width?: number\n /** Height in pixels. Ignored while the block axis is stretched, and returned to on release. */\n height?: number\n /** `false` disables resize-to-stretch snapping, for content that needs a bounded size. @default true */\n stretchable?: boolean\n}>(), { width: 320, height: 240, stretchable: true })\n\n/** Whether the widget is mounted. `v-model:open` replaces rdd's `usePanelFloatingWindow()`. */\nconst open = defineModel<boolean>('open', { default: true })\n\n/**\n * Where the widget sits: its corner, and which axes span the panel.\n *\n * Bind it and the caller owns placement — which is also the only way to persist it, since the\n * library serialises nothing about inner widgets. Leave it off and the widget keeps its own.\n */\nconst placement = defineModel<PanelFloatPlacement>('placement', {\n default: () => ({ anchor: 'top-right' as FloatAnchor, stretch: null }),\n})\n\nconst ws = useWorkspace()\nconst store = usePanelOverlayOptional()\nconst el = useTemplateRef<HTMLDivElement>('el')\n\nconst isRtl = computed(() => ws.state.isRtl)\nconst anchor = computed(() => placement.value.anchor)\nconst stretch = computed(() => placement.value.stretch)\n\n/** `docked` tracks a corner; `free` positions from an explicit box. */\nconst mode = ref<'docked' | 'free'>('docked')\nconst freePos = ref<{ x: number; y: number } | null>(null)\n\n/**\n * The widget's own size.\n *\n * Left untouched while an axis is stretched — the render simply stops reading it, exactly as a\n * maximised workspace window keeps its rect. Releasing the axis therefore restores the\n * previous size with no snapshot and no bookkeeping.\n */\nconst size = ref({ w: props.width, h: props.height })\n\n/** Which axes would snap to stretched if the drag ended now. Drives the visual cue. */\nconst snapArmed = ref({ inline: false, block: false })\n\n/** The single write path for placement, so a listener never sees a half-applied transition. */\nfunction applyPlacement(nextAnchor: FloatAnchor, nextStretch: Stretch | null): void {\n placement.value = { anchor: nextAnchor, stretch: nextStretch }\n}\n\n// ── stack membership ───────────────────────────────────────────────────────\n/**\n * Depends on the whole placement, not only the anchor — a full-width strip belongs to both\n * buckets of its edge — so this re-runs whenever either changes, not just on mount.\n *\n * `watch` with explicit sources rather than `watchEffect`: registering also *reads* the\n * stacks (to skip a no-op write), so an effect that tracked its own body would depend on the\n * very state it writes and re-trigger itself forever. Naming the sources is the fix, and it\n * also says exactly what a re-registration is supposed to follow.\n */\nwatch(\n [() => open.value, mode, anchor, stretch],\n ([isOpen, currentMode]) => {\n if (!store || !isOpen) return\n if (currentMode !== 'docked') { store.undock(props.widgetId); return }\n store.dock(props.widgetId, anchor.value, stretch.value)\n },\n { immediate: true },\n)\n\nwatch(() => size.value.h, (h) => store?.reportSize(props.widgetId, h), { immediate: true })\nonBeforeUnmount(() => store?.undock(props.widgetId))\n\n// A block-stretched widget spans the very axis stacking uses to separate siblings, so it\n// cannot stack — it overlaps them and z-order decides. Worth saying once.\nlet warnedBlockStretch = false\n// Also an explicit watch, for the same reason: it reads the stacks, which the effect above\n// writes, so as a `watchEffect` the two would chase each other.\nwatch([mode, anchor, stretch], () => {\n if (process.env.NODE_ENV === 'production' || warnedBlockStretch) return\n if (!store || mode.value !== 'docked' || !stretchesBlock(stretch.value)) return\n const half = anchor.value.endsWith('-right') ? 'right' : 'left'\n const neighbours = ([`top-${half}`, `bottom-${half}`] as FloatAnchor[])\n .flatMap(bucket => store.stacks[bucket] ?? [])\n .filter(other => other !== props.widgetId)\n if (neighbours.length === 0) return\n warnedBlockStretch = true\n console.warn(\n `[vue-dockable-desktop] <VddFloatingWidget> \"${props.widgetId}\" stretches the block axis ` +\n `(\"${stretch.value}\") while ${neighbours.length} other widget(s) are anchored to the same ` +\n `side (${neighbours.join(', ')}). A block-stretched widget spans the axis stacking uses to ` +\n `separate siblings, so it cannot stack and will overlap them — z-order decides which is on ` +\n `top. Give it a fixed height, or move the others to the opposite side.`,\n )\n})\n\n// ── geometry ───────────────────────────────────────────────────────────────\nconst zOrder = computed(() => store?.zOrders[props.widgetId] ?? 101)\nconst isActive = computed(() => !store || store.topId.value === props.widgetId)\nconst band = () => dockedBand(store?.insets ?? { top: 0, bottom: 0, inlineStart: 0, inlineEnd: 0 }, isRtl.value)\n\nfunction containerBounds(): { cw: number; ch: number } {\n const parent = el.value?.offsetParent as HTMLElement | null\n return { cw: parent?.clientWidth ?? 9999, ch: parent?.clientHeight ?? 9999 }\n}\n\nconst stack = computed(() => {\n if (!store || mode.value !== 'docked') return { offset: 0, registered: true }\n return stackOffset(\n props.widgetId,\n bucketsFor(anchor.value, stretch.value),\n store.stacks,\n store.dockedSizes,\n props.height,\n )\n})\n\n/**\n * Inline axis: one inset plus an explicit width, or **both** insets and no width at all.\n * Setting both ends is the whole stretch mechanism — CSS then keeps the widget spanning the\n * panel for free, with no observer and no JavaScript.\n */\nconst widgetStyle = computed<Record<string, string | number | undefined>>(() => {\n if (mode.value !== 'docked' || !store) {\n return {\n left: `${freePos.value?.x ?? 0}px`,\n top: `${freePos.value?.y ?? 0}px`,\n width: `${size.value.w}px`,\n height: `${size.value.h}px`,\n zIndex: zOrder.value,\n }\n }\n\n const b = band()\n const style: Record<string, string | number | undefined> = {\n zIndex: zOrder.value,\n // Hidden until its first stack registration lands, so it never paints at the wrong offset.\n opacity: stack.value.registered ? undefined : 0,\n pointerEvents: stack.value.registered ? undefined : 'none',\n }\n\n if (stretchesInline(stretch.value)) {\n style.insetInlineStart = `${store.insets.inlineStart + DOCK_INSET}px`\n style.insetInlineEnd = `${store.insets.inlineEnd + DOCK_INSET}px`\n } else {\n style[anchor.value.endsWith('-right') ? 'insetInlineEnd' : 'insetInlineStart'] = `${DOCK_INSET}px`\n style.width = `${size.value.w}px`\n }\n\n // The block insets carry no gutter, matching how a docked widget sits flush against a\n // top or bottom toolbar rather than inset from it.\n if (stretchesBlock(stretch.value)) {\n style.top = `${b.top}px`\n style.bottom = `${b.bottom}px`\n } else if (anchor.value.startsWith('top-')) {\n style.top = `${b.top + stack.value.offset}px`\n style.height = `${size.value.h}px`\n } else {\n style.bottom = `${b.bottom + stack.value.offset}px`\n style.height = `${size.value.h}px`\n }\n\n return style\n})\n\nconst dirs = computed(() => handleDirs(mode.value, anchor.value, stretch.value, isRtl.value))\n\n// ── header drag ────────────────────────────────────────────────────────────\nlet drag: { mouseX: number; mouseY: number; posX: number; posY: number; moved: boolean } | null = null\n\nfunction onHeaderDown(event: PointerEvent): void {\n if (event.button !== 0) return\n event.preventDefault()\n\n let startX: number\n let startY: number\n if (mode.value === 'docked') {\n // Snapshot the rendered position so that *if* this becomes a real drag, switching to free\n // positioning causes no visual jump. Undocking is deferred to the threshold below: doing\n // it here meant a plain click on the header silently tore the widget off its anchor — it\n // looked unchanged, but its stacked siblings reflowed to close the gap and it stopped\n // tracking the corner on every later panel resize.\n const node = el.value\n const container = store?.container.value\n if (node && container) {\n const r = node.getBoundingClientRect()\n const c = container.getBoundingClientRect()\n startX = r.left - c.left\n startY = r.top - c.top\n } else {\n startX = DOCK_INSET\n startY = store?.insets.top ?? 0\n }\n } else {\n startX = freePos.value?.x ?? 0\n startY = freePos.value?.y ?? 0\n }\n\n drag = { mouseX: event.clientX, mouseY: event.clientY, posX: startX, posY: startY, moved: false }\n el.value?.setPointerCapture(event.pointerId)\n}\n\nfunction onPointerMove(event: PointerEvent): void {\n if (!drag) return\n if (!drag.moved) {\n if (Math.abs(event.clientX - drag.mouseX) + Math.abs(event.clientY - drag.mouseY) < DRAG_THRESHOLD) return\n drag.moved = true\n if (mode.value === 'docked') {\n // A stretched axis carries no size, so free mode — which positions from an explicit box\n // — would snap back to whatever the size was before stretching. Materialise what is\n // actually on screen, then clear stretch: free and spanning are mutually exclusive.\n if (stretch.value) {\n const r = el.value?.getBoundingClientRect()\n if (r) size.value = { w: Math.round(r.width), h: Math.round(r.height) }\n applyPlacement(anchor.value, null)\n }\n store?.undock(props.widgetId)\n mode.value = 'free'\n }\n document.body.classList.add('vdd-dragging-active')\n if (store) store.draggingId.value = props.widgetId\n }\n\n const { cw, ch } = containerBounds()\n freePos.value = {\n x: Math.max(0, Math.min(drag.posX + event.clientX - drag.mouseX, cw - size.value.w)),\n y: Math.max(0, Math.min(drag.posY + event.clientY - drag.mouseY, ch - size.value.h)),\n }\n\n const container = store?.container.value\n if (container && store) {\n const raw = hoveredZone(container.getBoundingClientRect(), event.clientX, event.clientY)\n store.hovered.value = raw && isRtl.value ? flipZoneHorizontal(raw) : raw\n }\n}\n\nfunction onPointerUp(): void {\n if (drag?.moved && store) {\n const zone = store.hovered.value\n if (zone) {\n mode.value = 'docked'\n freePos.value = null\n applyPlacement(zone, stretch.value)\n }\n store.hovered.value = null\n store.draggingId.value = null\n }\n document.body.classList.remove('vdd-dragging-active')\n drag = null\n}\n\nfunction onPointerCancel(): void {\n if (drag?.moved && store) {\n store.hovered.value = null\n store.draggingId.value = null\n }\n document.body.classList.remove('vdd-dragging-active')\n drag = null\n}\n\n// ── resize ─────────────────────────────────────────────────────────────────\nfunction onResizeDown(dir: ResizeDir, event: PointerEvent): void {\n if (event.button !== 0) return\n event.preventDefault()\n event.stopPropagation()\n\n let startX = 0\n let startY = 0\n if (mode.value === 'free') {\n startX = freePos.value?.x ?? 0\n startY = freePos.value?.y ?? 0\n } else {\n const node = el.value\n const parent = node?.offsetParent as HTMLElement | null\n if (node && parent) {\n const r = node.getBoundingClientRect()\n const p = parent.getBoundingClientRect()\n startX = r.left - p.left\n startY = r.top - p.top\n }\n }\n\n // A stretched axis's stored size is stale by design, so the drag has to start from the\n // *measured* extent or the widget jumps on the first move.\n const measured = el.value?.getBoundingClientRect()\n const start = {\n x: startX,\n y: startY,\n w: stretchesInline(stretch.value) && measured ? measured.width : size.value.w,\n h: stretchesBlock(stretch.value) && measured ? measured.height : size.value.h,\n }\n\n const dragsInline = dir.includes('e') || dir.includes('w')\n const dragsBlock = dir.includes('n') || dir.includes('s')\n let released = false\n let armed = { inline: false, block: false }\n\n /**\n * Dragging an end of a stretched axis releases it: the edge under the pointer becomes the\n * moving one and the opposite end becomes the new pin, so it reads exactly like an ordinary\n * resize. Once per drag — `released` guards repeat moves before the next render.\n */\n function releaseIfNeeded(): void {\n if (released || mode.value !== 'docked') return\n const current = stretch.value\n const axes = {\n inline: dragsInline && stretchesInline(current),\n block: dragsBlock && stretchesBlock(current),\n }\n if (!axes.inline && !axes.block) return\n released = true\n\n let next = current\n if (axes.inline) next = releaseAxis(next, 'inline')\n if (axes.block) next = releaseAxis(next, 'block')\n applyPlacement(anchorAfterRelease(anchor.value, dir, axes, isRtl.value), next)\n }\n\n startPointerDrag({\n element: event.currentTarget as HTMLElement,\n pointerId: event.pointerId,\n startClientX: event.clientX,\n startClientY: event.clientY,\n captureStart: () => start,\n activeClasses: [{ el: document.body, classes: ['vdd-resizing-active'] }],\n onMove: (dx, dy, from) => {\n // Re-measured every move: the container can change size mid-drag.\n const { cw, ch } = containerBounds()\n // Docked widgets stop at the toolbar band. Free ones are unconstrained beyond the\n // container itself, since free means free.\n const b = mode.value === 'docked' ? band() : { left: 0, right: 0, top: 0, bottom: 0 }\n const rect = computeResizedRect(dir, dx, dy, from, {\n minW: MIN_W, minH: MIN_H,\n maxW: (cw - b.right) - from.x, maxH: (ch - b.bottom) - from.y,\n minX: b.left, minY: b.top,\n })\n releaseIfNeeded()\n\n // ── resize-to-stretch snapping ──\n // The clamps above already stop growth exactly where a stretched axis would sit, so an\n // armed drag is visually at its target already; the cue is an outline, not a ghost.\n if (mode.value === 'docked' && props.stretchable) {\n const fullInline = cw - b.left - b.right\n const fullBlock = ch - b.top - b.bottom - stack.value.offset\n const current = stretch.value\n const next = { ...armed }\n if (dragsInline && !stretchesInline(current)) {\n if (rect.w >= fullInline - SNAP_IN) next.inline = true\n else if (armed.inline && rect.w < fullInline - SNAP_OUT) next.inline = false\n }\n if (dragsBlock && !stretchesBlock(current)) {\n if (rect.h >= fullBlock - SNAP_IN) next.block = true\n else if (armed.block && rect.h < fullBlock - SNAP_OUT) next.block = false\n }\n if (next.inline !== armed.inline || next.block !== armed.block) {\n armed = next\n snapArmed.value = next\n }\n }\n\n // Only write an axis that carries a size. A still-stretched axis keeps its stored\n // value, so releasing it later restores the size it had before stretching.\n const effective = released\n ? releaseAxis(releaseAxis(stretch.value, dragsInline ? 'inline' : 'block'), dragsBlock ? 'block' : 'inline')\n : stretch.value\n size.value = {\n w: stretchesInline(effective) ? size.value.w : rect.w,\n h: stretchesBlock(effective) ? size.value.h : rect.h,\n }\n if (mode.value === 'free') freePos.value = { x: rect.x, y: rect.y }\n },\n onEnd: (from) => {\n if (!armed.inline && !armed.block) {\n if (snapArmed.value.inline || snapArmed.value.block) snapArmed.value = { inline: false, block: false }\n return\n }\n // Restore the size the axis had *before* this drag: while an axis is stretched its\n // stored size is what releasing it returns to, so it should be the size the user last\n // chose deliberately — not the full-bleed value the drag happened to pass through.\n size.value = {\n w: armed.inline ? from.w : size.value.w,\n h: armed.block ? from.h : size.value.h,\n }\n let next = stretch.value\n if (armed.inline) next = addAxis(next, 'inline')\n if (armed.block) next = addAxis(next, 'block')\n applyPlacement(anchor.value, next)\n snapArmed.value = { inline: false, block: false }\n },\n })\n}\n</script>\n\n<template>\n <div\n v-if=\"open\"\n ref=\"el\"\n class=\"vdd-panel-float\"\n :class=\"{\n 'vdd-panel-float--active': isActive,\n 'vdd-panel-float--snapping': snapArmed.inline || snapArmed.block,\n }\"\n :style=\"widgetStyle\"\n :dir=\"ws.state.dir\"\n :data-vdd-widget=\"widgetId\"\n @pointerdown=\"store?.focus(widgetId)\"\n @pointermove=\"onPointerMove\"\n @pointerup=\"onPointerUp\"\n @pointercancel=\"onPointerCancel\"\n >\n <div class=\"vdd-panel-float__header\" data-vdd-widget-header @pointerdown=\"onHeaderDown\">\n <span v-if=\"icon\" class=\"vdd-panel-float__icon\"><component :is=\"icon\" /></span>\n <span class=\"vdd-panel-float__title\">{{ ws.format(title) }}</span>\n <button\n type=\"button\"\n class=\"vdd-panel-float__close\"\n :title=\"ws.format(ws.messages.close)\"\n :aria-label=\"ws.format(ws.messages.close)\"\n data-vdd-widget-close\n @click=\"open = false\"\n @pointerdown.stop\n >\n <svg width=\"8\" height=\"8\" viewBox=\"0 0 10 10\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\">\n <line x1=\"1\" y1=\"1\" x2=\"9\" y2=\"9\" />\n <line x1=\"9\" y1=\"1\" x2=\"1\" y2=\"9\" />\n </svg>\n </button>\n </div>\n\n <div class=\"vdd-panel-float__body\"><slot /></div>\n\n <div\n v-for=\"dir in dirs\"\n :key=\"dir\"\n class=\"vdd-resize-handle\"\n :class=\"`vdd-resize-${dir}`\"\n :data-vdd-widget-resize=\"dir\"\n @pointerdown=\"onResizeDown(dir, $event)\"\n />\n </div>\n</template>\n","<script setup lang=\"ts\">\n/**\n * The root of one panel's overlay: toolbars on its edges, floating widgets over its content.\n *\n * Wrap a panel's content in this to enable `<VddPanelToolbar>`, `<VddFloatingWidget>` and\n * `useFloatingWidgets()`. It is scoped to the panel, so each panel has its own toolbars,\n * its own widget stacks and its own z-order — nothing here is global.\n */\nimport { computed, provide, useTemplateRef, watchEffect } from 'vue'\nimport { ANCHORS } from '../core/panelOverlay'\nimport { createPanelOverlayStore, PANEL_OVERLAY_KEY } from '../core/overlayState'\nimport VddFloatingWidget from './VddFloatingWidget.vue'\n\nconst store = createPanelOverlayStore()\nprovide(PANEL_OVERLAY_KEY, store)\n\nconst root = useTemplateRef<HTMLDivElement>('root')\nwatchEffect(() => { store.container.value = root.value })\n\n/** Widgets opened through `useFloatingWidgets()`, rather than placed in a template. */\nconst managed = computed(() =>\n store.managedIds().map(id => ({ id, widget: store.managed.get(id)! })))\n\n/** Drop zones are only shown while something is being dragged. */\nconst dragging = computed(() => store.draggingId.value !== null)\n</script>\n\n<template>\n <div\n ref=\"root\"\n class=\"vdd-panel-overlay-root\"\n :class=\"{ 'vdd-dragging-active': dragging }\"\n data-vdd-panel-overlay\n >\n <slot />\n\n <template v-if=\"dragging\">\n <div\n v-for=\"zone in ANCHORS\"\n :key=\"zone\"\n class=\"vdd-panel-float-dropzone\"\n :class=\"[\n `vdd-panel-float-dropzone--${zone}`,\n { 'vdd-panel-float-dropzone--hovered': store.hovered.value === zone },\n ]\"\n :data-vdd-dropzone=\"zone\"\n aria-hidden=\"true\"\n />\n </template>\n\n <VddFloatingWidget\n v-for=\"{ id, widget } in managed\"\n :key=\"id\"\n :widget-id=\"id\"\n :title=\"widget.title\"\n :icon=\"widget.icon\"\n :open=\"true\"\n :placement=\"{ anchor: widget.anchor ?? 'top-right', stretch: widget.stretch ?? null }\"\n :width=\"widget.width ?? 320\"\n :height=\"widget.height ?? 240\"\n @update:open=\"(value: boolean) => { if (!value) store.closeManaged(id) }\"\n >\n <component :is=\"widget.component\" v-bind=\"widget.props\" />\n </VddFloatingWidget>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/**\n * The root of one panel's overlay: toolbars on its edges, floating widgets over its content.\n *\n * Wrap a panel's content in this to enable `<VddPanelToolbar>`, `<VddFloatingWidget>` and\n * `useFloatingWidgets()`. It is scoped to the panel, so each panel has its own toolbars,\n * its own widget stacks and its own z-order — nothing here is global.\n */\nimport { computed, provide, useTemplateRef, watchEffect } from 'vue'\nimport { ANCHORS } from '../core/panelOverlay'\nimport { createPanelOverlayStore, PANEL_OVERLAY_KEY } from '../core/overlayState'\nimport VddFloatingWidget from './VddFloatingWidget.vue'\n\nconst store = createPanelOverlayStore()\nprovide(PANEL_OVERLAY_KEY, store)\n\nconst root = useTemplateRef<HTMLDivElement>('root')\nwatchEffect(() => { store.container.value = root.value })\n\n/** Widgets opened through `useFloatingWidgets()`, rather than placed in a template. */\nconst managed = computed(() =>\n store.managedIds().map(id => ({ id, widget: store.managed.get(id)! })))\n\n/** Drop zones are only shown while something is being dragged. */\nconst dragging = computed(() => store.draggingId.value !== null)\n</script>\n\n<template>\n <div\n ref=\"root\"\n class=\"vdd-panel-overlay-root\"\n :class=\"{ 'vdd-dragging-active': dragging }\"\n data-vdd-panel-overlay\n >\n <slot />\n\n <template v-if=\"dragging\">\n <div\n v-for=\"zone in ANCHORS\"\n :key=\"zone\"\n class=\"vdd-panel-float-dropzone\"\n :class=\"[\n `vdd-panel-float-dropzone--${zone}`,\n { 'vdd-panel-float-dropzone--hovered': store.hovered.value === zone },\n ]\"\n :data-vdd-dropzone=\"zone\"\n aria-hidden=\"true\"\n />\n </template>\n\n <VddFloatingWidget\n v-for=\"{ id, widget } in managed\"\n :key=\"id\"\n :widget-id=\"id\"\n :title=\"widget.title\"\n :icon=\"widget.icon\"\n :open=\"true\"\n :placement=\"{ anchor: widget.anchor ?? 'top-right', stretch: widget.stretch ?? null }\"\n :width=\"widget.width ?? 320\"\n :height=\"widget.height ?? 240\"\n @update:open=\"(value: boolean) => { if (!value) store.closeManaged(id) }\"\n >\n <component :is=\"widget.component\" v-bind=\"widget.props\" />\n </VddFloatingWidget>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/**\n * A toolbar strip on one edge of a panel's overlay.\n *\n * It claims space on that edge, and docked widgets keep clear of it — both where they sit and\n * how far they may be resized. Left and right strips inset themselves past any top and bottom\n * strips, so the corners are never contested.\n */\nimport { computed, onBeforeUnmount, useTemplateRef, watchEffect } from 'vue'\nimport { usePanelOverlay } from '../composables/usePanelOverlay'\nimport type { ButtonVariant, ToolbarPosition, ToolbarVariant } from '../core/panelOverlay'\n\nconst props = withDefaults(defineProps<{\n position: ToolbarPosition\n /** @default 'transparent' */\n variant?: ToolbarVariant\n /** Inherited by this toolbar's buttons unless they override it. @default 'ghost' */\n buttonVariant?: ButtonVariant\n /** Icon button size in pixels. Left to the stylesheet when unset. */\n buttonSize?: number\n}>(), { variant: 'transparent', buttonVariant: 'ghost' })\n\nconst store = usePanelOverlay()\nconst el = useTemplateRef<HTMLDivElement>('el')\nconst isBlockEdge = computed(() => props.position === 'top' || props.position === 'bottom')\n\n/**\n * Re-measured, not measured once.\n *\n * A single measurement is right for a live mount, where the toolbar is already at its final\n * size. During a layout restore the panel's DOM is not necessarily settled at that instant, so\n * rdd baked in a wrong size — usually 0 — permanently, and every docked widget ended up\n * positioned at the toolbar's own edge, covering it. This also catches later changes: buttons\n * wrapping, a `buttonSize` change, content appearing.\n */\nlet observer: ResizeObserver | null = null\nwatchEffect((onCleanup) => {\n const node = el.value\n const position = props.position\n if (!node) return\n\n const measure = () => {\n store.registerToolbar(position, isBlockEdge.value ? node.offsetHeight : node.offsetWidth)\n }\n measure()\n\n if (typeof ResizeObserver !== 'undefined') {\n observer = new ResizeObserver(measure)\n observer.observe(node)\n }\n onCleanup(() => {\n observer?.disconnect()\n observer = null\n store.unregisterToolbar(position)\n })\n})\n\nonBeforeUnmount(() => store.unregisterToolbar(props.position))\n\n/**\n * Only position is inline, because only position depends on the other toolbars' measured\n * sizes. Everything else — padding, gap, backdrop, the strip's own thickness — is in the\n * stylesheet where a consumer can reach it (divergence D12).\n */\nconst placement = computed(() => {\n switch (props.position) {\n case 'top': return { top: '0px', left: '0px', right: '0px' }\n case 'bottom': return { bottom: '0px', left: '0px', right: '0px' }\n case 'left': return { insetInlineStart: '0px', top: `${store.insets.top}px`, bottom: `${store.insets.bottom}px` }\n default: return { insetInlineEnd: '0px', top: `${store.insets.top}px`, bottom: `${store.insets.bottom}px` }\n }\n})\n\nconst sizeVar = computed(() =>\n props.buttonSize != null ? { '--vdd-panel-toolbar-btn-size': `${props.buttonSize}px` } : {})\n</script>\n\n<template>\n <div\n ref=\"el\"\n class=\"vdd-panel-toolbar\"\n :class=\"`vdd-panel-toolbar--${position}`\"\n :data-variant=\"variant\"\n :data-btn-variant=\"buttonVariant\"\n :data-vdd-panel-toolbar=\"position\"\n :style=\"{ ...placement, ...sizeVar }\"\n role=\"toolbar\"\n :aria-orientation=\"isBlockEdge ? 'horizontal' : 'vertical'\"\n >\n <slot />\n </div>\n</template>\n","<script setup lang=\"ts\">\n/**\n * A toolbar strip on one edge of a panel's overlay.\n *\n * It claims space on that edge, and docked widgets keep clear of it — both where they sit and\n * how far they may be resized. Left and right strips inset themselves past any top and bottom\n * strips, so the corners are never contested.\n */\nimport { computed, onBeforeUnmount, useTemplateRef, watchEffect } from 'vue'\nimport { usePanelOverlay } from '../composables/usePanelOverlay'\nimport type { ButtonVariant, ToolbarPosition, ToolbarVariant } from '../core/panelOverlay'\n\nconst props = withDefaults(defineProps<{\n position: ToolbarPosition\n /** @default 'transparent' */\n variant?: ToolbarVariant\n /** Inherited by this toolbar's buttons unless they override it. @default 'ghost' */\n buttonVariant?: ButtonVariant\n /** Icon button size in pixels. Left to the stylesheet when unset. */\n buttonSize?: number\n}>(), { variant: 'transparent', buttonVariant: 'ghost' })\n\nconst store = usePanelOverlay()\nconst el = useTemplateRef<HTMLDivElement>('el')\nconst isBlockEdge = computed(() => props.position === 'top' || props.position === 'bottom')\n\n/**\n * Re-measured, not measured once.\n *\n * A single measurement is right for a live mount, where the toolbar is already at its final\n * size. During a layout restore the panel's DOM is not necessarily settled at that instant, so\n * rdd baked in a wrong size — usually 0 — permanently, and every docked widget ended up\n * positioned at the toolbar's own edge, covering it. This also catches later changes: buttons\n * wrapping, a `buttonSize` change, content appearing.\n */\nlet observer: ResizeObserver | null = null\nwatchEffect((onCleanup) => {\n const node = el.value\n const position = props.position\n if (!node) return\n\n const measure = () => {\n store.registerToolbar(position, isBlockEdge.value ? node.offsetHeight : node.offsetWidth)\n }\n measure()\n\n if (typeof ResizeObserver !== 'undefined') {\n observer = new ResizeObserver(measure)\n observer.observe(node)\n }\n onCleanup(() => {\n observer?.disconnect()\n observer = null\n store.unregisterToolbar(position)\n })\n})\n\nonBeforeUnmount(() => store.unregisterToolbar(props.position))\n\n/**\n * Only position is inline, because only position depends on the other toolbars' measured\n * sizes. Everything else — padding, gap, backdrop, the strip's own thickness — is in the\n * stylesheet where a consumer can reach it (divergence D12).\n */\nconst placement = computed(() => {\n switch (props.position) {\n case 'top': return { top: '0px', left: '0px', right: '0px' }\n case 'bottom': return { bottom: '0px', left: '0px', right: '0px' }\n case 'left': return { insetInlineStart: '0px', top: `${store.insets.top}px`, bottom: `${store.insets.bottom}px` }\n default: return { insetInlineEnd: '0px', top: `${store.insets.top}px`, bottom: `${store.insets.bottom}px` }\n }\n})\n\nconst sizeVar = computed(() =>\n props.buttonSize != null ? { '--vdd-panel-toolbar-btn-size': `${props.buttonSize}px` } : {})\n</script>\n\n<template>\n <div\n ref=\"el\"\n class=\"vdd-panel-toolbar\"\n :class=\"`vdd-panel-toolbar--${position}`\"\n :data-variant=\"variant\"\n :data-btn-variant=\"buttonVariant\"\n :data-vdd-panel-toolbar=\"position\"\n :style=\"{ ...placement, ...sizeVar }\"\n role=\"toolbar\"\n :aria-orientation=\"isBlockEdge ? 'horizontal' : 'vertical'\"\n >\n <slot />\n </div>\n</template>\n","<script setup lang=\"ts\">\n/** An icon button for a `<VddPanelToolbar>`. The icon is a slot, so anything renders. */\ndefineProps<{\n /** Tooltip and accessible name. */\n title?: string\n disabled?: boolean\n /** Overrides the toolbar's own `buttonVariant`. */\n variant?: 'ghost' | 'soft' | 'outlined' | 'filled'\n}>()\nconst emit = defineEmits<{ click: [] }>()\n</script>\n\n<template>\n <button\n type=\"button\"\n class=\"vdd-panel-toolbar-btn\"\n :title=\"title\"\n :aria-label=\"title\"\n :disabled=\"disabled\"\n :data-variant=\"variant\"\n @click=\"emit('click')\"\n ><slot /></button>\n</template>\n","<script setup lang=\"ts\">\n/** An icon button for a `<VddPanelToolbar>`. The icon is a slot, so anything renders. */\ndefineProps<{\n /** Tooltip and accessible name. */\n title?: string\n disabled?: boolean\n /** Overrides the toolbar's own `buttonVariant`. */\n variant?: 'ghost' | 'soft' | 'outlined' | 'filled'\n}>()\nconst emit = defineEmits<{ click: [] }>()\n</script>\n\n<template>\n <button\n type=\"button\"\n class=\"vdd-panel-toolbar-btn\"\n :title=\"title\"\n :aria-label=\"title\"\n :disabled=\"disabled\"\n :data-variant=\"variant\"\n @click=\"emit('click')\"\n ><slot /></button>\n</template>\n","<script setup lang=\"ts\">\n/**\n * A two-state icon button for a `<VddPanelToolbar>`.\n *\n * rdd took `active` plus `onToggle` and left flipping it to the caller. `v-model:active` is\n * the same thing without the boilerplate, and still controllable: bind it and you own it.\n */\ndefineProps<{\n title?: string\n disabled?: boolean\n variant?: 'ghost' | 'soft' | 'outlined' | 'filled'\n}>()\n\nconst active = defineModel<boolean>('active', { default: false })\n</script>\n\n<template>\n <button\n type=\"button\"\n class=\"vdd-panel-toolbar-btn\"\n :class=\"{ 'vdd-panel-toolbar-btn--active': active }\"\n :title=\"title\"\n :aria-label=\"title\"\n :aria-pressed=\"active\"\n :disabled=\"disabled\"\n :data-variant=\"variant\"\n @click=\"active = !active\"\n ><slot /></button>\n</template>\n","<script setup lang=\"ts\">\n/**\n * A two-state icon button for a `<VddPanelToolbar>`.\n *\n * rdd took `active` plus `onToggle` and left flipping it to the caller. `v-model:active` is\n * the same thing without the boilerplate, and still controllable: bind it and you own it.\n */\ndefineProps<{\n title?: string\n disabled?: boolean\n variant?: 'ghost' | 'soft' | 'outlined' | 'filled'\n}>()\n\nconst active = defineModel<boolean>('active', { default: false })\n</script>\n\n<template>\n <button\n type=\"button\"\n class=\"vdd-panel-toolbar-btn\"\n :class=\"{ 'vdd-panel-toolbar-btn--active': active }\"\n :title=\"title\"\n :aria-label=\"title\"\n :aria-pressed=\"active\"\n :disabled=\"disabled\"\n :data-variant=\"variant\"\n @click=\"active = !active\"\n ><slot /></button>\n</template>\n","<script setup lang=\"ts\">\n/** A divider between groups of toolbar items. */\n</script>\n\n<template>\n <span class=\"vdd-panel-toolbar__sep\" aria-hidden=\"true\" />\n</template>\n","<script setup lang=\"ts\">\n/** A divider between groups of toolbar items. */\n</script>\n\n<template>\n <span class=\"vdd-panel-toolbar__sep\" aria-hidden=\"true\" />\n</template>\n","<script setup lang=\"ts\">\n/** Grows to push everything after it to the far end of the toolbar. */\n</script>\n\n<template>\n <span class=\"vdd-panel-toolbar__spacer\" aria-hidden=\"true\" />\n</template>\n","<script setup lang=\"ts\">\n/** Grows to push everything after it to the far end of the toolbar. */\n</script>\n\n<template>\n <span class=\"vdd-panel-toolbar__spacer\" aria-hidden=\"true\" />\n</template>\n","<script setup lang=\"ts\">\n/** Wraps a control that is not one of the library's buttons — a select, an input, a badge. */\n</script>\n\n<template>\n <span class=\"vdd-panel-toolbar__item\"><slot /></span>\n</template>\n","<script setup lang=\"ts\">\n/** Wraps a control that is not one of the library's buttons — a select, an input, a badge. */\n</script>\n\n<template>\n <span class=\"vdd-panel-toolbar__item\"><slot /></span>\n</template>\n","<script setup lang=\"ts\">\n/** Centres its content in the toolbar, independent of what sits on either side. */\n</script>\n\n<template>\n <div class=\"vdd-panel-toolbar__center\"><slot /></div>\n</template>\n","<script setup lang=\"ts\">\n/** Centres its content in the toolbar, independent of what sits on either side. */\n</script>\n\n<template>\n <div class=\"vdd-panel-toolbar__center\"><slot /></div>\n</template>\n","<script setup lang=\"ts\">\n/**\n * A debounced search field for a `<VddPanelToolbar>`: a compact icon button that expands into\n * an input, with results in a dropdown teleported to the body so the toolbar's own bounds\n * cannot clip it.\n *\n * `search` receives an `AbortSignal` and must honour it. Without that, a slow request for an\n * earlier query can land after a fast one for a later query and overwrite it — the classic\n * stale-result race, and the reason the signal is part of the signature rather than optional.\n */\nimport { computed, nextTick, onBeforeUnmount, ref, useTemplateRef } from 'vue'\nimport type { Component } from 'vue'\nimport { useWorkspace } from '../composables/useWorkspace'\n\n/** One row in the dropdown. */\nexport interface SearchResult {\n id: string\n label: string\n /** Secondary text, below the label. */\n description?: string\n /** Groups results under a heading. */\n group?: string\n icon?: Component\n}\n\nconst props = withDefaults(defineProps<{\n placeholder?: string\n /** Called on each change, debounced. Return results, or a promise of them. */\n search: (query: string, signal: AbortSignal) => SearchResult[] | Promise<SearchResult[]>\n /** Debounce in ms. @default 300 */\n debounce?: number\n}>(), { debounce: 300 })\n\nconst emit = defineEmits<{ select: [result: SearchResult] }>()\n\nconst ws = useWorkspace()\nconst root = useTemplateRef<HTMLDivElement>('root')\nconst input = useTemplateRef<HTMLInputElement>('input')\n\nconst expanded = ref(false)\nconst query = ref('')\nconst results = ref<SearchResult[]>([])\nconst dropdown = ref<{ top: number; left: number; width: number } | null>(null)\n\nlet controller: AbortController | null = null\nlet timer: ReturnType<typeof setTimeout> | null = null\n\nfunction reset(): void {\n controller?.abort()\n controller = null\n if (timer) { clearTimeout(timer); timer = null }\n}\n\nasync function expand(): Promise<void> {\n expanded.value = true\n await nextTick()\n input.value?.focus({ preventScroll: true })\n}\n\nfunction collapse(): void {\n expanded.value = false\n query.value = ''\n results.value = []\n dropdown.value = null\n reset()\n}\n\nfunction place(): void {\n const node = root.value\n if (!node || results.value.length === 0) { dropdown.value = null; return }\n const r = node.getBoundingClientRect()\n const width = Math.max(r.width, 240)\n const left = Math.min(r.left, window.innerWidth - width - 8)\n dropdown.value = { top: r.bottom + 4, left: Math.max(8, left), width }\n}\n\nfunction onInput(): void {\n reset()\n if (!query.value.trim()) { results.value = []; dropdown.value = null; return }\n\n timer = setTimeout(async () => {\n const own = new AbortController()\n controller = own\n try {\n const found = await props.search(query.value, own.signal)\n // A result that arrives after its own request was superseded is discarded here as well\n // as in the caller's fetch, since an abort cannot retract a resolved promise.\n if (own.signal.aborted) return\n results.value = found\n place()\n } catch {\n // An abort, or the caller's own error. Either way there is nothing to show.\n }\n }, props.debounce)\n}\n\nfunction choose(result: SearchResult): void {\n emit('select', result)\n collapse()\n}\n\n/** Collapse when focus leaves the field entirely, but not when it moves inside it. */\nfunction onFocusOut(event: FocusEvent): void {\n const next = event.relatedTarget\n if (next instanceof Node && root.value?.contains(next)) return\n collapse()\n}\n\nconst grouped = computed(() => {\n const map = new Map<string, SearchResult[]>()\n for (const r of results.value) {\n const key = r.group ?? ''\n const list = map.get(key)\n if (list) list.push(r)\n else map.set(key, [r])\n }\n return Array.from(map, ([group, items]) => ({ group, items }))\n})\n\nonBeforeUnmount(reset)\n</script>\n\n<template>\n <div\n ref=\"root\"\n class=\"vdd-panel-toolbar-search\"\n :class=\"{ 'vdd-panel-toolbar-search--open': expanded }\"\n data-vdd-toolbar-search\n @focusout=\"onFocusOut\"\n >\n <button\n type=\"button\"\n class=\"vdd-panel-toolbar-btn\"\n :title=\"ws.format(ws.messages.search)\"\n :aria-label=\"ws.format(ws.messages.search)\"\n data-vdd-search-toggle\n @click=\"expanded ? collapse() : expand()\"\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"6.5\" cy=\"6.5\" r=\"4.5\" />\n <line x1=\"10\" y1=\"10\" x2=\"14\" y2=\"14\" />\n </svg>\n </button>\n\n <input\n v-if=\"expanded\"\n ref=\"input\"\n v-model=\"query\"\n class=\"vdd-panel-toolbar-search__input\"\n type=\"text\"\n :placeholder=\"placeholder ?? ws.format(ws.messages.search)\"\n autocomplete=\"off\"\n data-vdd-search-input\n @input=\"onInput\"\n @keydown.esc=\"collapse\"\n >\n\n <!--\n Teleported so the toolbar's bounds cannot clip the dropdown. Its z-index comes from the\n stylesheet, against --vdd-z-base, so `zIndexBase` moves it with everything else.\n -->\n <Teleport v-if=\"dropdown && results.length\" to=\"body\">\n <div\n class=\"vdd-panel-toolbar-search__dropdown\"\n :style=\"{ position: 'fixed', top: `${dropdown.top}px`, left: `${dropdown.left}px`, width: `${dropdown.width}px` }\"\n data-vdd-search-results\n @mousedown.prevent\n >\n <template v-for=\"{ group, items } in grouped\" :key=\"group || '__default__'\">\n <div v-if=\"group\" class=\"vdd-panel-toolbar-search__group\">{{ group }}</div>\n <button\n v-for=\"item in items\"\n :key=\"item.id\"\n type=\"button\"\n class=\"vdd-panel-toolbar-search__item\"\n :data-vdd-search-result=\"item.id\"\n @click=\"choose(item)\"\n >\n <span v-if=\"item.icon\" class=\"vdd-panel-toolbar-search__item-icon\"><component :is=\"item.icon\" /></span>\n <span class=\"vdd-panel-toolbar-search__item-label\">{{ item.label }}</span>\n <span v-if=\"item.description\" class=\"vdd-panel-toolbar-search__item-desc\">{{ item.description }}</span>\n </button>\n </template>\n </div>\n </Teleport>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/**\n * A debounced search field for a `<VddPanelToolbar>`: a compact icon button that expands into\n * an input, with results in a dropdown teleported to the body so the toolbar's own bounds\n * cannot clip it.\n *\n * `search` receives an `AbortSignal` and must honour it. Without that, a slow request for an\n * earlier query can land after a fast one for a later query and overwrite it — the classic\n * stale-result race, and the reason the signal is part of the signature rather than optional.\n */\nimport { computed, nextTick, onBeforeUnmount, ref, useTemplateRef } from 'vue'\nimport type { Component } from 'vue'\nimport { useWorkspace } from '../composables/useWorkspace'\n\n/** One row in the dropdown. */\nexport interface SearchResult {\n id: string\n label: string\n /** Secondary text, below the label. */\n description?: string\n /** Groups results under a heading. */\n group?: string\n icon?: Component\n}\n\nconst props = withDefaults(defineProps<{\n placeholder?: string\n /** Called on each change, debounced. Return results, or a promise of them. */\n search: (query: string, signal: AbortSignal) => SearchResult[] | Promise<SearchResult[]>\n /** Debounce in ms. @default 300 */\n debounce?: number\n}>(), { debounce: 300 })\n\nconst emit = defineEmits<{ select: [result: SearchResult] }>()\n\nconst ws = useWorkspace()\nconst root = useTemplateRef<HTMLDivElement>('root')\nconst input = useTemplateRef<HTMLInputElement>('input')\n\nconst expanded = ref(false)\nconst query = ref('')\nconst results = ref<SearchResult[]>([])\nconst dropdown = ref<{ top: number; left: number; width: number } | null>(null)\n\nlet controller: AbortController | null = null\nlet timer: ReturnType<typeof setTimeout> | null = null\n\nfunction reset(): void {\n controller?.abort()\n controller = null\n if (timer) { clearTimeout(timer); timer = null }\n}\n\nasync function expand(): Promise<void> {\n expanded.value = true\n await nextTick()\n input.value?.focus({ preventScroll: true })\n}\n\nfunction collapse(): void {\n expanded.value = false\n query.value = ''\n results.value = []\n dropdown.value = null\n reset()\n}\n\nfunction place(): void {\n const node = root.value\n if (!node || results.value.length === 0) { dropdown.value = null; return }\n const r = node.getBoundingClientRect()\n const width = Math.max(r.width, 240)\n const left = Math.min(r.left, window.innerWidth - width - 8)\n dropdown.value = { top: r.bottom + 4, left: Math.max(8, left), width }\n}\n\nfunction onInput(): void {\n reset()\n if (!query.value.trim()) { results.value = []; dropdown.value = null; return }\n\n timer = setTimeout(async () => {\n const own = new AbortController()\n controller = own\n try {\n const found = await props.search(query.value, own.signal)\n // A result that arrives after its own request was superseded is discarded here as well\n // as in the caller's fetch, since an abort cannot retract a resolved promise.\n if (own.signal.aborted) return\n results.value = found\n place()\n } catch {\n // An abort, or the caller's own error. Either way there is nothing to show.\n }\n }, props.debounce)\n}\n\nfunction choose(result: SearchResult): void {\n emit('select', result)\n collapse()\n}\n\n/** Collapse when focus leaves the field entirely, but not when it moves inside it. */\nfunction onFocusOut(event: FocusEvent): void {\n const next = event.relatedTarget\n if (next instanceof Node && root.value?.contains(next)) return\n collapse()\n}\n\nconst grouped = computed(() => {\n const map = new Map<string, SearchResult[]>()\n for (const r of results.value) {\n const key = r.group ?? ''\n const list = map.get(key)\n if (list) list.push(r)\n else map.set(key, [r])\n }\n return Array.from(map, ([group, items]) => ({ group, items }))\n})\n\nonBeforeUnmount(reset)\n</script>\n\n<template>\n <div\n ref=\"root\"\n class=\"vdd-panel-toolbar-search\"\n :class=\"{ 'vdd-panel-toolbar-search--open': expanded }\"\n data-vdd-toolbar-search\n @focusout=\"onFocusOut\"\n >\n <button\n type=\"button\"\n class=\"vdd-panel-toolbar-btn\"\n :title=\"ws.format(ws.messages.search)\"\n :aria-label=\"ws.format(ws.messages.search)\"\n data-vdd-search-toggle\n @click=\"expanded ? collapse() : expand()\"\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"6.5\" cy=\"6.5\" r=\"4.5\" />\n <line x1=\"10\" y1=\"10\" x2=\"14\" y2=\"14\" />\n </svg>\n </button>\n\n <input\n v-if=\"expanded\"\n ref=\"input\"\n v-model=\"query\"\n class=\"vdd-panel-toolbar-search__input\"\n type=\"text\"\n :placeholder=\"placeholder ?? ws.format(ws.messages.search)\"\n autocomplete=\"off\"\n data-vdd-search-input\n @input=\"onInput\"\n @keydown.esc=\"collapse\"\n >\n\n <!--\n Teleported so the toolbar's bounds cannot clip the dropdown. Its z-index comes from the\n stylesheet, against --vdd-z-base, so `zIndexBase` moves it with everything else.\n -->\n <Teleport v-if=\"dropdown && results.length\" to=\"body\">\n <div\n class=\"vdd-panel-toolbar-search__dropdown\"\n :style=\"{ position: 'fixed', top: `${dropdown.top}px`, left: `${dropdown.left}px`, width: `${dropdown.width}px` }\"\n data-vdd-search-results\n @mousedown.prevent\n >\n <template v-for=\"{ group, items } in grouped\" :key=\"group || '__default__'\">\n <div v-if=\"group\" class=\"vdd-panel-toolbar-search__group\">{{ group }}</div>\n <button\n v-for=\"item in items\"\n :key=\"item.id\"\n type=\"button\"\n class=\"vdd-panel-toolbar-search__item\"\n :data-vdd-search-result=\"item.id\"\n @click=\"choose(item)\"\n >\n <span v-if=\"item.icon\" class=\"vdd-panel-toolbar-search__item-icon\"><component :is=\"item.icon\" /></span>\n <span class=\"vdd-panel-toolbar-search__item-label\">{{ item.label }}</span>\n <span v-if=\"item.description\" class=\"vdd-panel-toolbar-search__item-desc\">{{ item.description }}</span>\n </button>\n </template>\n </div>\n </Teleport>\n </div>\n</template>\n","import { getCurrentScope, onScopeDispose } from 'vue'\nimport type { ContextMenuItem, ShowContextMenuOptions } from '../core/contextMenu'\nimport { usePanel } from './usePanel'\nimport { useWorkspace } from './useWorkspace'\n\n/**\n * Open a context menu from anywhere.\n *\n * ```ts\n * const showMenu = useContextMenu()\n * showMenu({ event, items: [{ label: 'Rename', action: rename }] })\n * ```\n *\n * Works wherever `useWorkspace()` works, including outside the component tree — the menu\n * request is state, and `<VddContextMenu>` renders what is pending.\n */\nexport function useContextMenu(): (options: ShowContextMenuOptions) => void {\n const ws = useWorkspace()\n return (options) => ws.showContextMenu(options)\n}\n\n/**\n * Contribute items to *this panel's* own context menu, from inside the panel.\n *\n * Pass a getter: it is read each time the menu opens, so items that enable, disable, check\n * or disappear with the panel's state need no extra wiring.\n *\n * ```ts\n * usePanelContextMenu(() => [\n * { label: 'Save', action: save },\n * { label: 'Revert', action: revert, disabled: !dirty.value },\n * ])\n * ```\n *\n * Registered in `setup` and disposed with the component — nothing to unsubscribe.\n */\nexport function usePanelContextMenu(items: () => ContextMenuItem[]): void {\n const ws = useWorkspace()\n const { id } = usePanel()\n if (id === 'standalone') {\n if (process.env.NODE_ENV !== 'production') {\n console.warn(\n '[vue-dockable-desktop] usePanelContextMenu() was called outside a panel, so it did ' +\n 'nothing. This is expected when a panel component is rendered standalone.',\n )\n }\n return\n }\n const off = ws.registerPanelMenu(id, items)\n if (getCurrentScope()) onScopeDispose(off)\n}\n","import { inject } from 'vue'\nimport { SIDEBAR_KEY, SIDEBAR_TAB_KEY } from '../core/sidebarTypes'\nimport type { SidebarContext, SidebarTabContext } from '../core/sidebarTypes'\n\n/**\n * Control the sidebar from anywhere inside it — including from a panel rendered in the\n * workspace, since the workspace is the sidebar's own content.\n *\n * @throws if used outside a `<VddSidebar>`.\n */\nexport function useSidebar(): SidebarContext {\n const context = inject(SIDEBAR_KEY, null)\n if (!context) {\n throw new Error('[vue-dockable-desktop] useSidebar() must be used inside a <VddSidebar>.')\n }\n return context\n}\n\n/**\n * Control scoped to the tab you are inside: close the drawer, re-open this tab, or switch to\n * another one, without having to know which tab you are.\n *\n * @throws if used outside a sidebar tab's content.\n */\nexport function useSidebarTab(): SidebarTabContext {\n const context = inject(SIDEBAR_TAB_KEY, null)\n if (!context) {\n throw new Error(\n '[vue-dockable-desktop] useSidebarTab() must be used inside a sidebar tab\\'s content.',\n )\n }\n return context\n}\n","import { useWorkspace } from './useWorkspace'\nimport type { ToolbarState } from '../core/toolbarState'\n\n/**\n * Toolbar selection state — which item is active in each radio group, and which toggles are\n * on — for items the caller has not taken control of.\n *\n * Reachable anywhere `useWorkspace()` is, so a panel can read or set the active tool without\n * the toolbar having to pass anything down. rdd needed a `<ToolbarProvider>` in the tree.\n */\nexport function useToolbar(): ToolbarState {\n return useWorkspace().toolbar\n}\n","import { computed } from 'vue'\nimport type { Component, ComputedRef } from 'vue'\nimport { useWorkspace } from './useWorkspace'\nimport type { ModalOptions, OverlayInstance, SidePanelOptions } from '../core/overlays'\n\n/**\n * The modal stack.\n *\n * ```ts\n * const { open, close, closeAll, stack } = useModals()\n * const id = open(EditFeature, { featureId }, { size: 'medium', title: 'Edit' })\n * ```\n *\n * `open` returns the instance id, which is what `close(id)` takes. Modals stack, so opening\n * one never closes another.\n *\n * Every one of these is also reachable as `workspace.overlays.*` without a component, which\n * is the usual case for a confirmation raised by a service.\n */\nexport function useModals(): {\n /** Bottom to top — the last entry is the topmost. */\n stack: ComputedRef<OverlayInstance[]>\n /** The topmost modal, or `null`. */\n topmost: ComputedRef<OverlayInstance | null>\n open: (component: Component, props?: Record<string, unknown>, options?: ModalOptions) => string\n /** Close, honouring the modal's close guard and dirty state. */\n close: (id: string, options?: { force?: boolean }) => Promise<void>\n closeAll: () => void\n} {\n const { overlays } = useWorkspace()\n return {\n stack: computed(() => overlays.state.modals),\n topmost: computed(() => overlays.state.modals[overlays.state.modals.length - 1] ?? null),\n open: overlays.openModal,\n close: (id, options) => overlays.requestClose(id, options),\n closeAll: overlays.closeAllModals,\n }\n}\n\n/**\n * The two side drawers.\n *\n * ```ts\n * const { openLeft, openRight, close, left, right } = useSidePanels()\n * await openLeft(LayerList, { layers }, { title: 'Layers', width: 320 })\n * ```\n *\n * Each side holds one panel, so opening is also closing whatever was there — which is why\n * `openLeft`/`openRight` are async and resolve to `null` when the occupant's close guard\n * refuses. A synchronous return would have to either ignore the guard or lie about the id.\n */\nexport function useSidePanels(): {\n left: ComputedRef<OverlayInstance | null>\n right: ComputedRef<OverlayInstance | null>\n openLeft: (component: Component, props?: Record<string, unknown>, options?: SidePanelOptions) => Promise<string | null>\n openRight: (component: Component, props?: Record<string, unknown>, options?: SidePanelOptions) => Promise<string | null>\n close: (id: string, options?: { force?: boolean }) => Promise<void>\n /** Close both drawers, leaving the modal stack alone. */\n closeAll: () => void\n} {\n const { overlays } = useWorkspace()\n return {\n left: computed(() => overlays.state.leftPanel),\n right: computed(() => overlays.state.rightPanel),\n openLeft: overlays.openLeftPanel,\n openRight: overlays.openRightPanel,\n close: (id, options) => overlays.requestClose(id, options),\n closeAll: () => {\n for (const instance of [overlays.state.leftPanel, overlays.state.rightPanel]) {\n if (instance) overlays.close(instance.id)\n }\n },\n }\n}\n","import { computed, getCurrentScope, onScopeDispose, toValue, watch } from 'vue'\nimport type { Component, ComputedRef, MaybeRefOrGetter } from 'vue'\nimport { useWorkspace } from './useWorkspace'\nimport { usePanel } from './usePanel'\nimport { mergeSidebarTabs, mergeToolbarItems } from '../core/contributions'\nimport type { PanelContribution } from '../core/contributions'\nimport type { SidebarTab } from '../core/sidebarTypes'\nimport type { ToolbarItem } from '../core/toolbarTypes'\n\n/**\n * Publish this panel's toolbar items and sidebar sections, for the shell to surface while\n * this panel is the active one.\n *\n * Pass a getter or a ref, not a plain object: the contribution is re-published whenever it\n * changes, so items that enable, check or disappear with the panel's own state need no extra\n * wiring.\n *\n * ```ts\n * const tool = ref<'pan' | 'draw'>('pan')\n * usePanelContribution(() => ({\n * toolbarItems: [\n * { type: 'toggle', id: 'draw', label: 'Draw', icon: Pencil,\n * active: tool.value === 'draw', onToggle: v => (tool.value = v ? 'draw' : 'pan') },\n * ],\n * sidebarSections: [{ id: 'layers', label: 'Layers', component: LayerList }],\n * }))\n * ```\n *\n * rdd asked callers to memoise the object themselves, and to call the hook on every render,\n * because a new object literal each render meant republishing each render. A getter makes\n * that the framework's problem instead of the caller's.\n *\n * Registered in `setup` and withdrawn with the component — nothing to unsubscribe.\n */\nexport function usePanelContribution(contribution: MaybeRefOrGetter<PanelContribution>): void {\n const ws = useWorkspace()\n const { id } = usePanel()\n\n if (id === 'standalone') {\n if (process.env.NODE_ENV !== 'production') {\n console.warn(\n '[vue-dockable-desktop] usePanelContribution() was called outside a panel, so it did ' +\n 'nothing. This is expected when a panel component is rendered standalone.',\n )\n }\n return\n }\n\n let withdraw = ws.contributions.publish(id, toValue(contribution))\n const stop = watch(() => toValue(contribution), (next) => {\n // Publish the new one *first*, then withdraw the old registration. The other order\n // leaves a moment where this panel has published nothing, which anything reading the\n // active contribution during the same flush would observe as a disappearance. The\n // store's withdraw is a no-op once something newer has replaced it, which is what makes\n // this order safe rather than merely lucky.\n const previous = withdraw\n withdraw = ws.contributions.publish(id, next)\n previous()\n }, { deep: true })\n\n if (getCurrentScope()) {\n onScopeDispose(() => { stop(); withdraw() })\n }\n}\n\n/**\n * What the active panel has published, or `null`.\n *\n * For the app shell to merge into its own `<VddToolbar :items>` and `<VddSidebar :tabs>`.\n * Nothing is merged automatically: the library does not know what a contributed item means\n * for your domain, or where in your toolbar it belongs.\n */\nexport function useActiveContribution(): ComputedRef<PanelContribution | null> {\n return useWorkspace().activeContribution\n}\n\n/**\n * A static toolbar list with the active panel's contributed items appended behind a\n * separator, as a `computed`.\n *\n * `mergeToolbarItems` is the plain function underneath, for when the merge position or the\n * separator needs to be different.\n */\nexport function useMergedToolbarItems(\n staticItems: MaybeRefOrGetter<ToolbarItem[]>,\n): ComputedRef<ToolbarItem[]> {\n const active = useActiveContribution()\n return computed(() => mergeToolbarItems(toValue(staticItems), active.value))\n}\n\n/** A static tab list with the active panel's contributed sections appended, as a `computed`. */\nexport function useMergedSidebarTabs(\n staticTabs: MaybeRefOrGetter<SidebarTab[]>,\n fallbackIcon?: Component,\n): ComputedRef<SidebarTab[]> {\n const active = useActiveContribution()\n return computed(() => mergeSidebarTabs(toValue(staticTabs), active.value, fallbackIcon))\n}\n","import { computed, onScopeDispose, ref } from 'vue'\nimport type { ComputedRef } from 'vue'\n\n/** What the workspace is currently rendering as. */\nexport type ColorScheme = 'dark' | 'light'\n\nconst read = (): ColorScheme =>\n document.documentElement.getAttribute('data-color-scheme') === 'light' ? 'light' : 'dark'\n\n/**\n * The workspace's current colour scheme, as a ref that follows it.\n *\n * `<VddDesktop>` mirrors the scheme onto `document.documentElement` as\n * `data-color-scheme`; this watches that attribute. For panel content that has to match —\n * a map's tile layer, an embedded editor's theme, a chart's palette.\n *\n * ```ts\n * const scheme = useColorScheme()\n * watch(scheme, s => map.setStyle(s === 'dark' ? darkStyle : lightStyle))\n * ```\n *\n * Anything other than `'light'` reads as `'dark'`, including a missing attribute, so there is\n * no third state to handle.\n */\nexport function useColorScheme(): ComputedRef<ColorScheme> {\n const scheme = ref<ColorScheme>(read())\n\n // A MutationObserver rather than a workspace subscription: the attribute is the contract,\n // so this works for a scheme set by the application itself, with no workspace involved.\n const observer = new MutationObserver(() => { scheme.value = read() })\n observer.observe(document.documentElement, { attributes: true, attributeFilter: ['data-color-scheme'] })\n onScopeDispose(() => observer.disconnect())\n\n // A computed rather than the raw ref: reading the scheme is the whole API, and writing it\n // would set an attribute nobody is watching for.\n return computed(() => scheme.value)\n}\n","/**\n * vue-dockable-desktop — public entry point.\n *\n * Every runtime export here is mirrored in `api-surface.json`; the gate fails if the two\n * disagree, so the public surface cannot grow by accident.\n */\n\n/** The library version, mirroring `package.json`. */\nexport const version = '1.0.0'\n\n// ─── The workspace ───────────────────────────────────────────────────────────\n// `createWorkspace()` returns both the imperative API and a Vue plugin, the shape\n// `createPinia()` / `createRouter()` already established.\nexport { createWorkspace, WORKSPACE_KEY } from './core/workspace'\nexport type { Workspace, WorkspaceConfig, WorkspaceState, PanelDefinition, OpenPanelOptions, HostClasses } from './core/workspace'\nexport type { BuiltInEvents } from './core/eventBus'\n\n// ─── Components ──────────────────────────────────────────────────────────────\nexport { default as VddDesktop } from './components/VddDesktop.vue'\nexport { default as VddContextMenu } from './components/VddContextMenu.vue'\nexport { default as VddSidebar } from './components/VddSidebar.vue'\nexport { default as VddSecondarySidebar } from './components/VddSecondarySidebar.vue'\nexport { default as VddToolbar } from './components/VddToolbar.vue'\nexport { default as VddSidePanels } from './components/VddSidePanels.vue'\nexport { default as VddModals } from './components/VddModals.vue'\nexport { default as VddToasts } from './components/VddToasts.vue'\nexport { default as VddConfirm } from './components/VddConfirm.vue'\n\n// ─── Panel overlay — toolbars and floating widgets inside one panel ──────────\nexport { default as VddPanelOverlay } from './components/VddPanelOverlay.vue'\nexport { default as VddPanelToolbar } from './components/VddPanelToolbar.vue'\nexport { default as VddToolbarButton } from './components/VddToolbarButton.vue'\nexport { default as VddToolbarToggle } from './components/VddToolbarToggle.vue'\nexport { default as VddToolbarSeparator } from './components/VddToolbarSeparator.vue'\nexport { default as VddToolbarSpacer } from './components/VddToolbarSpacer.vue'\nexport { default as VddToolbarItem } from './components/VddToolbarItem.vue'\nexport { default as VddToolbarCenter } from './components/VddToolbarCenter.vue'\nexport { default as VddToolbarSearch } from './components/VddToolbarSearch.vue'\nexport { default as VddFloatingWidget } from './components/VddFloatingWidget.vue'\n\n// ─── Composables ─────────────────────────────────────────────────────────────\nexport { useWorkspace } from './composables/useWorkspace'\nexport { useContextMenu, usePanelContextMenu } from './composables/useContextMenu'\nexport { useSidebar, useSidebarTab } from './composables/useSidebar'\nexport { useToolbar } from './composables/useToolbar'\nexport { usePanel } from './composables/usePanel'\nexport type { UsePanelReturn } from './composables/usePanel'\nexport { useModals, useSidePanels } from './composables/useOverlays'\nexport { useFloatingWidgets } from './composables/usePanelOverlay'\nexport {\n usePanelContribution,\n useActiveContribution,\n useMergedToolbarItems,\n useMergedSidebarTabs,\n} from './composables/useContributions'\nexport { useColorScheme } from './composables/useColorScheme'\nexport type { ColorScheme } from './composables/useColorScheme'\n\n// ─── Drag and resize primitives ──────────────────────────────────────────────\n// Public so an application can build its own resizable UI inside a panel with exactly the\n// behaviour the library uses for its own windows.\nexport { startPointerDrag, computeResizedRect } from './core/dragResize'\nexport { clampFloatingRect } from './core/anchorGeometry'\nexport type { FloatingRect } from './core/anchorGeometry'\nexport type { PointerDragConfig, ResizeDir, ResizeRect, ResizeConstraints } from './core/dragResize'\n\n// ─── Serialisability ─────────────────────────────────────────────────────────\n// Exported so an application can apply the same check the library will apply at save time,\n// and warn early rather than discovering a panel was pruned after the fact.\nexport { isSerializable } from './core/serializable'\n\n// ─── Messages and i18n ───────────────────────────────────────────────────────\nexport { defaultMessages, formatLabel } from './core/messages'\nexport type { MessageKey } from './core/messages'\n\n// ─── Panel registry ──────────────────────────────────────────────────────────\nexport { PanelRegistry } from './core/registry'\nexport type { PanelRegistryEntry, PanelDefaultOptions } from './core/registry'\n\n// ─── Context menus ───────────────────────────────────────────────────────────\nexport type {\n ContextMenuItem,\n ContextMenuSimpleItem,\n ContextMenuSeparator,\n ContextMenuSubMenu,\n ContextMenuCheckbox,\n ShowContextMenuOptions,\n} from './core/contextMenu'\n\n// ─── Sidebar and toolbar ─────────────────────────────────────────────────────\nexport type {\n SidebarProps,\n SidebarTab,\n SidebarActionButton,\n SidebarCustomEntry,\n SidebarRailEntry,\n SidebarContext,\n SidebarTabContext,\n} from './core/sidebarTypes'\nexport type {\n ToolbarItem,\n ToolbarActionItem,\n ToolbarRadioItem,\n ToolbarToggleItem,\n ToolbarGroupItem,\n ToolbarGroupSubItem,\n ToolbarGroupEntry,\n ToolbarSeparator,\n} from './core/toolbarTypes'\nexport type { ToolbarState } from './core/toolbarState'\n\n// ─── Side panels, modals and toasts ──────────────────────────────────────────\nexport type {\n Overlays,\n OverlayInstance,\n OverlayKind,\n OverlayState,\n SidePanelOptions,\n ModalOptions,\n ConfirmDiscard,\n} from './core/overlays'\nexport { toast, resetToasts } from './core/toast'\nexport type {\n ToastFunction,\n ToastOptions,\n ResolvedToastOptions,\n ToastType,\n ToastPosition,\n ToastAdapter,\n ToastRecord,\n ToastPromiseMessages,\n} from './core/toast'\n\n// ─── Inner-widget placement ──────────────────────────────────────────────────\nexport type { Stretch, PanelFloatPlacement } from './core/stretch'\nexport type { ToolbarPosition, ToolbarVariant, ButtonVariant, ToolbarInsets } from './core/panelOverlay'\nexport type { ManagedWidget } from './core/overlayState'\n\n// ─── Panel contributions ─────────────────────────────────────────────────────\nexport { sectionToTab, mergeToolbarItems, mergeSidebarTabs } from './core/contributions'\nexport type { PanelContribution, PanelSidebarSection, Contributions } from './core/contributions'\n\nexport type { SearchResult } from './components/VddToolbarSearch.vue'\n\n// ─── Domain types ────────────────────────────────────────────────────────────\nexport type {\n AlertType,\n ContainerType,\n DirtyStateOptions,\n DropPosition,\n DropTarget,\n FloatAnchor,\n FloatingWindow,\n Label,\n LayoutGridNode,\n LayoutLeafNode,\n LayoutNode,\n MessageDescriptor,\n MessageFormatter,\n PanelInfo,\n PanelState,\n SerializedLayout,\n SplitDirection,\n SplitOrientation,\n} from './types'\n"],"mappings":"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"}