WIDGET SIZING FIX

Comprehensive guidelines for AI-assisted development within the Editor platform

🎯 Widget Sizing Fix - Making Widgets Resizable

The Problem

Widgets were not resizable when dragging the resize handles. The button was stuck at its initial size.

Root Cause

// ❌ WRONG: Hardcoded dimensions
containerStyles = computed(() => ({
  width: '100%',   // ❌ Always fills parent
  height: '100%',  // ❌ Prevents resizing!
  // ... other styles
}));

Why this breaks resizing:

  1. WidgetHostComponent uses libResponsiveResizable directive
  2. Directive sets width/height on the host element
  3. Widget's containerStyles() overrides with 100%
  4. Result: Widget always fills container, resize handles do nothing

The Solution

NEVER set width/height in widget's containerStyles!

// ✅ CORRECT: Let WidgetHostComponent control size
containerStyles = computed(() => ({
  // ❌ NO width
  // ❌ NO height
  position: 'relative',
  'box-sizing': 'border-box',
  display: 'flex',
  'flex-direction': 'column',
  // ... other styles (font, colors, etc.)
}));

How Widget Sizing Works

Architecture

┌─────────────────────────────────────┐
│  WidgetHostComponent                │
│  (applies size via directives)      │
│  ┌───────────────────────────────┐  │
│  │  libResponsiveDraggable       │  │
│  │  libResponsiveResizable       │  │
│  │  → Sets width/height on       │  │
│  │    host element               │  │
│  └───────────────────────────────┘  │
│  ┌───────────────────────────────┐  │
│  │  ButtonWidgetComponent        │  │
│  │  (just renders content)       │  │
│  │  ❌ Don't override size!      │  │
│  └───────────────────────────────┘  │
└─────────────────────────────────────┘

Data Flow

  1. User drags resize handle
  2. libResponsiveResizable directive catches event
  3. Directive calculates new width/height
  4. Directive applies: element.style.width = '200px'
  5. Widget updates via widget.size property
  6. Flash-persistence saves new size
  7. Widget component MUST NOT override with 100%!

Pattern for All Widgets

DO ✅

// Widget Component
containerStyles = computed(() => ({
  // ✅ Layout properties
  position: 'relative',
  display: 'flex',
  'box-sizing': 'border-box',
  
  // ✅ Visual properties
  'background-color': this.getBgColor(),
  border: this.getBorder(),
  'border-radius': '8px',
  
  // ✅ Typography (if applicable)
  'font-family': this.getFontFamily(),
  'font-size': this.getFontSize(),
  
  // ✅ Cursor
  cursor: this.isPreview() ? 'default' : 'pointer',
  
  // ❌ NO width
  // ❌ NO height
}));

DON'T ❌

// ❌ NEVER DO THIS:
containerStyles = computed(() => ({
  width: '100%',        // ❌ Breaks resizing
  height: '100%',       // ❌ Breaks resizing
  width: '200px',       // ❌ Overrides user's size
  minWidth: '100px',    // ⚠️ Use widget.size.minWidth instead
  maxWidth: '500px',    // ⚠️ Use widget.size.maxWidth instead
}));

Min/Max Width Handling

If your widget needs min/max constraints:

Option 1: Use widget.size properties (Recommended)

// In widget definition or property panel
widget.size = {
  width: 200,
  height: 100,
  minWidth: 50,     // ✅ Respected by resize directive
  maxWidth: 500,    // ✅ Respected by resize directive
  minHeight: 30,
  maxHeight: 300,
  responsive: true
};

Option 2: Apply constraints conditionally

// Only if NOT being resized by user
containerStyles = computed(() => {
  const styles: Record<string, string> = {
    position: 'relative',
    display: 'flex'
  };
  
  // ⚠️ Only set min/max if widget defines them
  const size = this.widget()?.size;
  if (size?.minWidth) {
    styles['min-width'] = `${size.minWidth}px`;
  }
  if (size?.maxWidth) {
    styles['max-width'] = `${size.maxWidth}px`;
  }
  
  return styles;
});

Wrapper Elements

Inner Wrappers CAN fill container

<!-- ✅ CORRECT Structure -->
<div class="widget-container" [ngStyle]="containerStyles()">
  <!-- ❌ NO width/height on container -->
  
  <div class="content-wrapper" style="width: 100%; height: 100%;">
    <!-- ✅ Inner wrapper CAN be 100% -->
    <button>Content</button>
  </div>
</div>

Why this works:


Testing Resizing

Test Checklist

For EVERY widget, verify:

  1. ✅ Can resize width by dragging right edge
  2. ✅ Can resize height by dragging bottom edge
  3. ✅ Can resize both by dragging corner
  4. ✅ Size persists after page refresh (flash-persistence)
  5. ✅ Responsive - size changes work per breakpoint
  6. ✅ Min/Max respected (if defined)
  7. ✅ Content scales properly with size

Visual Test

Before fix:

┌────────────────┐
│  [Widget]      │ ← Drag handle
│  Size: 200x100 │    does nothing ❌
└────────────────┘

After fix:

┌────────────────┐        ┌──────────────────────────┐
│  [Widget]      │ ───→   │  [Widget]                │ ✅
│  Size: 200x100 │ drag   │  Size: 300x100           │
└────────────────┘        └──────────────────────────┘

Apply This Pattern To

ALL widgets must follow this pattern:

High Priority (User-facing)

Medium Priority

Low Priority


Common Mistakes to Avoid

Mistake 1: Setting width on wrong element

// ❌ WRONG
<div [ngStyle]="{ width: '100%' }">  // Sets on container
  <div class="content">...</div>
</div>

// ✅ CORRECT
<div [ngStyle]="containerStyles()">  // No width
  <div class="content" style="width: 100%">...</div>  // Sets on content
</div>

Mistake 2: Inline styles override computed

<!-- ❌ WRONG -->
<div 
  class="widget-container"
  style="width: 100%; height: 100%"  <!-- Inline overrides everything! -->
  [ngStyle]="containerStyles()">

Mistake 3: CSS classes set dimensions

// ❌ WRONG
.widget-container {
  width: 100%;   // Class overrides directive
  height: 100%;
}

// ✅ CORRECT
.widget-container {
  // No width/height
  display: flex;
  flex-direction: column;
}

Summary

The Golden Rule

Widgets render content, WidgetHost controls size.

Quick Checklist


Last Updated: December 6, 2024
Status: ✅ ButtonWidget FIXED - Pattern defined
Next: Apply to remaining widgets systematically