dsh-vscode-mode 0.14.12 → 0.15.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.
@@ -0,0 +1,87 @@
1
+ /** In-page window chrome; closing returns tabs, never closes documents. @author ddj 2026年10月14号 */
2
+ import React from 'react'
3
+ import { createPortal } from 'react-dom'
4
+ import { clampFloat, type FloatRect } from '../state/editorLayout.js'
5
+
6
+ export interface FloatingProps {
7
+ group: string
8
+ owner: string
9
+ rect: FloatRect
10
+ focused: boolean
11
+ layer: number
12
+ onBounds(group: string, rect: FloatRect): void
13
+ onFocus(group: string): void
14
+ onReturn(group: string): void
15
+ children?: React.ReactNode
16
+ }
17
+
18
+ /** @public @author ddj 2026年10月14号 @param props Window geometry and group callbacks. @returns Theme-aware portal with move/resize handles. */
19
+ export function FloatingEditor(props: FloatingProps): React.ReactPortal {
20
+ const latest = React.useRef(props)
21
+ latest.current = props
22
+ const cleanup = React.useRef<(() => void) | null>(null)
23
+ /** @private @author ddj 2026年10月14号 Cancel a gesture on unmount without altering tabs. */
24
+ function cancel(): void { cleanup.current?.(); cleanup.current = null }
25
+ React.useEffect(() => cancel, [])
26
+ React.useEffect(() => {
27
+ /** @private @author ddj 2026年10月14号 Keep title/chrome visible after viewport changes. */
28
+ function resize(): void {
29
+ const current = latest.current
30
+ current.onBounds(current.group, clampFloat(current.rect, window.innerWidth, window.innerHeight))
31
+ }
32
+ window.addEventListener('resize', resize)
33
+ resize()
34
+ return () => window.removeEventListener('resize', resize)
35
+ }, [])
36
+
37
+ /** @private @author ddj 2026年10月14号 @param event Handle press. @param sizing Resize rather than move. */
38
+ function start(event: React.PointerEvent, sizing: boolean): void {
39
+ if (event.button !== 0 || !event.isPrimary) return
40
+ cancel()
41
+ event.preventDefault()
42
+ const initial = latest.current.rect
43
+ const x = event.clientX, y = event.clientY, pointer = event.pointerId
44
+ let frame: number | null = null
45
+ let next = initial
46
+ latest.current.onFocus(props.group)
47
+ /** @private @author ddj 2026年10月14号 Apply at most one geometry change per animation frame. */
48
+ function paint(): void { frame = null; latest.current.onBounds(props.group, next) }
49
+ /** @private @author ddj 2026年10月14号 @param moveEvent Pointer movement for the originating pointer. */
50
+ function move(moveEvent: PointerEvent): void {
51
+ if (moveEvent.pointerId !== pointer) return
52
+ moveEvent.preventDefault()
53
+ const dx = moveEvent.clientX - x, dy = moveEvent.clientY - y
54
+ next = clampFloat(sizing ? { ...initial, width: initial.width + dx, height: initial.height + dy } : { ...initial, left: initial.left + dx, top: initial.top + dy }, window.innerWidth, window.innerHeight)
55
+ if (frame === null) frame = requestAnimationFrame(paint)
56
+ }
57
+ /** @private @author ddj 2026年10月14号 Remove gesture listeners and pending frames. */
58
+ function stop(): void {
59
+ if (frame !== null) { cancelAnimationFrame(frame); paint() }
60
+ window.removeEventListener('pointermove', move, true)
61
+ window.removeEventListener('pointerup', stop, true)
62
+ window.removeEventListener('pointercancel', stop, true)
63
+ window.removeEventListener('blur', stop)
64
+ cleanup.current = null
65
+ }
66
+ cleanup.current = stop
67
+ window.addEventListener('pointermove', move, true)
68
+ window.addEventListener('pointerup', stop, true)
69
+ window.addEventListener('pointercancel', stop, true)
70
+ window.addEventListener('blur', stop)
71
+ }
72
+ /** @private @author ddj 2026年10月14号 @param event Title press. Move window without stealing button presses. */
73
+ function moveStart(event: React.PointerEvent): void { if (!(event.target as Element).closest('button')) start(event, false) }
74
+ /** @private @author ddj 2026年10月14号 @param event Resize grip press. */
75
+ function sizeStart(event: React.PointerEvent): void { start(event, true) }
76
+ /** @private @author ddj 2026年10月14号 Return all tabs to the main group. */
77
+ function returnTabs(): void { latest.current.onReturn(props.group) }
78
+ /** @private @author ddj 2026年10月14号 Bring the group to the foreground. */
79
+ function focus(): void { latest.current.onFocus(props.group) }
80
+ const rect = clampFloat(props.rect, window.innerWidth, window.innerHeight)
81
+ return createPortal(React.createElement('div', { className: 'edrv-floating' + (props.focused ? ' edrv-floating-focused' : ''), 'data-edrv-view': '1', 'data-edrv-float': props.group, 'data-edrv-owner': props.owner, style: { ...rect, zIndex: (props.focused ? 80 : 60) + props.layer }, onPointerDownCapture: focus },
82
+ React.createElement('div', { className: 'edrv-floating-title', onPointerDown: moveStart },
83
+ React.createElement('span', null, '浮动编辑组'), React.createElement('button', { type: 'button', title: '将全部文件移回主编辑区', onClick: returnTabs }, '移回主编辑区'),
84
+ React.createElement('button', { type: 'button', title: '关闭浮窗并将文件移回主编辑区', 'aria-label': '关闭浮窗并移回文件', onClick: returnTabs }, '×')),
85
+ props.children,
86
+ React.createElement('div', { className: 'edrv-floating-grip', title: '拖动调整窗口大小', onPointerDown: sizeStart })), document.body)
87
+ }