diff --git a/changelog.md b/changelog.md index 527c1ff..78a78a4 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,15 @@ # Changelog +## 2025-12-31 - 3.23.0 - feat(workspace) +add resizable file tree and terminal panes with draggable handles and public layout APIs + +- Introduce reactive state for currentFileTreeWidth, currentTerminalHeight, isDraggingFileTree and isDraggingTerminal +- Add mouse event handlers (mousedown/move/up) to drag-resize the file tree and terminal with min/max clamping +- Dispatch window resize event after resizing to notify Monaco/editor of layout changes +- Clean up resize event listeners in disconnectedCallback +- Initialize current sizes from component properties in firstUpdated +- Expose public layout methods: setFileTreeWidth, setTerminalHeight, resetLayout + ## 2025-12-31 - 3.22.0 - feat(workspace) add resizable markdown editor/preview split with draggable handle and markdown outlet styling/demo diff --git a/ts_web/00_commitinfo_data.ts b/ts_web/00_commitinfo_data.ts index ffcf0b2..3ca497a 100644 --- a/ts_web/00_commitinfo_data.ts +++ b/ts_web/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@design.estate/dees-catalog', - version: '3.22.0', + version: '3.23.0', description: 'A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.' } diff --git a/ts_web/elements/00group-workspace/dees-workspace/dees-workspace.ts b/ts_web/elements/00group-workspace/dees-workspace/dees-workspace.ts index b7aaa3c..6001df8 100644 --- a/ts_web/elements/00group-workspace/dees-workspace/dees-workspace.ts +++ b/ts_web/elements/00group-workspace/dees-workspace/dees-workspace.ts @@ -262,6 +262,19 @@ testSmartPromise(); @state() accessor initOutput: string[] = []; + // Resize state + @state() + accessor currentFileTreeWidth: number = 250; + + @state() + accessor currentTerminalHeight: number = 200; + + @state() + accessor isDraggingFileTree: boolean = false; + + @state() + accessor isDraggingTerminal: boolean = false; + // Keyboard shortcut handler (bound for proper cleanup) private keydownHandler = (e: KeyboardEvent) => { // Cmd+S (Mac) or Ctrl+S (Windows/Linux) - Save @@ -277,6 +290,71 @@ testSmartPromise(); } }; + // ========== Filetree Resize Handlers ========== + private handleFileTreeMouseDown = (e: MouseEvent) => { + e.preventDefault(); + this.isDraggingFileTree = true; + document.addEventListener('mousemove', this.handleFileTreeMouseMove); + document.addEventListener('mouseup', this.handleFileTreeMouseUp); + }; + + private handleFileTreeMouseMove = (e: MouseEvent) => { + if (!this.isDraggingFileTree) return; + + const containerRect = this.getBoundingClientRect(); + const mouseX = e.clientX - containerRect.left; + + // Clamp to min/max (150px min, 50% of container max) + const minWidth = 150; + const maxWidth = containerRect.width * 0.5; + const newWidth = Math.max(minWidth, Math.min(maxWidth, mouseX)); + + this.currentFileTreeWidth = newWidth; + }; + + private handleFileTreeMouseUp = () => { + this.isDraggingFileTree = false; + document.removeEventListener('mousemove', this.handleFileTreeMouseMove); + document.removeEventListener('mouseup', this.handleFileTreeMouseUp); + + // Notify Monaco editor of size change + window.dispatchEvent(new Event('resize')); + }; + + // ========== Terminal Resize Handlers ========== + private handleTerminalMouseDown = (e: MouseEvent) => { + e.preventDefault(); + this.isDraggingTerminal = true; + document.addEventListener('mousemove', this.handleTerminalMouseMove); + document.addEventListener('mouseup', this.handleTerminalMouseUp); + }; + + private handleTerminalMouseMove = (e: MouseEvent) => { + if (!this.isDraggingTerminal) return; + + const containerRect = this.getBoundingClientRect(); + const mouseY = e.clientY - containerRect.top; + + // Calculate terminal height from bottom + const terminalHeight = containerRect.height - mouseY; + + // Clamp to min/max (100px min, 70% of container max) + const minHeight = 100; + const maxHeight = containerRect.height * 0.7; + const newHeight = Math.max(minHeight, Math.min(maxHeight, terminalHeight)); + + this.currentTerminalHeight = newHeight; + }; + + private handleTerminalMouseUp = () => { + this.isDraggingTerminal = false; + document.removeEventListener('mousemove', this.handleTerminalMouseMove); + document.removeEventListener('mouseup', this.handleTerminalMouseUp); + + // Notify Monaco editor of size change + window.dispatchEvent(new Event('resize')); + }; + public static styles = [ themeDefaultStyles, cssManager.defaultStyles, @@ -293,67 +371,45 @@ testSmartPromise(); } .workspace-container { - display: grid; + display: flex; + flex-direction: row; height: 100%; width: 100%; } - .workspace-container.with-filetree.with-terminal { - grid-template-columns: auto 1fr; - grid-template-rows: 1fr auto; - grid-template-areas: - "filetree editor" - "filetree terminal"; - } - - .workspace-container.with-filetree:not(.with-terminal) { - grid-template-columns: auto 1fr; - grid-template-rows: 1fr; - grid-template-areas: "filetree editor"; - } - - .workspace-container:not(.with-filetree).with-terminal { - grid-template-columns: 1fr; - grid-template-rows: 1fr auto; - grid-template-areas: - "editor" - "terminal"; - } - - .workspace-container:not(.with-filetree):not(.with-terminal) { - grid-template-columns: 1fr; - grid-template-rows: 1fr; - grid-template-areas: "editor"; + .editor-area { + display: flex; + flex-direction: column; + flex: 1; + min-width: 0; + overflow: hidden; } .filetree-panel { - grid-area: filetree; position: relative; - border-right: 1px solid ${cssManager.bdTheme('hsl(0 0% 85%)', 'hsl(0 0% 15%)')}; overflow: hidden; - transition: width 0.2s ease; + flex-shrink: 0; } .filetree-panel.collapsed { width: 0 !important; - border-right: none; } .editor-panel { - grid-area: editor; position: relative; display: flex; flex-direction: column; overflow: hidden; background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 9%)')}; + flex: 1; + min-width: 200px; } .terminal-panel { - grid-area: terminal; position: relative; border-top: 1px solid ${cssManager.bdTheme('hsl(0 0% 85%)', 'hsl(0 0% 15%)')}; overflow: hidden; - transition: height 0.2s ease; + flex-shrink: 0; } .terminal-panel.collapsed { @@ -695,14 +751,96 @@ testSmartPromise(); right: 0; bottom: 0; } + + /* Resize handles */ + .resize-handle-vertical { + width: 6px; + cursor: col-resize; + background: transparent; + transition: background 0.15s ease; + position: relative; + flex-shrink: 0; + z-index: 10; + } + + .resize-handle-vertical:hover, + .resize-handle-vertical.dragging { + background: ${cssManager.bdTheme('#3b82f6', '#58a6ff')}; + } + + .resize-handle-vertical::after { + content: ''; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 2px; + height: 32px; + background: ${cssManager.bdTheme('#9ca3af', '#6e7681')}; + border-radius: 1px; + opacity: 0; + transition: opacity 0.15s ease; + } + + .resize-handle-vertical:hover::after, + .resize-handle-vertical.dragging::after { + opacity: 1; + background: ${cssManager.bdTheme('#ffffff', '#ffffff')}; + } + + .resize-handle-horizontal { + height: 6px; + cursor: row-resize; + background: transparent; + transition: background 0.15s ease; + position: relative; + flex-shrink: 0; + z-index: 10; + } + + .resize-handle-horizontal:hover, + .resize-handle-horizontal.dragging { + background: ${cssManager.bdTheme('#3b82f6', '#58a6ff')}; + } + + .resize-handle-horizontal::after { + content: ''; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 32px; + height: 2px; + background: ${cssManager.bdTheme('#9ca3af', '#6e7681')}; + border-radius: 1px; + opacity: 0; + transition: opacity 0.15s ease; + } + + .resize-handle-horizontal:hover::after, + .resize-handle-horizontal.dragging::after { + opacity: 1; + background: ${cssManager.bdTheme('#ffffff', '#ffffff')}; + } + + /* Prevent text selection while dragging */ + .workspace-container.dragging { + user-select: none; + } + + .workspace-container.dragging .filetree-panel, + .workspace-container.dragging .editor-panel, + .workspace-container.dragging .terminal-panel { + pointer-events: none; + } + `, ]; public render(): TemplateResult { const containerClasses = [ 'workspace-container', - this.showFileTree && !this.isFileTreeCollapsed ? 'with-filetree' : '', - this.showTerminal ? 'with-terminal' : '', + (this.isDraggingFileTree || this.isDraggingTerminal) ? 'dragging' : '', ].filter(Boolean).join(' '); if (this.isInitializing) { @@ -720,10 +858,11 @@ testSmartPromise(); return html`