CALCULATION BASED RESPONSIVE SYSTEM
Ecosistema completo para organización y gestión de contenido con interfaces espaciales
🎯 Calculation-Based Responsive System Enhancement
Session: 1.3
Date: November 14, 2025
Status: 📋 Planning
Priority: 🔥 Critical Foundation
Vision: Platform-Agnostic Responsive Design
Core Philosophy
Instead of relying on CSS units (%, vw, vh, rem) which are platform-specific, we use mathematical calculations based on container dimensions. This enables:
- ✅ Perfect Flutter/React Native export - numeric calculations work everywhere
- ✅ Predictable behavior - no CSS quirks or browser differences
- ✅ Single canvas - one design adapts to all breakpoints automatically
- ✅ Intelligent conversions - "Fill Width" button calculates exact pixels
Current Architecture (What We Have)
✅ Already Implemented
Our ResponsiveWidgetPropsDirective already has:
// libs/widgets/src/lib/directives/responsive-widget-props.directive.ts:143-179
convertToPixels(value: string | number, dimensionType: 'width' | 'height'): number {
if (typeof value === 'number') return value;
const numValue = parseFloat(value);
if (isNaN(numValue)) return 0;
if (value.includes('px')) return numValue;
const containerRect = this.getContainerRect();
if (value.includes('%')) {
return dimensionType === 'width'
? (numValue / 100) * containerRect.width
: (numValue / 100) * containerRect.height;
}
if (value.includes('vw')) {
return (numValue / 100) * window.innerWidth;
}
if (value.includes('vh')) {
return (numValue / 100) * window.innerHeight;
}
// rem and em conversions...
return numValue;
}
✅ This is BRILLIANT! The foundation is already there.
🎯 Enhancement Goals
Phase 1: Separate Width/Height Units (Backward Compatible)
Problem: Current Size interface uses single units for both dimensions:
export interface Size {
width: number | string;
height: number | string;
units?: Units; // ❌ Single unit for both
}
Solution: Add optional per-dimension units while maintaining backward compatibility:
export interface Size {
width: number | string;
height: number | string;
// Legacy (still supported)
units?: Units; // Falls back to this if widthUnits/heightUnits not set
// New (preferred)
widthUnits?: Units;
heightUnits?: Units;
}
Migration Strategy:
- Keep
unitsas fallback (no breaking changes) - Add
widthUnitsandheightUnitsas optional - Update
ResponsiveWidgetPropsDirectiveto check both - Migrate widgets one by one (starting with divider)
Phase 2: Intelligent "Fill Width" Button
Feature: Add button to properties panel that calculates container width in pixels.
// New button in widget-size.component.ts
fillWidth(): void {
const containerWidth = this.getContainerWidth(); // From directive
this.updateSize('width', containerWidth);
this.updateUnits('px');
}
fillHeight(): void {
const containerHeight = this.getContainerHeight();
this.updateSize('height', containerHeight);
this.updateUnits('px');
}
UI Enhancement:
<div class="property-field">
<label for="size-width">Ancho</label>
<div class="input-with-actions">
<input type="number" id="size-width" [(ngModel)]="width" />
<button (click)="fillWidth()" title="Llenar ancho del contenedor">
<i class="fa fa-arrows-h"></i>
</button>
</div>
</div>
Phase 3: Enhanced Breakpoint System
Current: Already excellent! ✅
<select class="breakpoint-selector">
<option value="base">Base (Móvil)</option>
<option value="sm">SM (640px+)</option>
<option value="md">MD (768px+)</option>
<option value="lg">LG (1024px+)</option>
<option value="xl">XL (1280px+)</option>
<option value="2xl">2XL (1536px+)</option>
</select>
Enhancement: Add visual preview panel showing multiple breakpoints simultaneously.
<div class="devices-grid">
<div class="device-preview" data-breakpoint="base">
<div class="device-frame mobile-small">
<div class="device-screen">
<!-- Rendered canvas at 375x667 -->
</div>
</div>
<span>Móvil pequeño (375x667)</span>
</div>
<div class="device-preview" data-breakpoint="sm">
<div class="device-frame mobile-large">
<div class="device-screen">
<!-- Rendered canvas at 640x960 -->
</div>
</div>
<span>Móvil grande (640x960)</span>
</div>
<!-- Additional breakpoints... -->
</div>
📊 Calculation System Architecture
Storage Format (JSON)
{
"id": "widget-123",
"type": "divider",
"size": {
"base": {
"width": 360,
"height": 2,
"widthUnits": "px",
"heightUnits": "px"
},
"md": {
"width": 720,
"height": 2,
"widthUnits": "px",
"heightUnits": "px"
}
}
}
Runtime Calculation
// When rendering widget
const containerWidth = 1024; // Current container width
const widgetWidth = 360; // Stored value
const actualPixels = widgetWidth; // Direct use - no conversion needed!
// For export to Flutter
Widget buildDivider() {
return Container(
width: 360.0, // ✅ Direct numeric value
height: 2.0,
color: Color(0xFFDF4949),
);
}
Key Benefit: Numeric values work across ALL platforms!
🚀 Implementation Plan
Step 1: Update Core Models (Non-Breaking)
File: libs/libs-foundational/core-models/src/lib/types/size.types.ts
export interface Size {
width: number | string;
height: number | string;
minWidth: number | string;
minHeight: number | string;
maxWidth?: number | string;
maxHeight?: number | string;
responsive: boolean;
// Legacy (backward compatible)
units?: Units;
// New (preferred) - per-dimension units
widthUnits?: Units;
heightUnits?: Units;
}
Step 2: Update ResponsiveWidgetPropsDirective
File: libs/widgets/src/lib/directives/responsive-widget-props.directive.ts
getComputedStyles(): Record<string, string> {
const size = getValueForBreakpoint(this.widget.size, this.activeBreakpoint);
// Check for per-dimension units (new) or fallback to legacy units
const widthUnit = size.widthUnits || size.units || 'px';
const heightUnit = size.heightUnits || size.units || 'px';
// Calculate width
if (typeof size.width === 'number') {
styles['width'] = this.formatWithUnit(size.width, widthUnit);
} else {
styles['width'] = size.width as string;
}
// Calculate height
if (typeof size.height === 'number') {
styles['height'] = this.formatWithUnit(size.height, heightUnit);
} else {
styles['height'] = size.height as string;
}
return styles;
}
private formatWithUnit(value: number, unit: Units): string {
if (unit === 'px') return `${value}px`;
if (unit === '%') return `${value}%`;
// ... other units
return `${value}px`; // Default
}
Step 3: Update WidgetSizeComponent
File: libs/widgets/src/lib/properties-components/widget-size.component.ts
<!-- Enhanced template with per-dimension units -->
<div class="property-row">
<div class="property-field">
<label>Ancho</label>
<div class="input-with-actions">
<input type="number" [(ngModel)]="width" />
<select [(ngModel)]="widthUnits">
<option value="px">px</option>
<option value="%">%</option>
<option value="auto">auto</option>
</select>
<button (click)="fillWidth()" title="Llenar ancho">
<i class="fa fa-arrows-h"></i>
</button>
</div>
</div>
<div class="property-field">
<label>Alto</label>
<div class="input-with-actions">
<input type="number" [(ngModel)]="height" />
<select [(ngModel)]="heightUnits">
<option value="px">px</option>
<option value="%">%</option>
<option value="auto">auto</option>
</select>
<button (click)="fillHeight()" title="Llenar alto">
<i class="fa fa-arrows-v"></i>
</button>
</div>
</div>
</div>
Step 4: Widget-by-Widget Migration
Priority Order (Start with structural elements):
- ✅ Divider Widget - Simple, good test case
- ✅ Spacer Widget - Similar to divider
- 📦 Container Widget - Critical structural element
- 📦 Card Widget - Common layout component
- 📦 Text Widget - Most used widget
- 📦 Button Widget - Interactive element
- 📦 Image Widget - Media handling
- 📦 All remaining widgets - One by one
Migration Checklist per Widget:
- Update default config to use
widthUnits/heightUnits - Test responsive behavior across all breakpoints
- Verify export to JSON maintains correct format
- Test drag/resize/move operations
- Update property groups if needed
- Add "Fill Width/Height" buttons where appropriate
🧪 Testing Strategy
1. Unit Tests
describe('ResponsiveWidgetPropsDirective', () => {
it('should use widthUnits when available', () => {
const size = { width: 100, widthUnits: '%', heightUnits: 'px' };
const styles = directive.getComputedStyles();
expect(styles.width).toBe('100%');
});
it('should fall back to legacy units', () => {
const size = { width: 100, units: 'px' };
const styles = directive.getComputedStyles();
expect(styles.width).toBe('100px');
});
});
2. Visual Regression Tests
- Screenshot each widget at all 6 breakpoints
- Compare before/after migration
- Ensure pixel-perfect match
3. Export Tests
describe('Flutter Export', () => {
it('should export divider with numeric values', () => {
const flutter = exportToFlutter(dividerWidget);
expect(flutter).toContain('width: 360.0');
expect(flutter).not.toContain('%'); // No CSS units
});
});
🎨 UI/UX Enhancements
Enhanced Property Panel
┌─────────────────────────────────────┐
│ 📐 Tamaño │
├─────────────────────────────────────┤
│ Ancho │
│ ┌────┬─────┬────┐ │
│ │ 100│ [px▼] │[↔]│ │
│ └────┴─────┴────┘ │
│ │
│ Alto │
│ ┌────┬─────┬────┐ │
│ │ 50│ [px▼] │[↕]│ │
│ └────┴─────┴────┘ │
│ │
│ ☑ Mantener proporciones │
│ ☑ Bloquear relación de aspecto │
└─────────────────────────────────────┘
New Features:
- [↔] = Fill Width Button (calculates container width)
- [↕] = Fill Height Button (calculates container height)
- Per-dimension units = Independent control
- Visual feedback = Show calculated pixel values
Responsive Preview Panel
┌────────────────────────────────────────────┐
│ 📱 Vista Responsiva │
├────────────────────────────────────────────┤
│ Breakpoint actual: [MD (768px+) ▼] │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 📱 Base │ │ 📱 SM │ │ 💻 MD │ │
│ │ 375x667 │ │ 640x960 │ │ 768x1024 │ │
│ │ [preview]│ │ [preview]│ │ [preview]│ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │
│ Todos los tamaños se ajustan │
│ automáticamente ✨ │
└────────────────────────────────────────────┘
📦 Export System
Flutter Export Example
// Generated from GoGrab Space
import 'package:flutter/material.dart';
class GeneratedPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
// Responsive breakpoint detection
final width = constraints.maxWidth;
final isMobile = width < 640;
final isTablet = width >= 640 && width < 1024;
return Container(
child: Column(
children: [
// Divider widget
Container(
width: isMobile ? 360.0 : 720.0, // ✅ Calculated values
height: 2.0,
color: Color(0xFFDF4949),
),
// More widgets...
],
),
);
},
);
}
}
React Native Export Example
// Generated from GoGrab Space
import React from 'react';
import { View, useWindowDimensions } from 'react-native';
export default function GeneratedScreen() {
const { width } = useWindowDimensions();
const isMobile = width < 640;
return (
<View>
{/* Divider widget */}
<View style={{
width: isMobile ? 360 : 720, // ✅ Numeric calculations
height: 2,
backgroundColor: '#DF4949',
}} />
{/* More widgets... */}
</View>
);
}
Key Benefit: Pure calculations = platform-agnostic! 🎉
⚠️ Critical Considerations
1. Don't Break Existing Widgets
- Backward compatibility is ESSENTIAL
- Keep
unitsproperty working - Migrate one widget at a time
- Test exhaustively before moving forward
2. Maintain Responsive Excellence
The editor-web-lib team spent significant time perfecting:
- ✅
ResponsiveWidgetPropsDirective - ✅
ResponsiveDraggableDirective - ✅
ResponsiveResizableDirective - ✅ Breakpoint cascade system
- ✅ Single-canvas multi-breakpoint editing
DO NOT break any of this! Only enhance.
3. Performance Considerations
- Cache calculated values
- Use signals for reactive updates
- Minimize recalculations
- Profile before/after changes
📈 Success Metrics
Phase 1 Complete When:
-
Sizeinterface updated (backward compatible) -
ResponsiveWidgetPropsDirectivehandles per-dimension units - All existing widgets still work perfectly
- Unit tests pass (100% coverage)
Phase 2 Complete When:
- "Fill Width/Height" buttons implemented
-
WidgetSizeComponentsupports per-dimension units - Visual feedback shows calculated pixel values
- User can easily toggle between units per dimension
Phase 3 Complete When:
- Divider widget fully migrated
- Spacer widget fully migrated
- Container widget fully migrated
- All structural widgets use new system
- Export to Flutter works perfectly
- Export to React Native works perfectly
🎯 Next Steps
- Review this plan with the team
- Update Size interface (non-breaking change)
- Enhance ResponsiveWidgetPropsDirective (add per-dimension unit support)
- Test with divider widget (simplest case)
- Add "Fill Width" button to property panel
- Migrate remaining widgets one by one
- Implement export system for Flutter/React Native
🔗 Related Documentation
- Session 1.1 - Foundation
- Session 1.2 - Achievements
- Widget Builder System
- Responsive Widget Directives
Status: 📋 Planning Phase
Next Review: After Phase 1 implementation
Team: editor-web-lib + AI Enhancement