@node-tree/explorer
v0.0.2
Published
File explorer service layer with VSCode-like architecture for virtual file systems
Readme
@node-tree/explorer
A VSCode-inspired file explorer service layer that provides comprehensive file operations, navigation, and state management for tree-based file explorers. Built with TypeScript and designed for high-performance applications with large directory structures.
🎯 Purpose & Philosophy
This package implements the service layer of a file explorer following VSCode's architecture patterns. It orchestrates between:
- @firesystem/core: Virtual file system abstraction
- @node-tree/viewport: High-performance tree virtualization
- @node-tree/react: React hooks for UI integration
🔧 Note: This is a service-only package - it contains no UI components. Use
@node-tree/reactfor React integration.
🚀 Features
📁 Complete File Operations
- ✅ Create files/directories with smart naming
- ✅ Delete with confirmation and bulk support
- ✅ Rename with validation and conflict resolution
- ✅ Move/Copy with overwrite handling
- ✅ Duplicate with intelligent naming (
file-copy.ext)
🔄 Full Undo/Redo System
- ✅ Bounded operation history (configurable stack size)
- ✅ Complete operation reversal (files, directories, content)
- ✅ Branch handling - new operations clear future history
- ✅ Metadata tracking with affected paths
⌨️ VSCode-like Navigation
- ✅ Arrow key navigation (Up/Down, Left/Right)
- ✅ Page navigation (PageUp/PageDown)
- ✅ Jump navigation (Home/End, Parent/Child)
- ✅ Smart expansion (Right arrow expands directories)
- ✅ OPTIMIZED: High-performance End navigation (O(1) complexity)
🎯 Advanced Selection Management
- ✅ Single-click selection (VSCode behavior)
- ✅ Multi-select with Ctrl+Click (maintains previous selection)
- ✅ Range selection with Shift+Click
- ✅ Select all/clear/invert operations
- ✅ Type-specific selection (files only, directories only)
- ✅ NEW:
selectSingle()method for exclusive selection
📋 Clipboard Operations
- ✅ Cut/Copy/Paste with proper semantics
- ✅ Multi-paste support (copy once, paste multiple times)
- ✅ Auto-clear on cut operations
- ✅ Cross-operation clipboard persistence
- ✅ NEW: Memory-safe clipboard with 10,000 item limit
- ✅ NEW: Auto-expiring clipboard (30-minute TTL)
📡 Event-Driven Reactivity
- ✅ Typed event system for all state changes
- ✅ Progress tracking for long operations
- ✅ User confirmation hooks (delete, overwrite)
- ✅ Real-time UI synchronization
- ✅ ENHANCED: Comprehensive error events with detailed context
- ✅ NEW: Operation success events for better UX feedback
📦 Installation
npm install @node-tree/explorer @firesystem/core @node-tree/viewport
# or
yarn add @node-tree/explorer @firesystem/core @node-tree/viewport
# or
pnpm add @node-tree/explorer @firesystem/core @node-tree/viewport🏗️ Architecture Overview
┌─────────────────────────────────────────────────────────┐
│ @node-tree/react │
│ (React Hooks & UI) │
└─────────────────────┬───────────────────────────────────┘
│
┌─────────────────────▼───────────────────────────────────┐
│ @node-tree/explorer │
│ (Service Layer & Logic) │
│ ┌─────────────────┬─────────────────┬─────────────────┐ │
│ │ ExplorerService │ NavigationSvc │ SelectionSvc │ │
│ │ FileOpService │ ClipboardSvc │ HistoryManager │ │
│ └─────────────────┴─────────────────┴─────────────────┘ │
└─────────────────────┬───────────────────────────────────┘
│
┌─────────────────────▼───────────────────────────────────┐
│ @firesystem/core │ @node-tree/viewport │
│ (Virtual FS) │ (Tree Virtualization) │
└─────────────────────┴───────────────────────────────────┘🎮 VSCode Behavior Compatibility
Mouse Interactions
- Single Click on File → Selects file exclusively (clears previous selection)
- Single Click on Directory → Selects directory and toggles expansion
- Double Click on File → Opens file in permanent mode
- Double Click on Directory → No action (VSCode ignores double-click on directories)
- Ctrl+Click → Toggles selection of clicked item (maintains previous selection)
- Shift+Click → Selects range from cursor to clicked item
Selection Behavior Updates (v0.0.2)
- ✅ Fixed: Single click now properly selects the clicked item
- ✅ Fixed: Ctrl+Click maintains previous selection instead of clearing it
- ✅ Added:
selectSingle()method for exclusive selection (VSCode behavior) - ✅ Fixed: Double-click on directories is now properly ignored
🛠️ Core Services
ExplorerService - Main Orchestrator
Central service that coordinates all explorer functionality:
import { ExplorerService } from "@node-tree/explorer";
import { MemoryFileSystem } from "@firesystem/memory";
import { TreeViewport } from "@node-tree/viewport";
const fs = new MemoryFileSystem();
const viewport = new TreeViewport(provider, { itemHeight: 24 });
const explorer = new ExplorerService(fs, viewport, {
confirmDelete: true,
confirmOverwrite: true,
maxHistorySize: 50,
});FileOperationService - File Operations Engine
Handles all file system operations with undo/redo support:
// All operations return promises and emit events
await explorer.createFile("/src", "component.tsx");
await explorer.createDirectory("/src", "components");
await explorer.rename("/old-name.txt", "new-name.txt");
await explorer.delete(["/file1.txt", "/file2.txt"]);
await explorer.move(["/source.txt"], "/target-folder");
await explorer.copy(["/template.txt"], "/new-folder");
await explorer.duplicate(["/important.txt"]); // Creates important-copy.txtNavigationService - Tree Navigation
VSCode-like keyboard navigation:
// Navigate tree structure
await explorer.navigateUp(); // Move cursor up
await explorer.navigateDown(); // Move cursor down
await explorer.navigateLeft(); // Collapse or go to parent
await explorer.navigateRight(); // Expand or enter directory
await explorer.navigateToFirst(); // Jump to first item
await explorer.navigateToLast(); // Jump to last itemSelectionService - Multi-Selection
Advanced selection management:
// Selection operations
await explorer.selectAll(); // Select all visible items
explorer.clearSelection(); // Clear all selections
explorer.selectSingle("/file.txt"); // Select only this item (clear others)
explorer.toggleSelection("/file.txt"); // Toggle single item (keep others)
await explorer.selectRange("/start", "/end"); // Select range
await explorer.invertSelection(); // Invert current selectionClipboardService - Cut/Copy/Paste
Full clipboard operations with memory management:
// Clipboard operations
explorer.cut(["/file1.txt", "/file2.txt"]); // Cut files
explorer.copy(["/template.tsx"]); // Copy files
await explorer.paste("/destination"); // Paste to destination
// Check clipboard state
const clipboardState = explorer.getClipboardState();
console.log(clipboardState.hasItems); // boolean
console.log(clipboardState.operation); // 'cut' | 'copy'
// Memory-safe operations
try {
explorer.copy(largeFileList); // Automatically limited to 10,000 items
} catch (error) {
// Handle excessive clipboard usage
}
// Auto-expiring clipboard (30-minute TTL)
explorer.copy(["/file1.txt"]);
// ... 31 minutes later ...
console.log(explorer.canPaste()); // false (auto-cleared)HistoryManager - Undo/Redo System
Complete operation history:
// History operations
await explorer.undo(); // Undo last operation
await explorer.redo(); // Redo next operation
// Check history state
const historyState = explorer.getHistoryState();
console.log(historyState.canUndo); // boolean
console.log(historyState.canRedo); // boolean
console.log(historyState.operations); // IOperation[]🎮 Command System
The explorer provides a VSCode-like command system with categories, keybindings, and context awareness:
// Execute commands by ID
await explorer.executeCommand("explorer.newFile");
await explorer.executeCommand("explorer.delete");
await explorer.executeCommand("explorer.selectAll");
// Get all available commands
const commands = explorer.getCommands();
commands.forEach((cmd) => {
console.log(`${cmd.metadata.id}: ${cmd.metadata.label}`);
console.log(` Category: ${cmd.metadata.category}`);
console.log(` Keybinding: ${cmd.metadata.keybinding}`);
});
// Register custom commands
explorer.registerCommand({
metadata: {
id: "custom.myCommand",
label: "My Custom Command",
category: "Custom",
keybinding: "Ctrl+Alt+M",
},
handler: async (context) => {
// Custom logic here
},
enabled: (context) => context.explorer.hasSelection(),
});Built-in Commands
File Operations
explorer.newFile- Create new fileexplorer.newFolder- Create new directoryexplorer.delete- Delete selected itemsexplorer.rename- Rename current itemexplorer.refresh- Refresh tree viewexplorer.reveal- Reveal file in tree
Navigation
explorer.navigateUp/Down- Arrow navigationexplorer.navigateLeft/Right- Horizontal navigationexplorer.navigateToFirst/Last- Jump navigationexplorer.pageUp/Down- Page navigation
Edit Operations
explorer.cut- Cut selected itemsexplorer.copy- Copy selected itemsexplorer.paste- Paste clipboard itemsexplorer.duplicate- Duplicate selected itemsexplorer.undo/redo- History operations
Selection
explorer.selectAll- Select all visibleexplorer.clearSelection- Clear selectionexplorer.toggleSelection- Toggle current itemexplorer.invertSelection- Invert selection
📡 Event System
The explorer emits typed events for all state changes, enabling reactive UI updates:
// Listen to all explorer events
explorer.on("operation:start", (event) => {
console.log(`Starting ${event.type} on:`, event.paths);
});
explorer.on("operation:complete", (event) => {
console.log(`Completed ${event.type} on:`, event.paths);
});
explorer.on("operation:error", (event) => {
console.error(`Error in ${event.type}:`, event.error);
});
// Progress tracking for long operations
explorer.on("progress:start", (event) => {
console.log(`Starting operation (${event.total} items)`);
});
explorer.on("progress:update", (event) => {
const percent = (event.current / event.total) * 100;
console.log(`Progress: ${percent.toFixed(1)}%`);
});
// State change notifications
explorer.on("clipboard:change", (state) => {
updateClipboardUI(state);
});
explorer.on("history:change", (state) => {
updateUndoRedoButtons(state);
});
// User confirmation hooks
explorer.on("confirm:delete", (event) => {
const result = window.confirm(`Delete ${event.paths.length} items?`);
event.confirm(result);
});
explorer.on("confirm:overwrite", (event) => {
const result = window.confirm(`Overwrite ${event.path}?`);
event.confirm(result);
});Event Types
interface IExplorerEvents {
// Operation lifecycle
"operation:start": { type: OperationType; paths: string[] };
"operation:complete": { type: OperationType; paths: string[] };
"operation:error": { type: OperationType; error: Error };
// Progress tracking
"progress:start": { total: number };
"progress:update": { current: number; total: number };
"progress:complete": void;
// State changes
"clipboard:change": IClipboardState;
"history:change": { canUndo: boolean; canRedo: boolean };
// User confirmations
"confirm:delete": { paths: string[]; confirm: (result: boolean) => void };
"confirm:overwrite": { path: string; confirm: (result: boolean) => void };
}⚡ Performance & Memory Management
Optimized Navigation
// High-performance End navigation - O(1) complexity
await explorer.navigateToLast(); // Uses viewport.scrollToIndex() for efficiency
// Smart fallback for large trees
// If scrollToIndex fails, gracefully falls back to visible windowMemory-Safe Clipboard
// Automatic limits prevent memory issues
const MAX_CLIPBOARD_ITEMS = 10000; // Built-in limit
const CLIPBOARD_TTL_MS = 30 * 60 * 1000; // 30-minute auto-expire
// Error handling for large selections
explorer.on("operation:error", (event) => {
if (event.error.message.includes("Maximum allowed")) {
showToast("Selection too large. Maximum 10,000 items allowed.");
}
});Event-Driven Error Handling
// Enhanced error events with detailed context
explorer.on("operation:error", (event) => {
console.log(`Operation: ${event.type}`);
console.log(`Error: ${event.error.message}`);
// Handle specific error types
});
// Success events for better UX
explorer.on("operation:complete", (event) => {
console.log(`${event.type} completed on ${event.paths.length} items`);
});Performance Best Practices
// 1. Use bulk operations instead of loops
await explorer.delete(["/file1", "/file2", "/file3"]); // ✅ Good
// NOT: for (const file of files) await explorer.delete([file]); // ❌ Bad
// 2. Configure appropriate history size
const explorer = new ExplorerService(fs, viewport, {
maxHistorySize: 100, // Adjust based on memory constraints
});
// 3. Listen to progress events for long operations
explorer.on("progress:update", (event) => {
const percent = (event.current / event.total) * 100;
updateProgressBar(percent);
});⚙️ Configuration
interface IExplorerConfig {
// User confirmations
confirmDelete?: boolean; // Default: false
confirmOverwrite?: boolean; // Default: false
// History settings
maxHistorySize?: number; // Default: 50
// File operation settings
autoExpandOnCreate?: boolean; // Default: true
autoSelectOnCreate?: boolean; // Default: true
// Navigation settings
pageNavigationSize?: number; // Default: 10
wrapNavigation?: boolean; // Default: false
}🔗 Integration with React
For React applications, use @node-tree/react which provides hooks that integrate seamlessly with this service layer:
import { useExplorerService, useKeyboardShortcuts, useExplorerEvents } from '@node-tree/react';
function FileExplorer() {
// Create explorer service
const explorer = useExplorerService(fs, viewport, config);
// Setup keyboard shortcuts
const shortcuts = useKeyboardShortcuts(explorer);
// Listen to events
useExplorerEvents(explorer, {
'operation:complete': (event) => {
toast.success(`${event.type} completed successfully`);
},
'operation:error': (event) => {
toast.error(`Error: ${event.error.message}`);
}
});
return <YourTreeComponent />;
}🧪 Testing
The package includes comprehensive tests with 90.34% coverage and 387 test cases:
npm test # Run all tests
npm run test:coverage # Run with coverage report
npm run test:watch # Watch mode for developmentTest Categories
- Unit Tests: Individual service testing with mocks
- Integration Tests: Full FileSystem + Viewport + Explorer integration
- Performance Tests: Memory limits, TTL expiration, large datasets
- Error Handling: Comprehensive error scenarios and edge cases
New Test Coverage
- ✅ Clipboard TTL and auto-expiration
- ✅ Memory limits and large selection handling
- ✅ Optimized navigation performance
- ✅ Enhanced error event handling
- ✅ Success event emissions
📋 Requirements
- Node.js: >=16.0.0
- TypeScript: >=4.8.0
- @firesystem/core: ^1.0.0
- @node-tree/viewport: workspace:*
📄 License
MIT © Anderson D. Rosa
🤝 Contributing
This package follows VSCode architecture patterns and maintains high code quality standards:
- 🔧 TypeScript: Fully typed with strict mode
- 🧪 Testing: Comprehensive test suite with high coverage
- 📋 Linting: ESLint + Prettier configuration
- 🏗️ Architecture: Service-oriented with clear separation of concerns
- 📡 Events: Reactive architecture with typed events
- ♻️ Operations: Full undo/redo support for all operations
📝 Changelog
v0.0.2 (Latest)
- ✅ Added
selectSingle()method for exclusive selection - ✅ Fixed single-click selection behavior to match VSCode
- ✅ Fixed Ctrl+Click to maintain previous selection
- ✅ Fixed double-click on directories (now properly ignored)
- ✅ Improved VSCode behavior compatibility
v0.0.1 (Initial Release)
- Core explorer service implementation
- File operations with undo/redo
- Navigation and selection services
- Clipboard operations
- Event system
- Command registry
Built for high-performance applications that need VSCode-level file explorer functionality with complete TypeScript support and reactive architecture.
