SMART PROPERTY SYSTEM DESIGN
Comprehensive guidelines for AI-assisted development within the Editor platform
🧠 Smart Property System - Intelligent & Reusable Design
Problem: Current approach requires manual property checking in each widget (NOT REUSABLE!)
Solution: Self-applying property groups with automatic style generation
❌ Current Problem (What You Pointed Out)
Manual Property Checking (ButtonWidget lines 143-254):
// ❌ THIS IS NOT SMART!
containerStyles = computed(() => {
const props = this.widget()?.properties || {};
// Manual checking - NOT REUSABLE!
'font-family': props['fontFamily'] || 'inherit',
'font-size': props['fontSize'] ? `${props['fontSize']}px` : '14px',
'font-weight': props['fontWeight'] || 'normal',
'font-style': props['fontStyle'] || 'normal',
'text-align': props['textAlign'] || 'left',
// ... 20 more manual checks!
});
customButtonStyles = computed(() => {
const props = this.widget()?.properties || {};
const styles: Record<string, string> = {};
// Manual checking AGAIN - DUPLICATE CODE!
if (props['fontFamily']) styles['font-family'] = props['fontFamily'];
if (props['fontSize']) styles['font-size'] = `${props['fontSize']}px`;
// ... MORE manual checks!
return styles;
});
Problems:
- ❌ Not reusable - every widget needs this code
- ❌ Violates DRY - same logic duplicated everywhere
- ❌ Not maintainable - change font property → update 20 widgets
- ❌ Not AI-trainable - too much boilerplate
- ❌ Not modular - can't easily add/remove property groups
✅ Smart Solution (What You Want)
Self-Applying Property Groups
// ✅ SMART: Property group KNOWS how to apply itself!
// 1. Define property group with applicator
export const FONT_PROPERTY_GROUP: PropertyGroupWithApplicator = {
id: 'font',
title: 'Font & Text',
icon: 'text_format',
properties: [
{ key: 'fontFamily', type: 'select', defaultValue: 'inherit' },
{ key: 'fontSize', type: 'number', defaultValue: 14 },
{ key: 'fontWeight', type: 'select', defaultValue: 'normal' },
// ...
],
// ✅ SMART: Applicator converts properties → styles automatically!
applicator: new FontPropertyApplicator()
};
// 2. Use in widget (ONE LINE!)
@Component({...})
export class ButtonWidgetComponent extends BaseWidgetComponent {
// ✅ SMART: Automatically applies font properties!
styles = usePropertyGroups(this.widget, this.activeBreakpoint, [
FONT_PROPERTY_GROUP,
APPEARANCE_PROPERTY_GROUP,
LAYOUT_PROPERTY_GROUP
]);
}
Benefits:
- ✅ Reusable - any widget can use
FONT_PROPERTY_GROUP - ✅ DRY - logic in ONE place
- ✅ Maintainable - change applicator → all widgets updated
- ✅ AI-trainable - clear, simple pattern
- ✅ Modular - add/remove groups easily
🎯 Proposed Architecture
1. Property Group Applicators (Smart Converters)
// Base interface
interface PropertyApplicator {
// Convert properties → CSS styles
apply(properties: Record<string, any>, breakpoint: BreakpointKey): Record<string, string>;
// Get property keys this applicator handles
getHandledKeys(): string[];
// Validate property values
validate(properties: Record<string, any>): ValidationResult;
}
// Font applicator
export class FontPropertyApplicator implements PropertyApplicator {
apply(props: Record<string, any>, breakpoint: BreakpointKey): Record<string, string> {
const styles: Record<string, string> = {};
// Smart conversion with defaults
if (props['fontFamily']) styles['font-family'] = props['fontFamily'];
if (props['fontSize']) styles['font-size'] = `${props['fontSize']}px`;
if (props['fontWeight']) styles['font-weight'] = props['fontWeight'];
if (props['fontStyle']) styles['font-style'] = props['fontStyle'];
if (props['textAlign']) styles['text-align'] = props['textAlign'];
if (props['textDecoration']) styles['text-decoration'] = props['textDecoration'];
if (props['textTransform']) styles['text-transform'] = props['textTransform'];
if (props['lineHeight']) styles['line-height'] = this.formatLineHeight(props['lineHeight']);
if (props['letterSpacing']) styles['letter-spacing'] = `${props['letterSpacing']}px`;
return styles;
}
getHandledKeys(): string[] {
return ['fontFamily', 'fontSize', 'fontWeight', 'fontStyle', 'textAlign',
'textDecoration', 'textTransform', 'lineHeight', 'letterSpacing'];
}
private formatLineHeight(value: any): string {
return typeof value === 'number' ? `${value}` : value;
}
}
// Appearance applicator
export class AppearancePropertyApplicator implements PropertyApplicator {
apply(props: Record<string, any>, breakpoint: BreakpointKey): Record<string, string> {
const styles: Record<string, string> = {};
if (props['backgroundColor']) {
styles['background'] = props['backgroundColor'];
styles['background-image'] = 'none'; // Override gradients
}
if (props['color']) styles['color'] = props['color'];
if (props['borderWidth']) styles['border-width'] = `${props['borderWidth']}px`;
if (props['borderStyle']) styles['border-style'] = props['borderStyle'];
if (props['borderColor']) styles['border-color'] = props['borderColor'];
if (props['borderRadius']) styles['border-radius'] = `${props['borderRadius']}px`;
if (props['opacity']) styles['opacity'] = `${props['opacity']}`;
if (props['boxShadow']) styles['box-shadow'] = props['boxShadow'];
return styles;
}
getHandledKeys(): string[] {
return ['backgroundColor', 'color', 'borderWidth', 'borderStyle',
'borderColor', 'borderRadius', 'opacity', 'boxShadow'];
}
}
// Layout applicator
export class LayoutPropertyApplicator implements PropertyApplicator {
apply(props: Record<string, any>, breakpoint: BreakpointKey): Record<string, string> {
const styles: Record<string, string> = {};
if (props['padding']) styles['padding'] = `${props['padding']}px`;
if (props['margin']) styles['margin'] = `${props['margin']}px`;
if (props['display']) styles['display'] = props['display'];
if (props['flexDirection']) styles['flex-direction'] = props['flexDirection'];
if (props['justifyContent']) styles['justify-content'] = props['justifyContent'];
if (props['alignItems']) styles['align-items'] = props['alignItems'];
if (props['gap']) styles['gap'] = `${props['gap']}px`;
return styles;
}
getHandledKeys(): string[] {
return ['padding', 'margin', 'display', 'flexDirection',
'justifyContent', 'alignItems', 'gap'];
}
}
2. Property Group Registry (Centralized)
@Injectable({ providedIn: 'root' })
export class PropertyApplicatorRegistry {
private applicators = new Map<string, PropertyApplicator>();
constructor() {
// Register built-in applicators
this.register('font', new FontPropertyApplicator());
this.register('appearance', new AppearancePropertyApplicator());
this.register('layout', new LayoutPropertyApplicator());
}
register(groupId: string, applicator: PropertyApplicator): void {
this.applicators.set(groupId, applicator);
}
get(groupId: string): PropertyApplicator | undefined {
return this.applicators.get(groupId);
}
// Apply all registered applicators
applyAll(properties: Record<string, any>, breakpoint: BreakpointKey): Record<string, string> {
const allStyles: Record<string, string> = {};
for (const [groupId, applicator] of this.applicators) {
const groupStyles = applicator.apply(properties, breakpoint);
Object.assign(allStyles, groupStyles);
}
return allStyles;
}
}
3. Smart Widget Hook (Reusable Everywhere!)
// Helper function for widgets
export function usePropertyStyles(
widget: Signal<Widget | undefined>,
breakpoint: Signal<BreakpointKey>,
groupIds: string[] // Which property groups to apply
): Signal<Record<string, string>> {
const applicatorRegistry = inject(PropertyApplicatorRegistry);
return computed(() => {
const w = widget();
if (!w) return {};
const props = w.properties || {};
const bp = breakpoint();
const styles: Record<string, string> = {};
// Apply each property group automatically
for (const groupId of groupIds) {
const applicator = applicatorRegistry.get(groupId);
if (applicator) {
const groupStyles = applicator.apply(props, bp);
Object.assign(styles, groupStyles);
}
}
return styles;
});
}
4. Widget Usage (SIMPLE & CLEAN!)
// ✅ SMART: ButtonWidget using new system
@Component({
selector: 'lib-button-widget',
template: `
<ui-button
[ngStyle]="smartStyles()"
[text]="config().text"
></ui-button>
`
})
export class ButtonWidgetComponent extends BaseWidgetComponent {
// ✅ ONE LINE: Apply font, appearance, and layout properties!
smartStyles = usePropertyStyles(
this.widget,
this.activeBreakpoint,
['font', 'appearance', 'layout'] // ← Groups to apply
);
// That's IT! No manual property checking!
}
Compare:
- ❌ Old: 100+ lines of manual property checking
- ✅ New: 5 lines, automatic application
🔄 Property Flow (How It Would Work)
1. User changes "Font Size" in properties panel
↓
2. PropertyEditorComponent emits propertyChange event
↓
3. PropertiesPanelNewComponent updates widget.properties
↓
4. EditorService flash-persists widget
↓
5. Widget re-renders (signal reactivity)
↓
6. usePropertyStyles() hook triggers
↓
7. FontPropertyApplicator.apply() converts properties → styles
↓
8. Styles applied via [ngStyle]
↓
9. Button appearance updates instantly! ✅
🎯 Benefits
For Developers:
- ✅ Add property group in ONE place
- ✅ Reuse across ALL widgets
- ✅ No duplicate code
- ✅ Easy to maintain
For Widgets:
// Want font properties? ONE LINE!
smartStyles = usePropertyStyles(this.widget, this.activeBreakpoint, ['font']);
// Want font + appearance? ONE LINE!
smartStyles = usePropertyStyles(this.widget, this.activeBreakpoint, ['font', 'appearance']);
// Want everything? ONE LINE!
smartStyles = usePropertyStyles(this.widget, this.activeBreakpoint, ['font', 'appearance', 'layout', 'animation']);
For AI:
- ✅ Clear pattern: "Add property group X"
- ✅ Automatic applicator generation
- ✅ Easy to train on
📊 Impact
Code Reduction:
- ButtonWidget: 100+ lines → 5 lines
- TextWidget: Similar reduction
- ALL widgets: Massive simplification
Maintainability:
- Change font property format? → Update ONE applicator
- Add new property? → Update ONE applicator
- All widgets benefit instantly!
🚀 Implementation Plan
Phase 1: Create Core System (30 mins)
- Create
PropertyApplicatorinterface - Create
FontPropertyApplicator - Create
AppearancePropertyApplicator - Create
LayoutPropertyApplicator - Create
PropertyApplicatorRegistry - Create
usePropertyStyles()hook
Phase 2: Refactor ButtonWidget (15 mins)
- Remove manual property checking
- Use
usePropertyStyles()hook - Test and verify
Phase 3: Apply to Other Widgets (30 mins)
- TextWidget
- ImageWidget
- CardWidget
- All other widgets
💡 My Recommendation
DO THIS NOW! This is the RIGHT way to build a reusable system!
Your instinct is correct - the code needs to be SMARTER, not MORE code!
Want me to implement this? I can have the core system ready in 30 minutes! 🚀