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`;

View File

@@ -432,7 +432,7 @@ class DebugControl extends ControlBase {
if (content) {
content.innerHTML = `
<div style="padding: 1rem; font-size: 0.8rem;">
<h4 style="margin-top: 0; margin-bottom: 1rem;">Debug Information</h4>
<div style="margin-top: 0; margin-bottom: 1rem; font-weight: 600; font-size: 1.1em; color: #333;">Debug Information</div>
${this.generateSystemInfoHTML()}
${this.generatePerformanceHTML()}

View File

@@ -68,11 +68,11 @@ class EditControl extends ControlBase {
return this.safeOperation(() => {
return `
<div style="padding: 1rem; font-size: 0.8rem;">
<h4 style="margin-top: 0; margin-bottom: 1rem;">Edit Tools</h4>
<div style="margin-top: 0; margin-bottom: 1rem; font-weight: 600; font-size: 1.1em; color: #333;">Edit Tools</div>
<!-- Document Actions -->
<div class="action-section" style="margin-bottom: 1rem;">
<h5 style="margin: 0 0 0.5rem 0; font-size: 0.9em; color: #666;">Document Actions</h5>
<div style="margin: 0 0 0.5rem 0; font-size: 0.9em; color: #666; font-weight: 600;">Document Actions</div>
<button onclick="this.closest('.edit-control').editControl.printDocument()"
style="width: 100%; padding: 0.5rem; margin-bottom: 0.3rem; background: #28a745; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 0.8rem;">
@@ -97,7 +97,7 @@ class EditControl extends ControlBase {
<!-- Navigation Tools -->
<div class="navigation-section" style="margin-bottom: 1rem;">
<h5 style="margin: 0 0 0.5rem 0; font-size: 0.9em; color: #666;">Navigation</h5>
<div style="margin: 0 0 0.5rem 0; font-size: 0.9em; color: #666; font-weight: 600;">Navigation</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 0.3rem;">
<button onclick="this.closest('.edit-control').editControl.scrollToTop()"
@@ -119,7 +119,7 @@ class EditControl extends ControlBase {
<!-- Text Tools -->
<div class="text-section" style="margin-bottom: 1rem;">
<h5 style="margin: 0 0 0.5rem 0; font-size: 0.9em; color: #666;">Text Tools</h5>
<div style="margin: 0 0 0.5rem 0; font-size: 0.9em; color: #666; font-weight: 600;">Text Tools</div>
<button onclick="this.closest('.edit-control').editControl.showFindReplace()"
style="width: 100%; padding: 0.4rem; margin-bottom: 0.3rem; background: #ffc107; color: #000; border: none; border-radius: 3px; cursor: pointer; font-size: 0.7rem;">
@@ -146,7 +146,7 @@ class EditControl extends ControlBase {
<!-- Markdown Tools -->
<div class="markdown-section" style="margin-bottom: 1rem;">
<h5 style="margin: 0 0 0.5rem 0; font-size: 0.9em; color: #666;">Markdown Tools</h5>
<div style="margin: 0 0 0.5rem 0; font-size: 0.9em; color: #666; font-weight: 600;">Markdown Tools</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 0.3rem;">
<button onclick="this.closest('.edit-control').editControl.insertMarkdown('**', '**', 'Bold text')"
@@ -240,7 +240,7 @@ class EditControl extends ControlBase {
`;
exportMenu.innerHTML = `
<h4 style="margin-top: 0;">Export Document</h4>
<div style="margin-top: 0; font-weight: 600; font-size: 1.1em; color: #333; margin-bottom: 1rem;">Export Document</div>
<button onclick="this.parentElement.exportAsHTML()" style="display: block; width: 100%; padding: 0.5rem; margin-bottom: 0.3rem; background: #28a745; color: white; border: none; border-radius: 3px; cursor: pointer;">
Export as HTML
</button>

View File

@@ -132,7 +132,7 @@ class StatusControl extends ControlBase {
return `
<div style="padding: 1rem; font-size: 0.8rem;">
<h4 style="margin-top: 0; margin-bottom: 1rem;">Document Statistics</h4>
<div style="margin-top: 0; margin-bottom: 1rem; font-weight: 600; font-size: 1.1em; color: #333;">Document Statistics</div>
<div class="stats-grid" style="display: grid; grid-template-columns: 1fr 1fr; gap: 0.5rem; margin-bottom: 1rem;">
<div class="stat-item">
@@ -161,7 +161,7 @@ class StatusControl extends ControlBase {
</div>
<div class="structure-stats" style="border-top: 1px solid #eee; padding-top: 0.5rem; margin-bottom: 1rem;">
<h5 style="margin: 0 0 0.5rem 0; font-size: 0.9em;">Document Structure</h5>
<div style="margin: 0 0 0.5rem 0; font-size: 0.9em; font-weight: 600; color: #555;">Document Structure</div>
<div style="display: flex; justify-content: space-between; margin-bottom: 0.3rem;">
<span>Paragraphs:</span>

View File

@@ -82,13 +82,13 @@ const MarkitectMain = {
initializeControlPanels: function() {
console.log('🎛️ Initializing enhanced control panels with compass positioning...');
// ContentsControl (Northwest)
// ContentsControl (West)
if (typeof ContentsControl !== 'undefined') {
this.contentsControl = new ContentsControl();
this.contentsControl.config.position = 'nw';
this.contentsControl.config.position = 'w';
this.contentsControl.show();
window.contentsControl = this.contentsControl;
console.log('✅ ContentsControl initialized (Northwest) with enhanced ControlBase');
console.log('✅ ContentsControl initialized (West) with enhanced ControlBase');
}
// StatusControl (East)

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`;

View File

@@ -432,7 +432,7 @@ class DebugControl extends ControlBase {
if (content) {
content.innerHTML = `
<div style="padding: 1rem; font-size: 0.8rem;">
<h4 style="margin-top: 0; margin-bottom: 1rem;">Debug Information</h4>
<div style="margin-top: 0; margin-bottom: 1rem; font-weight: 600; font-size: 1.1em; color: #333;">Debug Information</div>
${this.generateSystemInfoHTML()}
${this.generatePerformanceHTML()}

View File

@@ -68,11 +68,11 @@ class EditControl extends ControlBase {
return this.safeOperation(() => {
return `
<div style="padding: 1rem; font-size: 0.8rem;">
<h4 style="margin-top: 0; margin-bottom: 1rem;">Edit Tools</h4>
<div style="margin-top: 0; margin-bottom: 1rem; font-weight: 600; font-size: 1.1em; color: #333;">Edit Tools</div>
<!-- Document Actions -->
<div class="action-section" style="margin-bottom: 1rem;">
<h5 style="margin: 0 0 0.5rem 0; font-size: 0.9em; color: #666;">Document Actions</h5>
<div style="margin: 0 0 0.5rem 0; font-size: 0.9em; color: #666; font-weight: 600;">Document Actions</div>
<button onclick="this.closest('.edit-control').editControl.printDocument()"
style="width: 100%; padding: 0.5rem; margin-bottom: 0.3rem; background: #28a745; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 0.8rem;">
@@ -97,7 +97,7 @@ class EditControl extends ControlBase {
<!-- Navigation Tools -->
<div class="navigation-section" style="margin-bottom: 1rem;">
<h5 style="margin: 0 0 0.5rem 0; font-size: 0.9em; color: #666;">Navigation</h5>
<div style="margin: 0 0 0.5rem 0; font-size: 0.9em; color: #666; font-weight: 600;">Navigation</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 0.3rem;">
<button onclick="this.closest('.edit-control').editControl.scrollToTop()"
@@ -119,7 +119,7 @@ class EditControl extends ControlBase {
<!-- Text Tools -->
<div class="text-section" style="margin-bottom: 1rem;">
<h5 style="margin: 0 0 0.5rem 0; font-size: 0.9em; color: #666;">Text Tools</h5>
<div style="margin: 0 0 0.5rem 0; font-size: 0.9em; color: #666; font-weight: 600;">Text Tools</div>
<button onclick="this.closest('.edit-control').editControl.showFindReplace()"
style="width: 100%; padding: 0.4rem; margin-bottom: 0.3rem; background: #ffc107; color: #000; border: none; border-radius: 3px; cursor: pointer; font-size: 0.7rem;">
@@ -146,7 +146,7 @@ class EditControl extends ControlBase {
<!-- Markdown Tools -->
<div class="markdown-section" style="margin-bottom: 1rem;">
<h5 style="margin: 0 0 0.5rem 0; font-size: 0.9em; color: #666;">Markdown Tools</h5>
<div style="margin: 0 0 0.5rem 0; font-size: 0.9em; color: #666; font-weight: 600;">Markdown Tools</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 0.3rem;">
<button onclick="this.closest('.edit-control').editControl.insertMarkdown('**', '**', 'Bold text')"
@@ -240,7 +240,7 @@ class EditControl extends ControlBase {
`;
exportMenu.innerHTML = `
<h4 style="margin-top: 0;">Export Document</h4>
<div style="margin-top: 0; font-weight: 600; font-size: 1.1em; color: #333; margin-bottom: 1rem;">Export Document</div>
<button onclick="this.parentElement.exportAsHTML()" style="display: block; width: 100%; padding: 0.5rem; margin-bottom: 0.3rem; background: #28a745; color: white; border: none; border-radius: 3px; cursor: pointer;">
Export as HTML
</button>

View File

@@ -132,7 +132,7 @@ class StatusControl extends ControlBase {
return `
<div style="padding: 1rem; font-size: 0.8rem;">
<h4 style="margin-top: 0; margin-bottom: 1rem;">Document Statistics</h4>
<div style="margin-top: 0; margin-bottom: 1rem; font-weight: 600; font-size: 1.1em; color: #333;">Document Statistics</div>
<div class="stats-grid" style="display: grid; grid-template-columns: 1fr 1fr; gap: 0.5rem; margin-bottom: 1rem;">
<div class="stat-item">
@@ -161,7 +161,7 @@ class StatusControl extends ControlBase {
</div>
<div class="structure-stats" style="border-top: 1px solid #eee; padding-top: 0.5rem; margin-bottom: 1rem;">
<h5 style="margin: 0 0 0.5rem 0; font-size: 0.9em;">Document Structure</h5>
<div style="margin: 0 0 0.5rem 0; font-size: 0.9em; font-weight: 600; color: #555;">Document Structure</div>
<div style="display: flex; justify-content: space-between; margin-bottom: 0.3rem;">
<span>Paragraphs:</span>