BREAKING CHANGES: - Resize handles and edit controls now render in separate overlay containers - This prevents widget overflow and scrollbar issues Major Changes: Overlay System Refactor: - Create overlay containers for resize handles and edit controls - Append handles/controls to overlays instead of widget DOM - Fix position calculations using offsetLeft/Top instead of getBoundingClientRect - Add position sync after resize/drag/reposition operations - Add cleanup methods when switching tabs Mobile Fixes: - Add pointer/touch event support for Add Widget button - Fix file upload trigger for iOS/Android compatibility - Move modals to document.body with flex centering - Fix modal button font-sizes (rem-based instead of vw) - Add mobile-responsive styling for widget dialog Bug Fixes: - Fix getActiveTabId() calls (use activeTabId property instead) - Fix file input disabled state (exclude type="file" from edit mode disable) - Fix Add Widget modal registry.getAll() destructuring (objects not arrays) - Fix edit/delete button hover (keep visible when hovering buttons) - Fix reset layout to restore deleted widgets (regenerate default layout) UI Improvements: - No more scrollbars from resize handles - Consistent button visibility in edit mode - Touch-friendly button sizes on mobile (44px min-height) - Single-column widget grid on mobile - Proper z-index stacking for overlays Files Changed: - dashboardManager.js: Overlay container management, sync methods - resizeHandler.js: Append to overlay, update positioning - editModeManager.js: Append to overlay, hover behavior, sync/cleanup - dashboardIntegration.js: Mobile touch events, file upload, modal fixes - dashboardTemplate.html: File input accessibility - style.css: Modal button font-sizes, mobile optimizations - RESIZE_HANDLES_INVESTIGATION.md: Technical investigation documentation
11 KiB
Resize Handle Overlay Issue - Investigation Report
Problem Summary
The resize handles in edit mode are being rendered INSIDE the widget container DOM, causing:
- Widgets to stretch and overflow their grid bounds
- Scrollbars to appear unexpectedly
- Edit/delete buttons to be hidden or inconsistently visible
- Layout overflow issues
The handles use negative positioning (top: -6px, left: -3px) to extend outside widget bounds, but being children of the widget element causes them to contribute to the widget's offsetHeight and offsetWidth, which creates unwanted scrollbars and overflow.
Investigation Findings
1. Where Resize Handles Are Created and Appended
File: src/systems/dashboard/resizeHandler.js
Key Code (Lines 172-215):
createResizeHandles() {
const container = document.createElement('div');
container.className = 'resize-handles';
container.style.position = 'absolute';
container.style.inset = '0';
container.style.pointerEvents = 'none';
// Create 8 handles (4 corners + 4 edges)
Object.entries(this.handleTypes).forEach(([handleType, cursor]) => {
const handle = document.createElement('div');
handle.className = `resize-handle resize-handle-${handleType}`;
// ... positioning ...
handle.style.top = '-6px'; // Negative positioning
handle.style.left = '-3px'; // Negative positioning
handle.style.zIndex = '100';
container.appendChild(handle);
});
return container;
}
Appended At (Line 77):
initWidget(element, widget, onResizeEnd, constraints = {}) {
const handles = this.createResizeHandles();
element.appendChild(handles); // <-- APPENDED INSIDE WIDGET
// ...
}
Problem: The handles container is appended directly to the widget element (element.appendChild(handles)), making it a child of .rpg-widget.
2. Where Edit/Delete Buttons Are Created
File: src/systems/dashboard/editModeManager.js
Key Code (Lines 325-373):
addWidgetControls(element, widgetId) {
const controls = document.createElement('div');
controls.className = 'widget-edit-controls';
controls.style.position = 'absolute';
controls.style.top = '4px';
controls.style.right = '4px';
controls.style.display = 'flex';
controls.style.gap = '4px';
controls.style.zIndex = '100';
controls.style.opacity = '0';
controls.style.transition = 'opacity 0.2s';
// Create settings and delete buttons
const settingsBtn = this.createControlButton('⚙', 'Settings');
const deleteBtn = this.createControlButton('×', 'Delete');
controls.appendChild(settingsBtn);
controls.appendChild(deleteBtn);
element.appendChild(controls); // <-- APPENDED INSIDE WIDGET
// ...
}
Problem: Like the resize handles, the edit controls are appended inside the widget element as a child.
3. Current DOM Structure
<div class="rpg-widget" id="widget-widget-usermood">
<!-- Widget content (rendered by widget definition) -->
<div class="widget-content">...</div>
<!-- Resize handles INSIDE widget (PROBLEM) -->
<div class="resize-handles" style="position: absolute; inset: 0; pointer-events: none;">
<div class="resize-handle resize-handle-nw" style="top: -6px; left: -3px; ..."></div>
<!-- 7 more handles... -->
</div>
<!-- Edit controls INSIDE widget (PROBLEM) -->
<div class="widget-edit-controls" style="position: absolute; top: 4px; right: 4px; ...">
<button>⚙</button>
<button>×</button>
</div>
</div>
Why This Causes Issues:
- Even though handles have
position: absolute, they're still part of the DOM flow calculation - Negative positioning extends them outside the widget visually, but the browser still includes them in overflow calculations
- This causes scrollbars when the widget container has
overflow: autooroverflow: scroll - The controls at
top: 4px; right: 4pxwithz-index: 100can be covered or hidden by other elements
4. CSS Widget Styling
File: style.css
Key Widget CSS:
.rpg-widget {
box-sizing: border-box;
overflow: visible; /* Allow resize handles to extend beyond widget bounds */
display: flex;
flex-direction: column;
max-height: 100%; /* Prevent content from overflowing grid cell */
/* ... other styles ... */
}
/* Hide resize handles by default */
.resize-handles {
opacity: 0;
pointer-events: none;
transition: opacity 0.2s;
}
/* Show resize handles in edit mode */
.edit-mode .resize-handles {
opacity: 1;
pointer-events: auto;
}
/* Hide resize handles when widgets are locked */
.widgets-locked .resize-handles {
opacity: 0 !important;
pointer-events: none !important;
}
Current State:
- Widget has
overflow: visible- correct for allowing handles to show - But the negative positioning of handles inside the widget still causes layout issues
- The
max-height: 100%on flex column can cause scrollbars if child heights exceed parent
5. Why Buttons Are Inconsistently Visible
The edit/delete buttons are positioned inside the widget at top: 4px; right: 4px; with z-index: 100. Issues arise:
-
Scrollbars Overlap: If the widget develops a scrollbar, the buttons are positioned relative to the widget's content box, not the visible area, so they can be hidden by the scrollbar.
-
Parent Stacking Context: The widget element's positioning and z-index hierarchy may cause the buttons to be layered differently depending on scroll state.
-
Hover State Lost: When scrollbars appear, the widget's visual bounds change, and hover detection may fail to show/hide buttons consistently.
-
Absolute Positioning Within Scrollable Parent: Buttons positioned absolutely within a widget that can scroll create unpredictable rendering.
Recommended Approach: Make Handles & Buttons True Overlays
Strategy
Move resize handles and edit controls outside the widget DOM to a shared overlay container at the dashboard/grid level.
Current (Problematic) Structure:
<div class="rpg-widget">
<widget-content/>
<resize-handles/>
<edit-controls/>
</div>
Target (Fixed) Structure:
<div id="rpg-dashboard-grid">
<div class="rpg-widget">
<widget-content/>
</div>
<div id="rpg-widget-overlays">
<resize-handles for="widget-1"/>
<resize-handles for="widget-2"/>
<edit-controls for="widget-1"/>
<edit-controls for="widget-2"/>
</div>
</div>
Benefits
- No DOM Overflow: Handles and controls are outside widgets, don't contribute to widget dimensions
- Clean Widget DOM: Widgets only contain their actual content
- Consistent Visibility: Overlays are positioned relative to grid container, not affected by widget scrolling
- Proper Z-stacking: True layers with proper z-index control
- Easier Positioning: Overlay containers can be precisely positioned relative to grid, and handles/controls positioned relative to overlay
- No Scrollbar Interference: Buttons and handles won't be hidden by scrollbars
Implementation Plan
-
Create overlay container management in DashboardManager:
- Create and maintain
#rpg-resize-handles-overlaycontainer - Create and maintain
#rpg-edit-controls-overlaycontainer - Both positioned absolutely, covering entire grid,
pointer-events: noneby default
- Create and maintain
-
Modify ResizeHandler:
- Change
initWidget()to NOT append handles to widget element - Instead, create handles and append to
#rpg-resize-handles-overlay - Position handles using absolute positioning relative to overlay container
- Calculate positions based on widget's grid position + negative offsets
- Change
-
Modify EditModeManager:
- Change
addWidgetControls()to NOT append controls to widget element - Instead, create controls and append to
#rpg-edit-controls-overlay - Position controls using absolute positioning relative to overlay container
- Calculate positions based on widget's grid position
- Change
-
Update repositioning logic:
- When widgets are repositioned (drag/resize), update overlay child positions
- On tab switch, show/hide overlay child elements for that tab's widgets
- On widget removal, remove corresponding overlay children
-
CSS updates:
- Add styles for overlay containers
- Add positioning rules for handles and controls within overlays
Key Files Needing Changes
| File | Change | Impact |
|---|---|---|
src/systems/dashboard/resizeHandler.js |
Don't append handles to widget; append to overlay instead | Prevents widget overflow |
src/systems/dashboard/editModeManager.js |
Don't append controls to widget; append to overlay instead | Fixes button visibility |
src/systems/dashboard/dashboardManager.js |
Create/maintain overlay containers; manage overlay children on reposition | Coordinates layout |
style.css |
Add overlay container styles; update handle/control positioning | Visual presentation |
CSS Overflow Issue Analysis
Current .rpg-widget CSS:
.rpg-widget {
overflow: visible; /* Good - allows content overflow */
max-height: 100%; /* Can cause scrollbars if flex children exceed 100% */
display: flex;
flex-direction: column;
}
Why Scrollbars Appear:
- Widget has
display: flex; flex-direction: column - Widget has
max-height: 100% - If flex children (content + handles + controls) exceed max-height, scrollbars appear
- The
overflow: visibledoesn't prevent scrollbars -max-heighttriggers them
Solution:
- Moving handles/controls outside widget DOM solves the flex child height problem
- Keep
overflow: visiblefor clean content overflow - Remove or adjust
max-heightconstraint
Event Handler Interaction Points
Resize Handler
- Source:
resizeHandler.js, line 77 ininitWidget() - Current: Appends handles to widget element
- Change: Accept overlay container reference, append there instead
Drag/Drop Handler
- Source:
dragDrop.js, line 76 checks for.resize-handlewithclosest() - Impact: Still works (CSS class-based detection)
- Change: None needed - will still detect overlaid handles
Edit Mode Manager
- Source:
editModeManager.js, lines 325-373 inaddWidgetControls() - Current: Appends controls to widget element
- Change: Accept overlay container reference, append there instead
Dashboard Manager
- Source:
dashboardManager.js, lines 631-703 inrenderWidget() - Current: Calls
resizeHandler.initWidget()andeditManager.addWidgetControls()with widget element - Change: Pass overlay containers, handle repositioning on layout changes
Summary
The resize handles are being rendered inside the widget container, causing them to:
- Contribute to widget dimensions via negative positioning tricks
- Trigger scrollbars when combined with flex layout and
max-height - Cause edit/delete buttons to be hidden or inaccessible
- Create inconsistent UI behavior
Solution: Create true overlay containers at the grid level, position handles and controls outside the widget DOM, and coordinate their positioning through the DashboardManager lifecycle.
This approach is used in many professional UI frameworks and provides:
- Clean separation of concerns
- Better visual control
- Elimination of overflow/scrollbar issues
- Consistent button visibility
- Proper z-index layering