dsh-explorer 0.1.0 → 0.1.1

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.
@@ -1,6 +1,6 @@
1
1
  import React, { useEffect, useRef } from 'react'
2
- import css from './explorer.module.css'
3
- import type { FsEntry } from './api.ts'
2
+ import css from '../explorer.module.css'
3
+ import type { FsEntry } from '../utils/api.ts'
4
4
 
5
5
  interface ContextMenuProps {
6
6
  x: number
@@ -47,7 +47,6 @@ export const ExplorerContextMenu: React.FC<ContextMenuProps> = ({
47
47
  return () => window.removeEventListener('mousedown', handleOutsideClick)
48
48
  }, [onClose])
49
49
 
50
- // Adjust menu bounds if overflowing screen
51
50
  const top = Math.min(y, window.innerHeight - 260)
52
51
  const left = Math.min(x, window.innerWidth - 200)
53
52
 
@@ -156,4 +155,3 @@ export const ExplorerContextMenu: React.FC<ContextMenuProps> = ({
156
155
  </div>
157
156
  )
158
157
  }
159
-
@@ -0,0 +1,124 @@
1
+ import React, { useEffect, useRef, useState } from 'react'
2
+ import css from '../explorer.module.css'
3
+ import type { FsEntry } from '../utils/api.ts'
4
+ import { renderFileIcon } from '../utils/vscode-theme.tsx'
5
+
6
+ interface ExplorerDeleteModalProps {
7
+ entry: FsEntry
8
+ relativePath: string
9
+ onConfirm: () => Promise<void>
10
+ onClose: () => void
11
+ }
12
+
13
+ export const ExplorerDeleteModal: React.FC<ExplorerDeleteModalProps> = ({
14
+ entry,
15
+ relativePath,
16
+ onConfirm,
17
+ onClose,
18
+ }) => {
19
+ const [loading, setLoading] = useState(false)
20
+ const [error, setError] = useState<string | null>(null)
21
+ const cancelBtnRef = useRef<HTMLButtonElement>(null)
22
+ const modalRef = useRef<HTMLDivElement>(null)
23
+
24
+ useEffect(() => {
25
+ cancelBtnRef.current?.focus()
26
+
27
+ const handleKeyDown = (e: KeyboardEvent) => {
28
+ if (e.key === 'Escape') {
29
+ e.preventDefault()
30
+ onClose()
31
+ }
32
+ }
33
+
34
+ const handleMouseDown = (e: MouseEvent) => {
35
+ if (modalRef.current && !modalRef.current.contains(e.target as Node)) {
36
+ onClose()
37
+ }
38
+ }
39
+
40
+ document.addEventListener('keydown', handleKeyDown)
41
+ document.addEventListener('mousedown', handleMouseDown)
42
+ return () => {
43
+ document.removeEventListener('keydown', handleKeyDown)
44
+ document.removeEventListener('mousedown', handleMouseDown)
45
+ }
46
+ }, [onClose])
47
+
48
+ const handleDelete = async () => {
49
+ if (loading) return
50
+ setLoading(true)
51
+ setError(null)
52
+ try {
53
+ await onConfirm()
54
+ onClose()
55
+ } catch (err: any) {
56
+ setError(err?.message || 'Failed to delete')
57
+ setLoading(false)
58
+ }
59
+ }
60
+
61
+ return (
62
+ <div className={css.modalOverlay}>
63
+ <div ref={modalRef} className={css.modalCard} role="dialog" aria-modal="true">
64
+ <div className={css.modalHeader}>
65
+ <div className={css.modalTitleWrap}>
66
+ <svg width="15" height="15" viewBox="0 0 16 16" fill="currentColor">
67
+ <path d="M6.5 1h3a.5.5 0 0 1 .5.5v1H14a.5.5 0 0 1 0 1h-.6l-.8 11.2A2 2 0 0 1 10.6 16H5.4a2 2 0 0 1-2-1.8L2.6 3.5H2a.5.5 0 0 1 0-1h4v-1a.5.5 0 0 1 .5-.5zM7 2v.5h2V2H7zm-2.4 12.4a1 1 0 0 0 1 .6h5.2a1 1 0 0 0 1-.9L12.4 3.5H3.6l.8 10.9h.2zm2.9-9.9a.5.5 0 0 1 .5.5v7a.5.5 0 0 1-1 0v-7a.5.5 0 0 1 .5-.5zm3 0a.5.5 0 0 1 .5.5v7a.5.5 0 0 1-1 0v-7a.5.5 0 0 1 .5-.5z" />
68
+ </svg>
69
+ <span>{entry.isDir ? 'Delete Folder' : 'Delete File'}</span>
70
+ </div>
71
+ <button className={css.optionsIconBtn} title="Close" onClick={onClose}>
72
+ <svg width="12" height="12" viewBox="0 0 16 16" fill="currentColor">
73
+ <path d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z" />
74
+ </svg>
75
+ </button>
76
+ </div>
77
+
78
+ <div className={css.modalBody}>
79
+ <div>
80
+ Are you sure you want to permanently delete this {entry.isDir ? 'directory' : 'file'}?
81
+ </div>
82
+
83
+ <div className={css.modalItemPreview}>
84
+ <span className={css.iconWrap}>
85
+ {renderFileIcon(entry.name, entry.isDir, false, 16)}
86
+ </span>
87
+ <div className={css.modalItemInfo}>
88
+ <div className={css.modalItemName}>{entry.name}</div>
89
+ <div className={css.modalItemPath}>{relativePath}</div>
90
+ </div>
91
+ </div>
92
+
93
+ {entry.isDir && (
94
+ <div className={css.modalWarning}>
95
+ Warning: All nested files and subdirectories inside this folder will be permanently removed.
96
+ </div>
97
+ )}
98
+
99
+ {error && <div className={css.modalErrorBanner}>{error}</div>}
100
+ </div>
101
+
102
+ <div className={css.modalFooter}>
103
+ <button
104
+ ref={cancelBtnRef}
105
+ type="button"
106
+ className={css.modalBtnCancel}
107
+ onClick={onClose}
108
+ disabled={loading}
109
+ >
110
+ Cancel
111
+ </button>
112
+ <button
113
+ type="button"
114
+ className={css.modalBtnDanger}
115
+ onClick={handleDelete}
116
+ disabled={loading}
117
+ >
118
+ {loading ? 'Deleting...' : 'Delete'}
119
+ </button>
120
+ </div>
121
+ </div>
122
+ </div>
123
+ )
124
+ }
@@ -0,0 +1,125 @@
1
+ import React, { useEffect, useRef } from 'react'
2
+ import css from '../explorer.module.css'
3
+
4
+ interface ExplorerEmptyContextMenuProps {
5
+ x: number
6
+ y: number
7
+ onClose: () => void
8
+ onNewFile: () => void
9
+ onNewFolder: () => void
10
+ onToggleSearch: () => void
11
+ onExpandAll: () => void
12
+ onCollapseAll: () => void
13
+ onRefresh: () => void
14
+ onOpenOptions: () => void
15
+ }
16
+
17
+ export const ExplorerEmptyContextMenu: React.FC<ExplorerEmptyContextMenuProps> = ({
18
+ x,
19
+ y,
20
+ onClose,
21
+ onNewFile,
22
+ onNewFolder,
23
+ onToggleSearch,
24
+ onExpandAll,
25
+ onCollapseAll,
26
+ onRefresh,
27
+ onOpenOptions,
28
+ }) => {
29
+ const menuRef = useRef<HTMLDivElement>(null)
30
+
31
+ useEffect(() => {
32
+ const handleOutsideClick = (e: MouseEvent) => {
33
+ if (menuRef.current && !menuRef.current.contains(e.target as Node)) {
34
+ onClose()
35
+ }
36
+ }
37
+ const handleKeyDown = (e: KeyboardEvent) => {
38
+ if (e.key === 'Escape') {
39
+ onClose()
40
+ }
41
+ }
42
+ window.addEventListener('mousedown', handleOutsideClick)
43
+ window.addEventListener('keydown', handleKeyDown)
44
+ return () => {
45
+ window.removeEventListener('mousedown', handleOutsideClick)
46
+ window.removeEventListener('keydown', handleKeyDown)
47
+ }
48
+ }, [onClose])
49
+
50
+ const top = Math.min(y, window.innerHeight - 250)
51
+ const left = Math.min(x, window.innerWidth - 200)
52
+
53
+ return (
54
+ <div ref={menuRef} className={css.contextMenu} style={{ top, left }}>
55
+ <div
56
+ className={css.menuItem}
57
+ onClick={() => {
58
+ onNewFile()
59
+ onClose()
60
+ }}
61
+ >
62
+ <span>New File...</span>
63
+ </div>
64
+ <div
65
+ className={css.menuItem}
66
+ onClick={() => {
67
+ onNewFolder()
68
+ onClose()
69
+ }}
70
+ >
71
+ <span>New Folder...</span>
72
+ </div>
73
+ <div className={css.menuDivider} />
74
+ <div
75
+ className={css.menuItem}
76
+ onClick={() => {
77
+ onToggleSearch()
78
+ onClose()
79
+ }}
80
+ >
81
+ <span>Search Files...</span>
82
+ <span className={css.menuShortcut}>Ctrl+F</span>
83
+ </div>
84
+ <div className={css.menuDivider} />
85
+ <div
86
+ className={css.menuItem}
87
+ onClick={() => {
88
+ onExpandAll()
89
+ onClose()
90
+ }}
91
+ >
92
+ <span>Expand All</span>
93
+ </div>
94
+ <div
95
+ className={css.menuItem}
96
+ onClick={() => {
97
+ onCollapseAll()
98
+ onClose()
99
+ }}
100
+ >
101
+ <span>Collapse All</span>
102
+ </div>
103
+ <div className={css.menuDivider} />
104
+ <div
105
+ className={css.menuItem}
106
+ onClick={() => {
107
+ onRefresh()
108
+ onClose()
109
+ }}
110
+ >
111
+ <span>Refresh</span>
112
+ </div>
113
+ <div className={css.menuDivider} />
114
+ <div
115
+ className={css.menuItem}
116
+ onClick={() => {
117
+ onOpenOptions()
118
+ onClose()
119
+ }}
120
+ >
121
+ <span>Display Preferences...</span>
122
+ </div>
123
+ </div>
124
+ )
125
+ }
@@ -1,5 +1,5 @@
1
1
  import React from 'react'
2
- import css from './explorer.module.css'
2
+ import css from '../explorer.module.css'
3
3
 
4
4
  interface ExplorerHeaderProps {
5
5
  title: string
@@ -10,6 +10,7 @@ interface ExplorerHeaderProps {
10
10
  onNewFile: () => void
11
11
  onNewFolder: () => void
12
12
  onRefresh: () => void
13
+ onExpandAll: () => void
13
14
  onCollapseAll: () => void
14
15
  }
15
16
 
@@ -22,6 +23,7 @@ export const ExplorerHeader: React.FC<ExplorerHeaderProps> = ({
22
23
  onNewFile,
23
24
  onNewFolder,
24
25
  onRefresh,
26
+ onExpandAll,
25
27
  onCollapseAll,
26
28
  }) => {
27
29
  return (
@@ -32,39 +34,44 @@ export const ExplorerHeader: React.FC<ExplorerHeaderProps> = ({
32
34
  <div className={css.headerActions}>
33
35
  <button
34
36
  className={`${css.iconBtn} ${searchOpen ? css.iconBtnActive : ''}`}
35
- title="Filter files"
37
+ title="Search files and folders"
36
38
  onClick={onToggleSearch}
37
39
  >
38
- <svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor">
40
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
39
41
  <path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z"/>
40
42
  </svg>
41
43
  </button>
42
44
  <button className={css.iconBtn} title="New File" onClick={onNewFile}>
43
- <svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor">
45
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
44
46
  <path d="M9 1H4a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V6L9 1zm0 1.5L12.5 6H9V2.5zM8 8.5V11h1V8.5h2.5v-1H9V5H8v2.5H5.5v1H8z"/>
45
47
  </svg>
46
48
  </button>
47
49
  <button className={css.iconBtn} title="New Folder" onClick={onNewFolder}>
48
- <svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor">
50
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
49
51
  <path d="M7 2l2 2h5a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h5zm2.5 6.5H8V6H7v2.5H4.5v1H7V12h1V9.5h2.5v-1z"/>
50
52
  </svg>
51
53
  </button>
52
54
  <button className={css.iconBtn} title="Refresh" onClick={onRefresh}>
53
- <svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor">
55
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
54
56
  <path d="M13.65 2.35A7.958 7.958 0 0 0 8 0a8 8 0 1 0 8 8h-2a6 6 0 1 1-1.76-4.24l-2.24 2.24H16V0l-2.35 2.35z"/>
55
57
  </svg>
56
58
  </button>
57
- <button className={css.iconBtn} title="Collapse All Folders" onClick={onCollapseAll}>
58
- <svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor">
59
- <path d="M1 3.5l1.5-1.5L8 7.5 13.5 2 15 3.5 8 10.5 1 3.5zM1 9.5l1.5-1.5L8 13.5 13.5 8 15 9.5 8 16.5 1 9.5z"/>
59
+ <button className={css.iconBtn} title="Expand All" onClick={onExpandAll}>
60
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
61
+ <path d="M4.646 5.854a.5.5 0 0 1 0-.708l3-3a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1-.708.708L8 3.207 5.354 5.854a.5.5 0 0 1-.708 0zm0 4.292a.5.5 0 0 0 0 .708l3 3a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8 12.793l-2.646-2.647a.5.5 0 0 0-.708 0z"/>
62
+ </svg>
63
+ </button>
64
+ <button className={css.iconBtn} title="Collapse All" onClick={onCollapseAll}>
65
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
66
+ <path d="M4.646 2.854a.5.5 0 0 0 0 .708l3 3a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8 5.293 5.354 2.854a.5.5 0 0 0-.708 0zm0 10.292a.5.5 0 0 1 0-.708l3-3a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1-.708.708L8 10.707l-2.646 2.647a.5.5 0 0 1-.708 0z"/>
60
67
  </svg>
61
68
  </button>
62
69
  <button
63
70
  className={`${css.iconBtn} ${optionsOpen ? css.iconBtnActive : ''}`}
64
- title="Explorer Options"
71
+ title="Explorer display preferences"
65
72
  onClick={onToggleOptions}
66
73
  >
67
- <svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor">
74
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
68
75
  <path d="M9.405 1.05c-.413-1.4-2.397-1.4-2.81 0l-.1.34a1.464 1.464 0 0 1-2.105.872l-.31-.17c-1.283-.698-2.686.705-1.987 1.987l.169.311c.446.82.023 1.841-.872 2.105l-.34.1c-1.4.413-1.4 2.397 0 2.81l.34.1a1.464 1.464 0 0 1 .872 2.105l-.17.31c-.698 1.283.705 2.686 1.987 1.987l.311-.169a1.464 1.464 0 0 1 2.105.872l.1.34c.413 1.4 2.397 1.4 2.81 0l.1-.34a1.464 1.464 0 0 1 2.105-.872l.31.17c1.283.698 2.686-.705 1.987-1.987l-.169-.311a1.464 1.464 0 0 1 .872-2.105l.34-.1c1.4-.413 1.4-2.397 0-2.81l-.34-.1a1.464 1.464 0 0 1-.872-2.105l.17-.31c.698-1.283-.705-2.686-1.987-1.987l-.311.169a1.464 1.464 0 0 1-2.105-.872l-.1-.34zM8 10.93a2.929 2.929 0 1 1 0-5.86 2.929 2.929 0 0 1 0 5.858z"/>
69
76
  </svg>
70
77
  </button>
@@ -72,4 +79,3 @@ export const ExplorerHeader: React.FC<ExplorerHeaderProps> = ({
72
79
  </div>
73
80
  )
74
81
  }
75
-
@@ -1,6 +1,6 @@
1
1
  import React, { useEffect, useRef } from 'react'
2
- import css from './explorer.module.css'
3
- import type { ExplorerOptions, ExplorerDensity, SingleClickAction } from './options.ts'
2
+ import css from '../explorer.module.css'
3
+ import type { ExplorerOptions, ExplorerDensity, SingleClickAction } from '../utils/options.ts'
4
4
 
5
5
  interface ExplorerOptionsModalProps {
6
6
  options: ExplorerOptions
@@ -17,7 +17,6 @@ export const ExplorerOptionsModal: React.FC<ExplorerOptionsModalProps> = ({
17
17
  }) => {
18
18
  const popoverRef = useRef<HTMLDivElement>(null)
19
19
 
20
- // Close on Click Outside & Escape key
21
20
  useEffect(() => {
22
21
  const handleMouseDown = (e: MouseEvent) => {
23
22
  if (popoverRef.current && !popoverRef.current.contains(e.target as Node)) {
@@ -55,7 +54,6 @@ export const ExplorerOptionsModal: React.FC<ExplorerOptionsModalProps> = ({
55
54
 
56
55
  return (
57
56
  <div ref={popoverRef} className={css.optionsPopover}>
58
- {/* Header */}
59
57
  <div className={css.optionsHeader}>
60
58
  <div className={css.optionsHeaderTitle}>
61
59
  <svg width="13" height="13" viewBox="0 0 16 16" fill="currentColor" style={{ marginRight: '6px' }}>
@@ -77,13 +75,9 @@ export const ExplorerOptionsModal: React.FC<ExplorerOptionsModalProps> = ({
77
75
  </div>
78
76
  </div>
79
77
 
80
- {/* Body */}
81
78
  <div className={css.optionsBody}>
82
- {/* Section 1: Density & Scale */}
83
79
  <div className={css.optionsSection}>
84
80
  <div className={css.optionsSectionTitle}>Scale & Density</div>
85
-
86
- {/* Density preset buttons */}
87
81
  <div className={css.segmentGroup}>
88
82
  <button
89
83
  type="button"
@@ -108,7 +102,6 @@ export const ExplorerOptionsModal: React.FC<ExplorerOptionsModalProps> = ({
108
102
  </button>
109
103
  </div>
110
104
 
111
- {/* Scale zoom slider */}
112
105
  <div className={css.optionsRow} style={{ marginTop: '6px' }}>
113
106
  <div className={css.optionsLabelWrap}>
114
107
  <span className={css.optionsLabel}>Zoom Scale</span>
@@ -139,7 +132,6 @@ export const ExplorerOptionsModal: React.FC<ExplorerOptionsModalProps> = ({
139
132
  />
140
133
  </div>
141
134
 
142
- {/* Section 2: Motion & Animation */}
143
135
  <div className={css.optionsSection}>
144
136
  <div className={css.optionsSectionTitle}>Motion & Transitions</div>
145
137
  <div className={css.optionsRow}>
@@ -158,11 +150,9 @@ export const ExplorerOptionsModal: React.FC<ExplorerOptionsModalProps> = ({
158
150
  </div>
159
151
  </div>
160
152
 
161
- {/* Section 3: File Tree Display */}
162
153
  <div className={css.optionsSection}>
163
154
  <div className={css.optionsSectionTitle}>Workspace View</div>
164
155
 
165
- {/* Show hidden files */}
166
156
  <div className={css.optionsRow}>
167
157
  <div className={css.optionsLabelWrap}>
168
158
  <span className={css.optionsLabel}>Show Hidden Files</span>
@@ -178,7 +168,6 @@ export const ExplorerOptionsModal: React.FC<ExplorerOptionsModalProps> = ({
178
168
  </div>
179
169
  </div>
180
170
 
181
- {/* Git badges */}
182
171
  <div className={css.optionsRow}>
183
172
  <div className={css.optionsLabelWrap}>
184
173
  <span className={css.optionsLabel}>Git Status Indicators</span>
@@ -194,7 +183,6 @@ export const ExplorerOptionsModal: React.FC<ExplorerOptionsModalProps> = ({
194
183
  </div>
195
184
  </div>
196
185
 
197
- {/* Compact folders */}
198
186
  <div className={css.optionsRow}>
199
187
  <div className={css.optionsLabelWrap}>
200
188
  <span className={css.optionsLabel}>Compact Folders</span>
@@ -211,7 +199,6 @@ export const ExplorerOptionsModal: React.FC<ExplorerOptionsModalProps> = ({
211
199
  </div>
212
200
  </div>
213
201
 
214
- {/* Section 4: Quick Workflow */}
215
202
  <div className={css.optionsSection}>
216
203
  <div className={css.optionsSectionTitle}>Interaction</div>
217
204
  <div className={css.optionsRow}>
@@ -1,9 +1,9 @@
1
1
  import React, { useState, useRef, useEffect } from 'react'
2
- import css from './explorer.module.css'
3
- import type { FsEntry } from './api.ts'
4
- import type { SingleClickAction } from './options.ts'
5
- import { renderFileIcon } from './vscode-theme.tsx'
6
- import { setupDragStart, setupDragEnd, type DragExplorerItem } from './drag-drop.ts'
2
+ import css from '../explorer.module.css'
3
+ import type { FsEntry } from '../utils/api.ts'
4
+ import type { SingleClickAction } from '../utils/options.ts'
5
+ import { renderFileIcon } from '../utils/vscode-theme.tsx'
6
+ import { setupDragStart, setupDragEnd, type DragExplorerItem } from '../utils/drag-drop.ts'
7
7
 
8
8
  interface ExplorerRowProps {
9
9
  entry: FsEntry
@@ -11,6 +11,7 @@ interface ExplorerRowProps {
11
11
  isExpanded: boolean
12
12
  isSelected: boolean
13
13
  isFocused: boolean
14
+ isRenaming?: boolean
14
15
  gitStatus?: string
15
16
  relativePath: string
16
17
  compactChain?: FsEntry[]
@@ -26,6 +27,8 @@ interface ExplorerRowProps {
26
27
  onContextMenu: (e: React.MouseEvent) => void
27
28
  onInsertReference: () => void
28
29
  onCopyRelativePath: () => void
30
+ onStartRename?: () => void
31
+ onCancelRename?: () => void
29
32
  onRenameSubmit: (newName: string) => void
30
33
  onDelete: () => void
31
34
  }
@@ -36,6 +39,7 @@ export const ExplorerRow: React.FC<ExplorerRowProps> = ({
36
39
  isExpanded,
37
40
  isSelected,
38
41
  isFocused,
42
+ isRenaming = false,
39
43
  gitStatus,
40
44
  relativePath,
41
45
  compactChain,
@@ -51,19 +55,30 @@ export const ExplorerRow: React.FC<ExplorerRowProps> = ({
51
55
  onContextMenu,
52
56
  onInsertReference,
53
57
  onCopyRelativePath,
58
+ onStartRename,
59
+ onCancelRename,
54
60
  onRenameSubmit,
55
61
  onDelete,
56
62
  }) => {
57
- const [isRenaming, setIsRenaming] = useState(false)
58
63
  const [renameVal, setRenameVal] = useState(entry.name)
59
64
  const inputRef = useRef<HTMLInputElement>(null)
60
65
 
61
66
  useEffect(() => {
62
67
  if (isRenaming) {
63
- inputRef.current?.focus()
64
- inputRef.current?.select()
68
+ setRenameVal(entry.name)
69
+ requestAnimationFrame(() => {
70
+ if (inputRef.current) {
71
+ inputRef.current.focus()
72
+ const dotIndex = entry.isDir ? -1 : entry.name.lastIndexOf('.')
73
+ if (dotIndex > 0) {
74
+ inputRef.current.setSelectionRange(0, dotIndex)
75
+ } else {
76
+ inputRef.current.select()
77
+ }
78
+ }
79
+ })
65
80
  }
66
- }, [isRenaming])
81
+ }, [isRenaming, entry.name, entry.isDir])
67
82
 
68
83
  const handleDragStart = (e: React.DragEvent) => {
69
84
  const item: DragExplorerItem = {
@@ -77,25 +92,35 @@ export const ExplorerRow: React.FC<ExplorerRowProps> = ({
77
92
 
78
93
  const handleRenameKeyDown = (e: React.KeyboardEvent) => {
79
94
  if (e.key === 'Enter') {
95
+ e.stopPropagation()
80
96
  const trimmed = renameVal.trim()
81
97
  if (trimmed && trimmed !== entry.name) {
82
98
  onRenameSubmit(trimmed)
99
+ } else {
100
+ onCancelRename?.()
83
101
  }
84
- setIsRenaming(false)
85
102
  } else if (e.key === 'Escape') {
103
+ e.stopPropagation()
86
104
  setRenameVal(entry.name)
87
- setIsRenaming(false)
105
+ onCancelRename?.()
106
+ }
107
+ }
108
+
109
+ const handleRenameBlur = () => {
110
+ const trimmed = renameVal.trim()
111
+ if (trimmed && trimmed !== entry.name) {
112
+ onRenameSubmit(trimmed)
113
+ } else {
114
+ onCancelRename?.()
88
115
  }
89
116
  }
90
117
 
91
- // Display label: if compactFolders is on and compactChain has elements, join their names (e.g. "vscode/antigravity-quota-context")
92
118
  const displayName = compactFolders && compactChain && compactChain.length > 1
93
119
  ? compactChain.map((c) => c.name).join('/')
94
120
  : (compactFolders && entry.compactName
95
121
  ? entry.compactName
96
122
  : entry.name)
97
123
 
98
- // Git status styling
99
124
  let gitClass = ''
100
125
  let gitLetter = ''
101
126
  if (gitStatus) {
@@ -108,6 +133,9 @@ export const ExplorerRow: React.FC<ExplorerRowProps> = ({
108
133
  } else if (gitStatus.includes('D')) {
109
134
  gitClass = css.gitDeleted
110
135
  gitLetter = 'D'
136
+ } else if (gitStatus.includes('R')) {
137
+ gitClass = css.gitModified
138
+ gitLetter = 'R'
111
139
  }
112
140
  }
113
141
 
@@ -118,6 +146,7 @@ export const ExplorerRow: React.FC<ExplorerRowProps> = ({
118
146
  className={`${css.row} ${isSelected ? css.rowSelected : ''} ${isFocused ? css.rowFocused : ''}`}
119
147
  style={{ paddingLeft: `${depth * indentStep + 8}px` }}
120
148
  onClick={(e) => {
149
+ if (isRenaming) return
121
150
  onSelect(e)
122
151
  if (entry.isDir) {
123
152
  onToggle()
@@ -126,6 +155,7 @@ export const ExplorerRow: React.FC<ExplorerRowProps> = ({
126
155
  }
127
156
  }}
128
157
  onDoubleClick={(e) => {
158
+ if (isRenaming) return
129
159
  if (!entry.isDir) {
130
160
  e.stopPropagation()
131
161
  onDoubleClick?.()
@@ -136,7 +166,6 @@ export const ExplorerRow: React.FC<ExplorerRowProps> = ({
136
166
  onDragStart={handleDragStart}
137
167
  onDragEnd={setupDragEnd}
138
168
  >
139
- {/* Indentation guide lines */}
140
169
  {Array.from({ length: depth }).map((_, i) => (
141
170
  <span
142
171
  key={i}
@@ -145,7 +174,6 @@ export const ExplorerRow: React.FC<ExplorerRowProps> = ({
145
174
  />
146
175
  ))}
147
176
 
148
- {/* Expand/Collapse Chevron */}
149
177
  <span
150
178
  className={`${css.chevron} ${isExpanded ? css.chevronExpanded : ''} ${!entry.isDir ? css.chevronHidden : ''}`}
151
179
  style={!enableMotion ? { transition: 'none' } : undefined}
@@ -159,12 +187,10 @@ export const ExplorerRow: React.FC<ExplorerRowProps> = ({
159
187
  </svg>
160
188
  </span>
161
189
 
162
- {/* Language / Folder Icon */}
163
190
  <span className={css.iconWrap}>
164
191
  {renderFileIcon(entry.name, entry.isDir, isExpanded, iconSize)}
165
192
  </span>
166
193
 
167
- {/* Label or Inline Rename */}
168
194
  {isRenaming ? (
169
195
  <input
170
196
  ref={inputRef}
@@ -173,13 +199,7 @@ export const ExplorerRow: React.FC<ExplorerRowProps> = ({
173
199
  value={renameVal}
174
200
  onChange={(e) => setRenameVal(e.target.value)}
175
201
  onKeyDown={handleRenameKeyDown}
176
- onBlur={() => {
177
- const trimmed = renameVal.trim()
178
- if (trimmed && trimmed !== entry.name) {
179
- onRenameSubmit(trimmed)
180
- }
181
- setIsRenaming(false)
182
- }}
202
+ onBlur={handleRenameBlur}
183
203
  onClick={(e) => e.stopPropagation()}
184
204
  />
185
205
  ) : (
@@ -191,10 +211,8 @@ export const ExplorerRow: React.FC<ExplorerRowProps> = ({
191
211
  </span>
192
212
  )}
193
213
 
194
- {/* Git status badge */}
195
214
  {showGitBadges && gitLetter && <span className={`${css.gitBadge} ${gitClass}`}>{gitLetter}</span>}
196
215
 
197
- {/* Hover actions */}
198
216
  {!isRenaming && (
199
217
  <div className={css.hoverActions} onClick={(e) => e.stopPropagation()}>
200
218
  <button
@@ -226,7 +244,7 @@ export const ExplorerRow: React.FC<ExplorerRowProps> = ({
226
244
  title="Rename (F2)"
227
245
  onClick={(e) => {
228
246
  e.stopPropagation()
229
- setIsRenaming(true)
247
+ onStartRename?.()
230
248
  }}
231
249
  >
232
250
  <svg width="11" height="11" viewBox="0 0 16 16" fill="currentColor">