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:
WidgetHostComponentuseslibResponsiveResizabledirective- Directive sets width/height on the host element
- Widget's
containerStyles()overrides with100% - 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
- User drags resize handle
libResponsiveResizabledirective catches event- Directive calculates new width/height
- Directive applies:
element.style.width = '200px' - Widget updates via
widget.sizeproperty - Flash-persistence saves new size
- 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:
- Container has NO width/height → WidgetHost controls it
- Inner wrapper has 100% → Fills whatever container is
- Result: Resizing works, content fills widget
Testing Resizing
Test Checklist
For EVERY widget, verify:
- ✅ Can resize width by dragging right edge
- ✅ Can resize height by dragging bottom edge
- ✅ Can resize both by dragging corner
- ✅ Size persists after page refresh (flash-persistence)
- ✅ Responsive - size changes work per breakpoint
- ✅ Min/Max respected (if defined)
- ✅ 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)
- ✅ ButtonWidget (FIXED)
- ⏳ TextWidget
- ⏳ ImageWidget
- ⏳ CardWidget
- ⏳ InputWidget
- ⏳ SelectWidget
Medium Priority
- ⏳ DividerWidget
- ⏳ LinkWidget
- ⏳ GalleryWidget
- ⏳ HeroWidget
Low Priority
- ⏳ PricingWidget
- ⏳ ShapeWidget
- ⏳ All other widgets
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
- ❌ NO
width: '100%'in containerStyles - ❌ NO
height: '100%'in containerStyles - ❌ NO inline
style="width: 100%"on root element - ❌ NO CSS class setting width/height on root element
- ✅ Inner wrappers CAN be 100%
- ✅ Let WidgetHostComponent handle sizing
- ✅ Use widget.size for min/max constraints
- ✅ Test resizing works in all directions
Last Updated: December 6, 2024
Status: ✅ ButtonWidget FIXED - Pattern defined
Next: Apply to remaining widgets systematically