feat: enhance control panel UI and resize functionality

Panel UI improvements:
- Replace heading elements (h1-h6) with styled divs to avoid navigation interference
- Change ContentsControl position from northwest to west for better accessibility

Panel collapse/expand enhancements:
- Fix panel dragging to prevent unexpected positioning jumps
- Keep panel width and upper-left position when collapsing to header-only mode
- Complete height reduction when collapsed (no minimal size maintained)
- Toggle resize handle visibility based on panel state

Resize handle improvements:
- Change resize symbol from arrow to clean dot (●) in bottom-right corner
- Remove background circle, show transparent dot only
- Fix resize direction to properly follow mouse movement from bottom-right
- Set dynamic minimum size constraints (header height + padding)
- Allow arbitrary panel sizing with proper bounds checking
- Reset panel size to defaults when closed/collapsed

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-14 16:40:23 +01:00
parent 95ea13958a
commit 512085d283
9 changed files with 203 additions and 63 deletions

View File

@@ -334,6 +334,17 @@ class ControlBase {
this.element.style.transform = this.originalPosition.transform || '';
}
// Reset panel size to defaults
panel.style.width = '';
panel.style.height = '';
panel.style.minWidth = '300px';
panel.style.minHeight = '200px';
// Reset internal size tracking
this.size.width = 300;
this.size.height = 200;
this.storedWidth = null;
// Remove resize handle
this.removeResizeHandle();
}
@@ -354,9 +365,52 @@ class ControlBase {
}
const content = this.element?.querySelector('.control-content');
if (content) {
const panel = this.element?.querySelector('.control-panel-expanded');
if (content && panel) {
this.isHeaderOnly = !this.isHeaderOnly;
content.style.display = this.isHeaderOnly ? 'none' : 'block';
const resizeHandle = this.element?.querySelector('.control-resize-handle');
if (this.isHeaderOnly) {
// Store current width before collapsing
const currentWidth = panel.offsetWidth;
this.storedWidth = currentWidth;
// Hide content and shrink panel height only
content.style.display = 'none';
panel.style.minHeight = 'auto';
panel.style.height = 'auto';
// Keep the same width and position
panel.style.width = `${currentWidth}px`;
panel.style.minWidth = `${currentWidth}px`;
// Hide resize handle in header-only mode
if (resizeHandle) {
resizeHandle.style.display = 'none';
}
} else {
// Show content and restore full panel size
content.style.display = 'block';
panel.style.minHeight = '200px';
// Restore stored width or use default
const widthToRestore = this.storedWidth || 300;
panel.style.minWidth = `${widthToRestore}px`;
// Restore height if it was auto
if (!panel.style.height || panel.style.height === 'auto') {
panel.style.height = '200px';
}
if (!panel.style.width || panel.style.width === `${widthToRestore}px`) {
panel.style.width = `${widthToRestore}px`;
}
// Show resize handle when fully expanded
if (resizeHandle) {
resizeHandle.style.display = 'flex';
}
}
}
return this.isHeaderOnly;
@@ -378,11 +432,24 @@ class ControlBase {
y: event.clientY - rect.top
};
// Store current computed position before clearing styles
const computedStyle = window.getComputedStyle(this.element);
const currentLeft = rect.left;
const currentTop = rect.top;
// Clear any positioning styles that interfere with dragging
this.element.style.right = '';
this.element.style.bottom = '';
this.element.style.transform = '';
// Set the element to its current visual position using left/top
this.element.style.left = `${currentLeft}px`;
this.element.style.top = `${currentTop}px`;
// Update internal position tracking
this.position.x = currentLeft;
this.position.y = currentTop;
// Add global mouse move and up handlers
const handleMouseMove = (e) => this.handleDrag(e);
const handleMouseUp = () => this.stopDrag();
@@ -441,21 +508,19 @@ class ControlBase {
const resizeHandle = document.createElement('div');
resizeHandle.className = 'control-resize-handle';
resizeHandle.innerHTML = ''; // Bottom-left resize indicator
resizeHandle.innerHTML = ''; // Dot resize indicator
resizeHandle.style.cssText = `
position: absolute;
bottom: 0;
left: 0;
width: 20px;
height: 20px;
cursor: nw-resize;
font-size: 16px;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0,0,0,0.1);
border-radius: 0 8px 0 0;
bottom: 4px;
right: 4px;
width: 8px;
height: 8px;
cursor: se-resize;
font-size: 8px;
line-height: 1;
user-select: none;
color: #999;
background: transparent;
`;
// Add to the expanded panel
@@ -510,7 +575,7 @@ class ControlBase {
}
/**
* Handle resize movement (bottom-left corner resize)
* Handle resize movement (bottom-right corner resize)
*/
handleResize(event) {
if (!this.isResizing || !this.element) return;
@@ -518,13 +583,18 @@ class ControlBase {
const panel = this.element.querySelector('.control-panel-expanded');
if (!panel) return;
// Calculate size change based on mouse movement
const deltaX = this.resizeStart.mouseX - event.clientX; // Inverted for left edge
const deltaY = event.clientY - this.resizeStart.mouseY;
// Calculate size change based on mouse movement (bottom-right corner)
const deltaX = event.clientX - this.resizeStart.mouseX; // Right direction
const deltaY = event.clientY - this.resizeStart.mouseY; // Down direction
// Calculate new dimensions (minimum size constraints)
const newWidth = Math.max(200, this.resizeStart.width + deltaX);
const newHeight = Math.max(150, this.resizeStart.height + deltaY);
// Get minimum size (collapsed header size or default minimum)
const headerHeight = this.element.querySelector('.control-header')?.offsetHeight || 40;
const minWidth = 200;
const minHeight = headerHeight + 20; // Header plus small padding
// Calculate new dimensions with minimum constraints
const newWidth = Math.max(minWidth, this.resizeStart.width + deltaX);
const newHeight = Math.max(minHeight, this.resizeStart.height + deltaY);
// Apply new size to the panel
panel.style.width = `${newWidth}px`;