dsh-vscode-mode 0.14.13 → 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,130 @@
1
+ /** Independent group views; workbench owns documents, saves and commands. @author ddj 2026年10月14号 */
2
+ import React from 'react'
3
+ import { createViewKeeper, type ViewEditor } from '../state/editorViewState.js'
4
+ import type { Group } from '../state/editorLayout.js'
5
+ import type { TabLike } from '../tabActions.js'
6
+
7
+ export interface PaneProps {
8
+ group: Group
9
+ focused: boolean
10
+ dirty: Record<string, boolean>
11
+ onSelect(group: string, path?: string): void
12
+ onClose(path: string): void
13
+ onMenu(path: string, event: React.MouseEvent): void
14
+ onDrag(path: string, event: React.PointerEvent): void
15
+ consumeClick(): boolean
16
+ children?: React.ReactNode
17
+ extras?: React.ReactNode
18
+ }
19
+
20
+ /** @public @author ddj 2026年10月14号 @param props Group callbacks and file view. @returns Accessible tab strip and drop target. */
21
+ export function EditorPane(props: PaneProps): React.ReactElement {
22
+ const { group } = props
23
+ /** @private @author ddj 2026年10月14号 @param tab File record. @returns Draggable tab with isolated close control. */
24
+ function renderTab(tab: TabLike): React.ReactElement {
25
+ /** @private @author ddj 2026年10月14号 @param event Primary press. Start trusted file drag. */
26
+ function press(event: React.PointerEvent): void { props.onDrag(tab.path, event) }
27
+ /** @private @author ddj 2026年10月14号 Select unless release completed a drag. */
28
+ function select(): void { if (!props.consumeClick()) props.onSelect(group.id, tab.path) }
29
+ /** @private @author ddj 2026年10月14号 @param event Context event. Open file actions. */
30
+ function menu(event: React.MouseEvent): void { event.preventDefault(); event.stopPropagation(); props.onMenu(tab.path, event) }
31
+ /** @private @author ddj 2026年10月14号 @param event Close activation. Never select tab. */
32
+ function close(event: React.MouseEvent): void { event.stopPropagation(); props.onClose(tab.path) }
33
+ /** @private @author ddj 2026年10月14号 @param event Close press. Never start a drag. */
34
+ function stop(event: React.PointerEvent): void { event.stopPropagation() }
35
+ /** @private @author ddj 2026年10月14号 @param event Keyboard activation. */
36
+ function key(event: React.KeyboardEvent): void { if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); props.onSelect(group.id, tab.path) } }
37
+ return React.createElement('div', {
38
+ key: tab.path, 'data-edrv-tab-path': tab.path, role: 'tab', tabIndex: 0, 'aria-selected': tab.path === group.active,
39
+ className: 'edrv-tab' + (tab.path === group.active ? ' edrv-tab-active' : '') + (tab.pinned ? ' edrv-tab-pinned' : ''),
40
+ title: tab.path + (tab.pinned ? '(已固定)' : ''), onPointerDown: press, onClick: select, onContextMenu: menu, onKeyDown: key,
41
+ }, tab.pinned ? React.createElement('span', { className: 'edrv-tab-pin' }, '📌') : null,
42
+ React.createElement('span', { className: 'edrv-group-tab-name' }, tab.path.split(/[\\/]/).pop()),
43
+ props.dirty[tab.path] ? React.createElement('span', { className: 'edrv-tab-star', title: '未保存修改' }, '*') : null,
44
+ tab.pinned ? null : React.createElement('button', { className: 'edrv-tab-x', type: 'button', 'aria-label': '关闭 ' + tab.path, title: '关闭', onPointerDown: stop, onClick: close }, '×'))
45
+ }
46
+ /** @private @author ddj 2026年10月14号 @param event Group background press. Allow an empty main group to receive new files. */
47
+ function focusEmpty(event: React.PointerEvent): void {
48
+ if (!group.active && !props.focused && event.button === 0) props.onSelect(group.id)
49
+ }
50
+ return React.createElement('section', { className: 'edrv-group' + (props.focused ? ' edrv-group-focused' : ''), 'data-edrv-group': group.id, onPointerDown: focusEmpty },
51
+ React.createElement('div', { className: 'edrv-tabs', role: 'tablist', 'data-edrv-group-tabs': group.id }, group.tabs.map(renderTab), props.extras),
52
+ React.createElement('div', { className: 'edrv-group-body', role: 'tabpanel' }, props.children))
53
+ }
54
+
55
+ /** @public @author ddj 2026年10月14号 @param editor View instance. @param scope Workspace. @param path Canonical path. @param model Shared content. @returns Keeper whose disposal never disposes the model. */
56
+ export function bindPaneView(editor: ViewEditor, scope: string, path: string, model: unknown) {
57
+ const keeper = createViewKeeper(editor)
58
+ keeper.bind(scope, path, model)
59
+ return keeper
60
+ }
61
+
62
+ export interface PassiveProps {
63
+ path: string | null
64
+ mount(host: HTMLDivElement, path: string): (() => void) | undefined
65
+ }
66
+
67
+ /** @public @author ddj 2026年10月14号 @param props Workbench-owned mounting contract. @returns Independent visible file view without global command listeners. */
68
+ export function PassiveEditor(props: PassiveProps): React.ReactElement {
69
+ const host = React.useRef<HTMLDivElement>(null)
70
+ React.useLayoutEffect(() => {
71
+ if (host.current && props.path) return props.mount(host.current, props.path)
72
+ }, [props.path, props.mount])
73
+ return React.createElement('div', { ref: host, className: 'edrv-passive-view' })
74
+ }
75
+
76
+ export interface SplitProps {
77
+ axis: 'horizontal' | 'vertical'
78
+ ratio: number
79
+ onRatio(ratio: number): void
80
+ first: React.ReactNode
81
+ second: React.ReactNode
82
+ }
83
+
84
+ /** @public @author ddj 2026年10月14号 @param props Split geometry. @returns Resizable split with keyboard equivalents. */
85
+ export function EditorSplit(props: SplitProps): React.ReactElement {
86
+ const host = React.useRef<HTMLDivElement>(null)
87
+ const clean = React.useRef<(() => void) | null>(null)
88
+ const latest = React.useRef(props)
89
+ latest.current = props
90
+ React.useEffect(() => () => clean.current?.(), [])
91
+ /** @private @author ddj 2026年10月14号 @param event Separator press. Resize only this branch. */
92
+ function start(event: React.PointerEvent): void {
93
+ if (event.button !== 0 || !host.current) return
94
+ clean.current?.()
95
+ event.preventDefault()
96
+ const rect = host.current.getBoundingClientRect(), pointer = event.pointerId
97
+ /** @private @author ddj 2026年10月14号 @param value Pointer movement. Keep both leaves usable. */
98
+ function move(value: PointerEvent): void {
99
+ if (value.pointerId !== pointer) return
100
+ const horizontal = latest.current.axis === 'horizontal'
101
+ const size = horizontal ? rect.width : rect.height
102
+ const min = Math.min(0.45, (horizontal ? 240 : 160) / size)
103
+ const at = horizontal ? value.clientX - rect.left : value.clientY - rect.top
104
+ latest.current.onRatio(Math.max(min, Math.min(1 - min, at / size)))
105
+ }
106
+ /** @private @author ddj 2026年10月14号 Remove resize listeners. */
107
+ function stop(): void {
108
+ window.removeEventListener('pointermove', move)
109
+ window.removeEventListener('pointerup', stop)
110
+ window.removeEventListener('pointercancel', stop)
111
+ window.removeEventListener('blur', stop)
112
+ clean.current = null
113
+ }
114
+ clean.current = stop
115
+ window.addEventListener('pointermove', move)
116
+ window.addEventListener('pointerup', stop)
117
+ window.addEventListener('pointercancel', stop)
118
+ window.addEventListener('blur', stop)
119
+ }
120
+ /** @private @author ddj 2026年10月14号 @param event Keyboard resizing. Arrow keys change ratio. */
121
+ function key(event: React.KeyboardEvent): void {
122
+ if (!['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'].includes(event.key)) return
123
+ event.preventDefault()
124
+ props.onRatio(Math.max(0.1, Math.min(0.9, props.ratio + (event.key === 'ArrowLeft' || event.key === 'ArrowUp' ? -0.05 : 0.05))))
125
+ }
126
+ return React.createElement('div', { ref: host, className: 'edrv-split edrv-split-' + props.axis },
127
+ React.createElement('div', { className: 'edrv-split-child', style: { flex: props.ratio } }, props.first),
128
+ React.createElement('div', { className: 'edrv-split-handle', role: 'separator', tabIndex: 0, 'aria-orientation': props.axis === 'horizontal' ? 'vertical' : 'horizontal', 'aria-valuenow': Math.round(props.ratio * 100), 'aria-valuemin': 10, 'aria-valuemax': 90, onPointerDown: start, onKeyDown: key }),
129
+ React.createElement('div', { className: 'edrv-split-child', style: { flex: 1 - props.ratio } }, props.second))
130
+ }