Files
rpg-companion-sillytavern/RESIZE_HANDLES_INVESTIGATION.md
T
Lucas 'Paperboy' Rose-Winters 9fbc35dbd9 refactor(dashboard): move controls to overlays and fix mobile/positioning issues
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
2025-10-29 19:07:01 +11:00

11 KiB
Raw Blame History

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: 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:

.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.


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:

.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