dsh-coding-sidebar 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +524 -0
- package/cordis.patch.yml +49 -0
- package/lib/client-editor.js +48229 -0
- package/lib/client-mermaid.js +200745 -0
- package/lib/client-registry.js +21867 -0
- package/lib/client-terminal.js +9361 -0
- package/lib/client.js +21867 -0
- package/lib/index.js +4556 -0
- package/lib/invariant.js +22 -0
- package/lib/types/agent-opens.d.ts +65 -0
- package/lib/types/agent-pty.d.ts +226 -0
- package/lib/types/browser-probe.d.ts +15 -0
- package/lib/types/bundle-route.d.ts +12 -0
- package/lib/types/client/BrowserView.d.ts +39 -0
- package/lib/types/client/DiffTab.d.ts +6 -0
- package/lib/types/client/DiffView.d.ts +51 -0
- package/lib/types/client/EditorHost.d.ts +13 -0
- package/lib/types/client/FileTree.d.ts +38 -0
- package/lib/types/client/FreeWindow.d.ts +29 -0
- package/lib/types/client/GitView.d.ts +10 -0
- package/lib/types/client/MarkdownHtml.d.ts +32 -0
- package/lib/types/client/OrphanedTab.d.ts +11 -0
- package/lib/types/client/PdfView.d.ts +6 -0
- package/lib/types/client/RenderBoundary.d.ts +32 -0
- package/lib/types/client/SandboxStatusBar.d.ts +10 -0
- package/lib/types/client/SideCardSection.d.ts +74 -0
- package/lib/types/client/SideChatView.d.ts +16 -0
- package/lib/types/client/Sidebar.d.ts +6 -0
- package/lib/types/client/SubagentView.d.ts +16 -0
- package/lib/types/client/TabBar.d.ts +54 -0
- package/lib/types/client/TerminalView.d.ts +24 -0
- package/lib/types/client/TextEditor.d.ts +10 -0
- package/lib/types/client/TreePanel.d.ts +24 -0
- package/lib/types/client/UploadOverlay.d.ts +20 -0
- package/lib/types/client/add-plugin-modal.d.ts +15 -0
- package/lib/types/client/api.d.ts +275 -0
- package/lib/types/client/binary-download.d.ts +12 -0
- package/lib/types/client/breakpoints.d.ts +17 -0
- package/lib/types/client/browser.d.ts +60 -0
- package/lib/types/client/builtins/index.d.ts +18 -0
- package/lib/types/client/builtins/tabs.d.ts +11 -0
- package/lib/types/client/builtins/viewers.d.ts +3 -0
- package/lib/types/client/chunk-loader.d.ts +131 -0
- package/lib/types/client/chunks/editor.d.ts +9 -0
- package/lib/types/client/chunks/mermaid.d.ts +10 -0
- package/lib/types/client/chunks/terminal.d.ts +9 -0
- package/lib/types/client/cm-themes.d.ts +24 -0
- package/lib/types/client/conversation-draft.d.ts +14 -0
- package/lib/types/client/desktop-env.d.ts +35 -0
- package/lib/types/client/editor-load.d.ts +66 -0
- package/lib/types/client/frame-batcher.d.ts +24 -0
- package/lib/types/client/icons.d.ts +59 -0
- package/lib/types/client/image-types.d.ts +3 -0
- package/lib/types/client/ime-guard.d.ts +36 -0
- package/lib/types/client/index.d.ts +25 -0
- package/lib/types/client/intercept.d.ts +46 -0
- package/lib/types/client/lang.d.ts +14 -0
- package/lib/types/client/layout-push.d.ts +15 -0
- package/lib/types/client/lazy-chunk.d.ts +25 -0
- package/lib/types/client/link-intercept.d.ts +42 -0
- package/lib/types/client/locales-ar.d.ts +18 -0
- package/lib/types/client/locales-de.d.ts +4 -0
- package/lib/types/client/locales-fr.d.ts +10 -0
- package/lib/types/client/locales-hi.d.ts +17 -0
- package/lib/types/client/locales-id.d.ts +15 -0
- package/lib/types/client/locales-it.d.ts +8 -0
- package/lib/types/client/locales-ja.d.ts +17 -0
- package/lib/types/client/locales-ko.d.ts +9 -0
- package/lib/types/client/locales-nl.d.ts +16 -0
- package/lib/types/client/locales-pl.d.ts +19 -0
- package/lib/types/client/locales-pt.d.ts +1 -0
- package/lib/types/client/locales-ru.d.ts +13 -0
- package/lib/types/client/locales-sv.d.ts +1 -0
- package/lib/types/client/locales-th.d.ts +17 -0
- package/lib/types/client/locales-tr.d.ts +17 -0
- package/lib/types/client/locales-vi.d.ts +17 -0
- package/lib/types/client/locales-zh-HK.d.ts +32 -0
- package/lib/types/client/locales-zh-MO.d.ts +32 -0
- package/lib/types/client/locales-zh-TW.d.ts +32 -0
- package/lib/types/client/locales.d.ts +452 -0
- package/lib/types/client/markdown-html.d.ts +96 -0
- package/lib/types/client/markdown-images.d.ts +45 -0
- package/lib/types/client/markdown-labels.d.ts +38 -0
- package/lib/types/client/md-toc.d.ts +4 -0
- package/lib/types/client/mermaid-blocks.d.ts +47 -0
- package/lib/types/client/mermaid-sanitize.d.ts +2 -0
- package/lib/types/client/mermaid.d.ts +12 -0
- package/lib/types/client/open-when-sized.d.ts +20 -0
- package/lib/types/client/open-with-settings.d.ts +4 -0
- package/lib/types/client/open-with.d.ts +87 -0
- package/lib/types/client/openpath-intercept.d.ts +85 -0
- package/lib/types/client/paths.d.ts +43 -0
- package/lib/types/client/pdf-types.d.ts +2 -0
- package/lib/types/client/pinned.d.ts +86 -0
- package/lib/types/client/plugin-settings.d.ts +8 -0
- package/lib/types/client/plugins-shared.d.ts +27 -0
- package/lib/types/client/plugins-tabs.d.ts +3 -0
- package/lib/types/client/plugins-viewers.d.ts +3 -0
- package/lib/types/client/prefs.d.ts +41 -0
- package/lib/types/client/produced-files.d.ts +29 -0
- package/lib/types/client/selection-payload.d.ts +27 -0
- package/lib/types/client/service.d.ts +439 -0
- package/lib/types/client/settings-nav-icon.d.ts +19 -0
- package/lib/types/client/shell-presets.d.ts +48 -0
- package/lib/types/client/sidechat-transcript.d.ts +103 -0
- package/lib/types/client/split-pane.d.ts +36 -0
- package/lib/types/client/state.d.ts +432 -0
- package/lib/types/client/subagent-detect.d.ts +54 -0
- package/lib/types/client/subagent-jobs.d.ts +63 -0
- package/lib/types/client/tab-content-memo.d.ts +40 -0
- package/lib/types/client/terminal-font.d.ts +78 -0
- package/lib/types/client/terminal-links.d.ts +119 -0
- package/lib/types/client/theme.d.ts +45 -0
- package/lib/types/client/titlebar-strip.d.ts +23 -0
- package/lib/types/client/upload.d.ts +69 -0
- package/lib/types/client/wco.d.ts +47 -0
- package/lib/types/config.d.ts +64 -0
- package/lib/types/context-types.d.ts +605 -0
- package/lib/types/fs-operations.d.ts +28 -0
- package/lib/types/fs-search.d.ts +24 -0
- package/lib/types/fs-tree.d.ts +51 -0
- package/lib/types/git.d.ts +124 -0
- package/lib/types/html-route.d.ts +59 -0
- package/lib/types/index.d.ts +47 -0
- package/lib/types/invariant.d.ts +15 -0
- package/lib/types/jobs-routes.d.ts +45 -0
- package/lib/types/open-external.d.ts +24 -0
- package/lib/types/path-security.d.ts +20 -0
- package/lib/types/prefs-shared.d.ts +236 -0
- package/lib/types/pty-deps.d.ts +78 -0
- package/lib/types/pty-manager.d.ts +168 -0
- package/lib/types/sidechat-core.d.ts +172 -0
- package/lib/types/sidechat-routes.d.ts +30 -0
- package/lib/types/subagent-activity.d.ts +44 -0
- package/lib/types/subagent-live-route.d.ts +44 -0
- package/lib/types/tools.d.ts +34 -0
- package/lib/types/trust-fence.d.ts +24 -0
- package/lib/types/wire.d.ts +38 -0
- package/package.json +172 -0
- package/scripts/install.ps1 +306 -0
- package/scripts/install.sh +287 -0
- package/src/agent-opens.ts +265 -0
- package/src/agent-pty.ts +527 -0
- package/src/browser-probe.ts +26 -0
- package/src/bundle-route.ts +129 -0
- package/src/client/BrowserView.tsx +289 -0
- package/src/client/DiffTab.tsx +114 -0
- package/src/client/DiffView.tsx +305 -0
- package/src/client/EditorHost.tsx +558 -0
- package/src/client/FileTree.tsx +669 -0
- package/src/client/FreeWindow.tsx +337 -0
- package/src/client/GitView.tsx +717 -0
- package/src/client/MarkdownHtml.tsx +296 -0
- package/src/client/OrphanedTab.tsx +27 -0
- package/src/client/PdfView.tsx +110 -0
- package/src/client/RenderBoundary.tsx +49 -0
- package/src/client/SandboxStatusBar.tsx +60 -0
- package/src/client/SideCardSection.module.css +960 -0
- package/src/client/SideCardSection.tsx +1163 -0
- package/src/client/SideChatView.module.css +437 -0
- package/src/client/SideChatView.tsx +645 -0
- package/src/client/Sidebar.tsx +1370 -0
- package/src/client/SubagentView.module.css +547 -0
- package/src/client/SubagentView.tsx +883 -0
- package/src/client/TabBar.tsx +365 -0
- package/src/client/TerminalView.tsx +424 -0
- package/src/client/TextEditor.tsx +468 -0
- package/src/client/TreePanel.tsx +275 -0
- package/src/client/UploadOverlay.tsx +62 -0
- package/src/client/add-plugin-modal.tsx +199 -0
- package/src/client/api.ts +357 -0
- package/src/client/binary-download.tsx +23 -0
- package/src/client/breakpoints.ts +61 -0
- package/src/client/browser.ts +167 -0
- package/src/client/builtins/index.ts +37 -0
- package/src/client/builtins/tabs.tsx +340 -0
- package/src/client/builtins/viewers.tsx +125 -0
- package/src/client/chunk-loader.ts +358 -0
- package/src/client/chunks/editor.tsx +9 -0
- package/src/client/chunks/mermaid.tsx +10 -0
- package/src/client/chunks/terminal.tsx +9 -0
- package/src/client/cm-themes.ts +129 -0
- package/src/client/conversation-draft.ts +29 -0
- package/src/client/css-modules.d.ts +5 -0
- package/src/client/desktop-env.ts +72 -0
- package/src/client/editor-load.ts +92 -0
- package/src/client/frame-batcher.ts +56 -0
- package/src/client/icons.tsx +152 -0
- package/src/client/image-types.ts +8 -0
- package/src/client/ime-guard.ts +47 -0
- package/src/client/index.tsx +427 -0
- package/src/client/intercept.tsx +188 -0
- package/src/client/lang.ts +225 -0
- package/src/client/layout-push.ts +24 -0
- package/src/client/layout.css +90 -0
- package/src/client/lazy-chunk.tsx +89 -0
- package/src/client/link-intercept.ts +73 -0
- package/src/client/locales-ar.ts +368 -0
- package/src/client/locales-de.ts +353 -0
- package/src/client/locales-fr.ts +360 -0
- package/src/client/locales-hi.ts +367 -0
- package/src/client/locales-id.ts +365 -0
- package/src/client/locales-it.ts +358 -0
- package/src/client/locales-ja.ts +367 -0
- package/src/client/locales-ko.ts +359 -0
- package/src/client/locales-nl.ts +365 -0
- package/src/client/locales-pl.ts +369 -0
- package/src/client/locales-pt.ts +350 -0
- package/src/client/locales-ru.ts +365 -0
- package/src/client/locales-sv.ts +350 -0
- package/src/client/locales-th.ts +367 -0
- package/src/client/locales-tr.ts +367 -0
- package/src/client/locales-vi.ts +367 -0
- package/src/client/locales-zh-HK.ts +382 -0
- package/src/client/locales-zh-MO.ts +382 -0
- package/src/client/locales-zh-TW.ts +382 -0
- package/src/client/locales.ts +894 -0
- package/src/client/markdown-html.ts +331 -0
- package/src/client/markdown-images.ts +137 -0
- package/src/client/markdown-labels.tsx +52 -0
- package/src/client/md-toc.tsx +127 -0
- package/src/client/mermaid-blocks.ts +110 -0
- package/src/client/mermaid-sanitize.ts +93 -0
- package/src/client/mermaid.tsx +406 -0
- package/src/client/open-when-sized.ts +43 -0
- package/src/client/open-with-settings.tsx +135 -0
- package/src/client/open-with.ts +221 -0
- package/src/client/openpath-intercept.ts +143 -0
- package/src/client/paths.ts +63 -0
- package/src/client/pdf-types.ts +4 -0
- package/src/client/pinned.ts +183 -0
- package/src/client/plugin-settings.ts +43 -0
- package/src/client/plugins-shared.ts +29 -0
- package/src/client/plugins-tabs.ts +96 -0
- package/src/client/plugins-viewers.ts +30 -0
- package/src/client/prefs.ts +210 -0
- package/src/client/produced-files.ts +120 -0
- package/src/client/selection-payload.ts +86 -0
- package/src/client/service.ts +859 -0
- package/src/client/settings-nav-icon.ts +45 -0
- package/src/client/shell-presets.ts +83 -0
- package/src/client/sidebar.module.css +2788 -0
- package/src/client/sidechat-transcript.ts +321 -0
- package/src/client/split-pane.tsx +331 -0
- package/src/client/state.ts +1497 -0
- package/src/client/subagent-detect.ts +137 -0
- package/src/client/subagent-jobs.ts +161 -0
- package/src/client/tab-content-memo.ts +55 -0
- package/src/client/terminal-font.ts +224 -0
- package/src/client/terminal-links.ts +194 -0
- package/src/client/theme.ts +110 -0
- package/src/client/titlebar-strip.ts +37 -0
- package/src/client/upload.ts +187 -0
- package/src/client/wco.ts +120 -0
- package/src/config.ts +154 -0
- package/src/context-types.ts +611 -0
- package/src/fs-operations.ts +95 -0
- package/src/fs-search.ts +113 -0
- package/src/fs-tree.ts +158 -0
- package/src/git.ts +446 -0
- package/src/html-route.ts +106 -0
- package/src/index.ts +1291 -0
- package/src/invariant.ts +32 -0
- package/src/jobs-routes.ts +257 -0
- package/src/open-external.ts +90 -0
- package/src/path-security.ts +75 -0
- package/src/prefs-shared.ts +280 -0
- package/src/pty-deps.ts +240 -0
- package/src/pty-manager.ts +376 -0
- package/src/sidechat-core.ts +468 -0
- package/src/sidechat-routes.ts +344 -0
- package/src/subagent-activity.ts +91 -0
- package/src/subagent-live-route.ts +93 -0
- package/src/tools.ts +482 -0
- package/src/trust-fence.ts +84 -0
- package/src/wire.ts +100 -0
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One free window: a tab dragged out of the workbench floating over the
|
|
3
|
+
* conversation area at viewport coordinates (rendered inside the panel host,
|
|
4
|
+
* so desktop-shell transforms can never hijack its fixed containing block).
|
|
5
|
+
*
|
|
6
|
+
* The header drags the window with the panel-resize pattern — pointer
|
|
7
|
+
* capture + per-frame direct DOM writes + a store commit on release — and
|
|
8
|
+
* doubling as the DOCK-BACK gesture: while the pointer is over a workbench
|
|
9
|
+
* pane ([data-dsh-pane], either panel), that pane highlights live and
|
|
10
|
+
* releasing docks the tab into it (center merge); releasing anywhere else
|
|
11
|
+
* just moves the window. The SE corner resizes, any press raises (the
|
|
12
|
+
* floats array's order is the stacking order), the header right-click menu
|
|
13
|
+
* and the X button dock / close. The tab content reuses the regular tab
|
|
14
|
+
* renderer, so every tab type (terminal, editor, plugin tabs) floats
|
|
15
|
+
* unchanged.
|
|
16
|
+
*/
|
|
17
|
+
import { useEffect, useRef, useState, type ReactNode } from 'react'
|
|
18
|
+
import clsx from 'clsx'
|
|
19
|
+
import { IconCloseFill14, Menu } from '@deepseek-ai/dsh-client-ui-primitives'
|
|
20
|
+
import type { FloatWindow, SidebarTab } from './state.ts'
|
|
21
|
+
import { FLOAT_MIN_H, FLOAT_MIN_W } from './state.ts'
|
|
22
|
+
import { t } from './locales.ts'
|
|
23
|
+
import css from './sidebar.module.css'
|
|
24
|
+
|
|
25
|
+
/** A drag's mutable bookkeeping (start geometry + the last applied values). */
|
|
26
|
+
interface DragState {
|
|
27
|
+
/** Which handle owns the drag (the abort path reads it from this ref —
|
|
28
|
+
* the React `dragging` state lags a render behind pointerdown). */
|
|
29
|
+
mode: 'move' | 'resize'
|
|
30
|
+
pointerX: number
|
|
31
|
+
pointerY: number
|
|
32
|
+
startX: number
|
|
33
|
+
startY: number
|
|
34
|
+
startW: number
|
|
35
|
+
startH: number
|
|
36
|
+
/** Last geometry actually written to the DOM (the cancel-path fallback). */
|
|
37
|
+
applied: { x: number; y: number; w: number; h: number }
|
|
38
|
+
/** Settled after the final pointer event (the up position wins over the
|
|
39
|
+
* rAF-pending value — a fast flick coalesces its tail into the up event). */
|
|
40
|
+
committed: boolean
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** The pane under a viewport point, if any (rect hit-test; the dragged
|
|
44
|
+
* window itself is not a pane, so it cannot shadow the targets). */
|
|
45
|
+
function paneAt(x: number, y: number): HTMLElement | null {
|
|
46
|
+
for (const pane of document.querySelectorAll<HTMLElement>('[data-dsh-pane]')) {
|
|
47
|
+
const rect = pane.getBoundingClientRect()
|
|
48
|
+
if (rect.width === 0 || rect.height === 0) continue
|
|
49
|
+
if (x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom) return pane
|
|
50
|
+
}
|
|
51
|
+
return null
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Pointer-capture helpers tolerant of environments without the API (jsdom
|
|
55
|
+
* lacks setPointerCapture — component tests dispatch plain MouseEvents, so
|
|
56
|
+
* the optional calls keep them driving the drag; real browsers always have
|
|
57
|
+
* it and a missing pointerId can never occur there). */
|
|
58
|
+
const capturePointer = (element: HTMLElement, pointerId: number): void => {
|
|
59
|
+
element.setPointerCapture?.(pointerId)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const releasePointer = (element: HTMLElement, pointerId: number): void => {
|
|
63
|
+
element.releasePointerCapture?.(pointerId)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Whether the element holds the pointer (assumed true without the API). */
|
|
67
|
+
const holdsPointer = (element: HTMLElement, pointerId: number): boolean => {
|
|
68
|
+
return element.hasPointerCapture?.(pointerId) !== false
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function FreeWindow(props: {
|
|
72
|
+
float: FloatWindow
|
|
73
|
+
renderTab: (tab: SidebarTab, active: boolean, paneId: string) => ReactNode
|
|
74
|
+
getTabIcon?: (tab: SidebarTab) => ReactNode
|
|
75
|
+
onRaise: () => void
|
|
76
|
+
onMove: (x: number, y: number) => void
|
|
77
|
+
onResize: (w: number, h: number) => void
|
|
78
|
+
/** Dock the tab back; the pane id, or null for the active pane. */
|
|
79
|
+
onDock: (paneId: string | null) => void
|
|
80
|
+
onClose: () => void
|
|
81
|
+
}) {
|
|
82
|
+
const { float, renderTab, getTabIcon, onRaise, onMove, onResize, onDock, onClose } = props
|
|
83
|
+
const rootRef = useRef<HTMLDivElement>(null)
|
|
84
|
+
const dragRef = useRef<DragState | null>(null)
|
|
85
|
+
// The pane currently highlighted as the dock target (a direct DOM write:
|
|
86
|
+
// per-frame React state would re-render every mounted tab at drag cadence).
|
|
87
|
+
const dockTargetRef = useRef<HTMLElement | null>(null)
|
|
88
|
+
// rAF coalescing: pointermove outruns the display, one write per frame.
|
|
89
|
+
const frameRef = useRef<number | null>(null)
|
|
90
|
+
const pendingRef = useRef<{ x: number; y: number; w: number; h: number } | null>(null)
|
|
91
|
+
const [dragging, setDragging] = useState<'move' | 'resize' | null>(null)
|
|
92
|
+
const [menu, setMenu] = useState<{ x: number; y: number } | null>(null)
|
|
93
|
+
|
|
94
|
+
useEffect(() => () => {
|
|
95
|
+
if (frameRef.current !== null) cancelAnimationFrame(frameRef.current)
|
|
96
|
+
dockTargetRef.current?.removeAttribute('data-dsh-float-dock-over')
|
|
97
|
+
}, [])
|
|
98
|
+
|
|
99
|
+
/** Apply the pending geometry to the DOM (one rAF per frame at most). */
|
|
100
|
+
const scheduleApply = (geo: { x: number; y: number; w: number; h: number }): void => {
|
|
101
|
+
pendingRef.current = geo
|
|
102
|
+
if (frameRef.current !== null) return
|
|
103
|
+
frameRef.current = requestAnimationFrame(() => {
|
|
104
|
+
frameRef.current = null
|
|
105
|
+
const pending = pendingRef.current
|
|
106
|
+
const drag = dragRef.current
|
|
107
|
+
const root = rootRef.current
|
|
108
|
+
if (pending === null || drag === null || root === null) return
|
|
109
|
+
drag.applied = pending
|
|
110
|
+
root.style.left = `${pending.x}px`
|
|
111
|
+
root.style.top = `${pending.y}px`
|
|
112
|
+
root.style.width = `${pending.w}px`
|
|
113
|
+
root.style.height = `${pending.h}px`
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Flush the pending frame synchronously (the release path's last write). */
|
|
118
|
+
const flushNow = (): void => {
|
|
119
|
+
const pending = pendingRef.current
|
|
120
|
+
const drag = dragRef.current
|
|
121
|
+
const root = rootRef.current
|
|
122
|
+
if (pending === null || drag === null || root === null) return
|
|
123
|
+
if (frameRef.current !== null) {
|
|
124
|
+
cancelAnimationFrame(frameRef.current)
|
|
125
|
+
frameRef.current = null
|
|
126
|
+
}
|
|
127
|
+
pendingRef.current = null
|
|
128
|
+
drag.applied = pending
|
|
129
|
+
root.style.left = `${pending.x}px`
|
|
130
|
+
root.style.top = `${pending.y}px`
|
|
131
|
+
root.style.width = `${pending.w}px`
|
|
132
|
+
root.style.height = `${pending.h}px`
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const clearDockHighlight = (): void => {
|
|
136
|
+
dockTargetRef.current?.removeAttribute('data-dsh-float-dock-over')
|
|
137
|
+
dockTargetRef.current = null
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Release a drag: `pane` (when set) docks instead of moving. */
|
|
141
|
+
const finishDrag = (mode: 'move' | 'resize', geo: { x: number; y: number; w: number; h: number }, pane: HTMLElement | null): void => {
|
|
142
|
+
const drag = dragRef.current
|
|
143
|
+
if (drag === null || drag.committed) return
|
|
144
|
+
drag.committed = true
|
|
145
|
+
dragRef.current = null
|
|
146
|
+
setDragging(null)
|
|
147
|
+
const target = pane ?? dockTargetRef.current
|
|
148
|
+
clearDockHighlight()
|
|
149
|
+
if (mode === 'move' && target !== null) {
|
|
150
|
+
onDock(target.getAttribute('data-dsh-pane'))
|
|
151
|
+
} else if (mode === 'move') {
|
|
152
|
+
onMove(geo.x, geo.y)
|
|
153
|
+
} else {
|
|
154
|
+
onResize(geo.w, geo.h)
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Cancel-path settle (pointercancel / lostpointercapture): the last
|
|
159
|
+
* APPLIED geometry is the user-visible truth — commit it, never roll back
|
|
160
|
+
* (the panel drags' issue-#247 semantics). The mode comes from the ref —
|
|
161
|
+
* the React `dragging` state can still be null when the cancel lands
|
|
162
|
+
* before the pointerdown re-render commits. */
|
|
163
|
+
const abortDrag = (): void => {
|
|
164
|
+
const drag = dragRef.current
|
|
165
|
+
if (drag === null || drag.committed) return
|
|
166
|
+
flushNow()
|
|
167
|
+
finishDrag(drag.mode, drag.applied, null)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const clampMove = (x: number, y: number): { x: number; y: number; w: number; h: number } => {
|
|
171
|
+
const vw = window.innerWidth
|
|
172
|
+
const vh = window.innerHeight
|
|
173
|
+
return {
|
|
174
|
+
x: Math.min(Math.max(x, 0), Math.max(0, vw - float.w)),
|
|
175
|
+
y: Math.min(Math.max(y, 0), Math.max(0, vh - float.h)),
|
|
176
|
+
w: float.w,
|
|
177
|
+
h: float.h,
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const clampResize = (w: number, h: number): { x: number; y: number; w: number; h: number } => {
|
|
182
|
+
const vw = window.innerWidth
|
|
183
|
+
const vh = window.innerHeight
|
|
184
|
+
return {
|
|
185
|
+
x: float.x,
|
|
186
|
+
y: float.y,
|
|
187
|
+
w: Math.round(Math.min(Math.max(w, FLOAT_MIN_W), Math.max(FLOAT_MIN_W, vw - float.x))),
|
|
188
|
+
h: Math.round(Math.min(Math.max(h, FLOAT_MIN_H), Math.max(FLOAT_MIN_H, vh - float.y))),
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return (
|
|
193
|
+
<div
|
|
194
|
+
ref={rootRef}
|
|
195
|
+
className={clsx(css.floatWindow, dragging !== null && css.floatWindowDragging)}
|
|
196
|
+
data-dsh-float-window
|
|
197
|
+
data-dsh-float-id={float.id}
|
|
198
|
+
style={{ left: float.x, top: float.y, width: float.w, height: float.h }}
|
|
199
|
+
onPointerDown={() => { onRaise() }}
|
|
200
|
+
>
|
|
201
|
+
<div
|
|
202
|
+
className={css.floatHeader}
|
|
203
|
+
onPointerDown={(event) => {
|
|
204
|
+
if (event.button !== 0) return
|
|
205
|
+
// Portaled overlays (the header Menu) live at document.body in the
|
|
206
|
+
// DOM, yet their events still bubble through the React tree into
|
|
207
|
+
// this handler — without the containment guard a menu-row click
|
|
208
|
+
// would start a drag, preventDefault (killing the row's compat
|
|
209
|
+
// mousedown) and capture the pointer (retargeting the release at
|
|
210
|
+
// the header, so the row's click never fires). Buttons inside the
|
|
211
|
+
// header (the close X) need the same exclusion: a capture here
|
|
212
|
+
// swallows their click.
|
|
213
|
+
if (!(event.target instanceof Node) || !event.currentTarget.contains(event.target)) return
|
|
214
|
+
if (event.target instanceof Element && event.target.closest('button') !== null) return
|
|
215
|
+
event.preventDefault()
|
|
216
|
+
capturePointer(event.currentTarget, event.pointerId)
|
|
217
|
+
dragRef.current = {
|
|
218
|
+
mode: 'move',
|
|
219
|
+
pointerX: event.clientX,
|
|
220
|
+
pointerY: event.clientY,
|
|
221
|
+
startX: float.x,
|
|
222
|
+
startY: float.y,
|
|
223
|
+
startW: float.w,
|
|
224
|
+
startH: float.h,
|
|
225
|
+
applied: { x: float.x, y: float.y, w: float.w, h: float.h },
|
|
226
|
+
committed: false,
|
|
227
|
+
}
|
|
228
|
+
setDragging('move')
|
|
229
|
+
}}
|
|
230
|
+
onPointerMove={(event) => {
|
|
231
|
+
const drag = dragRef.current
|
|
232
|
+
if (drag === null || !holdsPointer(event.currentTarget, event.pointerId)) return
|
|
233
|
+
const geo = clampMove(drag.startX + (event.clientX - drag.pointerX), drag.startY + (event.clientY - drag.pointerY))
|
|
234
|
+
scheduleApply(geo)
|
|
235
|
+
const target = paneAt(event.clientX, event.clientY)
|
|
236
|
+
if (target !== dockTargetRef.current) {
|
|
237
|
+
clearDockHighlight()
|
|
238
|
+
if (target !== null) target.setAttribute('data-dsh-float-dock-over', '')
|
|
239
|
+
dockTargetRef.current = target
|
|
240
|
+
}
|
|
241
|
+
}}
|
|
242
|
+
onPointerUp={(event) => {
|
|
243
|
+
if (dragRef.current === null || !holdsPointer(event.currentTarget, event.pointerId)) return
|
|
244
|
+
releasePointer(event.currentTarget, event.pointerId)
|
|
245
|
+
flushNow()
|
|
246
|
+
const geo = clampMove(
|
|
247
|
+
dragRef.current.startX + (event.clientX - dragRef.current.pointerX),
|
|
248
|
+
dragRef.current.startY + (event.clientY - dragRef.current.pointerY),
|
|
249
|
+
)
|
|
250
|
+
finishDrag('move', geo, paneAt(event.clientX, event.clientY))
|
|
251
|
+
}}
|
|
252
|
+
onPointerCancel={() => { abortDrag() }}
|
|
253
|
+
onLostPointerCapture={() => { abortDrag() }}
|
|
254
|
+
onContextMenu={(event) => {
|
|
255
|
+
event.preventDefault()
|
|
256
|
+
setMenu({ x: event.clientX, y: event.clientY })
|
|
257
|
+
}}
|
|
258
|
+
>
|
|
259
|
+
{getTabIcon?.(float.tab) ?? null}
|
|
260
|
+
<span className={css.floatTitle} title={float.tab.title}>{float.tab.title}</span>
|
|
261
|
+
<button
|
|
262
|
+
type="button"
|
|
263
|
+
className={css.floatClose}
|
|
264
|
+
aria-label={t('close')}
|
|
265
|
+
onClick={(event) => { event.stopPropagation(); onClose() }}
|
|
266
|
+
>
|
|
267
|
+
<IconCloseFill14 />
|
|
268
|
+
</button>
|
|
269
|
+
<Menu
|
|
270
|
+
open={menu !== null}
|
|
271
|
+
onClose={() => { setMenu(null) }}
|
|
272
|
+
items={[
|
|
273
|
+
{ id: 'dock', label: t('dockToSidebar') },
|
|
274
|
+
{ id: 'close', label: t('close') },
|
|
275
|
+
]}
|
|
276
|
+
onSelect={(id) => {
|
|
277
|
+
setMenu(null)
|
|
278
|
+
if (id === 'dock') onDock(null)
|
|
279
|
+
else if (id === 'close') onClose()
|
|
280
|
+
}}
|
|
281
|
+
portal
|
|
282
|
+
align="start"
|
|
283
|
+
getAnchorRect={() => (menu === null ? null : new DOMRect(menu.x, menu.y, 0, 0))}
|
|
284
|
+
anchor={<span />}
|
|
285
|
+
/>
|
|
286
|
+
</div>
|
|
287
|
+
{/* The tab cell reuses .paneTab (the pane's per-tab wrapper): the
|
|
288
|
+
column-flex chain (window → .floatContent → .paneTab) is what
|
|
289
|
+
gives the component a definite width via align-stretch — the exact
|
|
290
|
+
sizing contract a tab gets inside a pane. */}
|
|
291
|
+
<div className={css.floatContent}>
|
|
292
|
+
<div className={css.paneTab}>{renderTab(float.tab, true, float.id)}</div>
|
|
293
|
+
</div>
|
|
294
|
+
<div
|
|
295
|
+
className={css.floatResize}
|
|
296
|
+
onPointerDown={(event) => {
|
|
297
|
+
if (event.button !== 0) return
|
|
298
|
+
if (!(event.target instanceof Node) || !event.currentTarget.contains(event.target)) return
|
|
299
|
+
event.preventDefault()
|
|
300
|
+
capturePointer(event.currentTarget, event.pointerId)
|
|
301
|
+
dragRef.current = {
|
|
302
|
+
mode: 'resize',
|
|
303
|
+
pointerX: event.clientX,
|
|
304
|
+
pointerY: event.clientY,
|
|
305
|
+
startX: float.x,
|
|
306
|
+
startY: float.y,
|
|
307
|
+
startW: float.w,
|
|
308
|
+
startH: float.h,
|
|
309
|
+
applied: { x: float.x, y: float.y, w: float.w, h: float.h },
|
|
310
|
+
committed: false,
|
|
311
|
+
}
|
|
312
|
+
setDragging('resize')
|
|
313
|
+
}}
|
|
314
|
+
onPointerMove={(event) => {
|
|
315
|
+
const drag = dragRef.current
|
|
316
|
+
if (drag === null || !holdsPointer(event.currentTarget, event.pointerId)) return
|
|
317
|
+
scheduleApply(clampResize(
|
|
318
|
+
drag.startW + (event.clientX - drag.pointerX),
|
|
319
|
+
drag.startH + (event.clientY - drag.pointerY),
|
|
320
|
+
))
|
|
321
|
+
}}
|
|
322
|
+
onPointerUp={(event) => {
|
|
323
|
+
if (dragRef.current === null || !holdsPointer(event.currentTarget, event.pointerId)) return
|
|
324
|
+
releasePointer(event.currentTarget, event.pointerId)
|
|
325
|
+
flushNow()
|
|
326
|
+
const geo = clampResize(
|
|
327
|
+
dragRef.current.startW + (event.clientX - dragRef.current.pointerX),
|
|
328
|
+
dragRef.current.startH + (event.clientY - dragRef.current.pointerY),
|
|
329
|
+
)
|
|
330
|
+
finishDrag('resize', geo, null)
|
|
331
|
+
}}
|
|
332
|
+
onPointerCancel={() => { abortDrag() }}
|
|
333
|
+
onLostPointerCapture={() => { abortDrag() }}
|
|
334
|
+
/>
|
|
335
|
+
</div>
|
|
336
|
+
)
|
|
337
|
+
}
|