9fbc35dbd9
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
322 lines
11 KiB
Markdown
322 lines
11 KiB
Markdown
# 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):**
|
||
```javascript
|
||
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):**
|
||
```javascript
|
||
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):**
|
||
```javascript
|
||
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: auto` or `overflow: scroll`
|
||
- The controls at `top: 4px; right: 4px` with `z-index: 100` can be covered or hidden by other elements
|
||
|
||
---
|
||
|
||
### 4. CSS Widget Styling
|
||
|
||
**File:** `style.css`
|
||
|
||
**Key Widget CSS:**
|
||
```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:
|
||
|
||
1. **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.
|
||
|
||
2. **Parent Stacking Context:** The widget element's positioning and z-index hierarchy may cause the buttons to be layered differently depending on scroll state.
|
||
|
||
3. **Hover State Lost:** When scrollbars appear, the widget's visual bounds change, and hover detection may fail to show/hide buttons consistently.
|
||
|
||
4. **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
|
||
|
||
1. **No DOM Overflow:** Handles and controls are outside widgets, don't contribute to widget dimensions
|
||
2. **Clean Widget DOM:** Widgets only contain their actual content
|
||
3. **Consistent Visibility:** Overlays are positioned relative to grid container, not affected by widget scrolling
|
||
4. **Proper Z-stacking:** True layers with proper z-index control
|
||
5. **Easier Positioning:** Overlay containers can be precisely positioned relative to grid, and handles/controls positioned relative to overlay
|
||
6. **No Scrollbar Interference:** Buttons and handles won't be hidden by scrollbars
|
||
|
||
### Implementation Plan
|
||
|
||
1. **Create overlay container management in DashboardManager:**
|
||
- Create and maintain `#rpg-resize-handles-overlay` container
|
||
- Create and maintain `#rpg-edit-controls-overlay` container
|
||
- Both positioned absolutely, covering entire grid, `pointer-events: none` by default
|
||
|
||
2. **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
|
||
|
||
3. **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
|
||
|
||
4. **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
|
||
|
||
5. **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:**
|
||
```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:**
|
||
1. Widget has `display: flex; flex-direction: column`
|
||
2. Widget has `max-height: 100%`
|
||
3. If flex children (content + handles + controls) exceed max-height, scrollbars appear
|
||
4. The `overflow: visible` doesn't prevent scrollbars - `max-height` triggers them
|
||
|
||
**Solution:**
|
||
- Moving handles/controls outside widget DOM solves the flex child height problem
|
||
- Keep `overflow: visible` for clean content overflow
|
||
- Remove or adjust `max-height` constraint
|
||
|
||
---
|
||
|
||
## Event Handler Interaction Points
|
||
|
||
### Resize Handler
|
||
- **Source:** `resizeHandler.js`, line 77 in `initWidget()`
|
||
- **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-handle` with `closest()`
|
||
- **Impact:** Still works (CSS class-based detection)
|
||
- **Change:** None needed - will still detect overlaid handles
|
||
|
||
### Edit Mode Manager
|
||
- **Source:** `editModeManager.js`, lines 325-373 in `addWidgetControls()`
|
||
- **Current:** Appends controls to widget element
|
||
- **Change:** Accept overlay container reference, append there instead
|
||
|
||
### Dashboard Manager
|
||
- **Source:** `dashboardManager.js`, lines 631-703 in `renderWidget()`
|
||
- **Current:** Calls `resizeHandler.initWidget()` and `editManager.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:
|
||
1. Contribute to widget dimensions via negative positioning tricks
|
||
2. Trigger scrollbars when combined with flex layout and `max-height`
|
||
3. Cause edit/delete buttons to be hidden or inaccessible
|
||
4. 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
|