fix(cls): Server-side device visibility + aspect-ratio for featured-image
- functions-addon.php: Validacion centralizada con wp_is_mobile() Componentes con show_on_mobile=false NO se renderizan en mobile Previene CLS de elementos ocultos con CSS - FeaturedImageRenderer: Agrega aspect-ratio 16/9 para reservar espacio Imagen usa object-fit:cover con position:absolute Metodo generateCSS() ahora publico para CriticalCSSService - CriticalCSSService: Agrega featured-image a CRITICAL_RENDERERS CSS se inyecta en <head> antes de que cargue contenido 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -47,9 +47,17 @@ final class FeaturedImageRenderer implements RendererInterface
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
$css = $this->generateCSS($data);
|
|
||||||
$html = $this->buildHTML($data);
|
$html = $this->buildHTML($data);
|
||||||
|
|
||||||
|
// Si is_critical=true, CSS ya fue inyectado en <head> por CriticalCSSService
|
||||||
|
$isCritical = $data['visibility']['is_critical'] ?? false;
|
||||||
|
|
||||||
|
if ($isCritical) {
|
||||||
|
return $html; // Solo HTML, sin CSS inline
|
||||||
|
}
|
||||||
|
|
||||||
|
// CSS inline para componentes no críticos
|
||||||
|
$css = $this->generateCSS($data);
|
||||||
return sprintf("<style>%s</style>\n%s", $css, $html);
|
return sprintf("<style>%s</style>\n%s", $css, $html);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,7 +92,16 @@ final class FeaturedImageRenderer implements RendererInterface
|
|||||||
return is_singular() && has_post_thumbnail();
|
return is_singular() && has_post_thumbnail();
|
||||||
}
|
}
|
||||||
|
|
||||||
private function generateCSS(array $data): string
|
/**
|
||||||
|
* Generar CSS usando CSSGeneratorService
|
||||||
|
*
|
||||||
|
* Este método es público para que CriticalCSSService pueda
|
||||||
|
* generar CSS crítico antes de wp_head sin duplicar lógica.
|
||||||
|
*
|
||||||
|
* @param array $data Datos del componente
|
||||||
|
* @return string CSS generado
|
||||||
|
*/
|
||||||
|
public function generateCSS(array $data): string
|
||||||
{
|
{
|
||||||
$spacing = $data['spacing'] ?? [];
|
$spacing = $data['spacing'] ?? [];
|
||||||
$effects = $data['visual_effects'] ?? [];
|
$effects = $data['visual_effects'] ?? [];
|
||||||
@@ -92,6 +109,8 @@ final class FeaturedImageRenderer implements RendererInterface
|
|||||||
|
|
||||||
$marginTop = $spacing['margin_top'] ?? '1rem';
|
$marginTop = $spacing['margin_top'] ?? '1rem';
|
||||||
$marginBottom = $spacing['margin_bottom'] ?? '2rem';
|
$marginBottom = $spacing['margin_bottom'] ?? '2rem';
|
||||||
|
// Aspect ratio para prevenir CLS - reserva espacio antes de que imagen cargue
|
||||||
|
$aspectRatio = $spacing['aspect_ratio'] ?? '16/9';
|
||||||
|
|
||||||
$borderRadius = $effects['border_radius'] ?? '12px';
|
$borderRadius = $effects['border_radius'] ?? '12px';
|
||||||
$boxShadow = $effects['box_shadow'] ?? '0 8px 24px rgba(0, 0, 0, 0.1)';
|
$boxShadow = $effects['box_shadow'] ?? '0 8px 24px rgba(0, 0, 0, 0.1)';
|
||||||
@@ -104,7 +123,7 @@ final class FeaturedImageRenderer implements RendererInterface
|
|||||||
|
|
||||||
$cssRules = [];
|
$cssRules = [];
|
||||||
|
|
||||||
// Container styles
|
// Container styles con aspect-ratio para prevenir CLS
|
||||||
$cssRules[] = $this->cssGenerator->generate('.featured-image-container', [
|
$cssRules[] = $this->cssGenerator->generate('.featured-image-container', [
|
||||||
'border-radius' => $borderRadius,
|
'border-radius' => $borderRadius,
|
||||||
'overflow' => 'hidden',
|
'overflow' => 'hidden',
|
||||||
@@ -112,14 +131,21 @@ final class FeaturedImageRenderer implements RendererInterface
|
|||||||
'margin-top' => $marginTop,
|
'margin-top' => $marginTop,
|
||||||
'margin-bottom' => $marginBottom,
|
'margin-bottom' => $marginBottom,
|
||||||
'transition' => "transform {$transitionDuration} ease, box-shadow {$transitionDuration} ease",
|
'transition' => "transform {$transitionDuration} ease, box-shadow {$transitionDuration} ease",
|
||||||
|
'aspect-ratio' => $aspectRatio,
|
||||||
|
'position' => 'relative',
|
||||||
|
'background-color' => '#f0f0f0',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Image styles
|
// Image styles - object-fit para llenar el contenedor con aspect-ratio
|
||||||
$cssRules[] = $this->cssGenerator->generate('.featured-image-container img', [
|
$cssRules[] = $this->cssGenerator->generate('.featured-image-container img', [
|
||||||
'width' => '100%',
|
'width' => '100%',
|
||||||
'height' => 'auto',
|
'height' => '100%',
|
||||||
|
'object-fit' => 'cover',
|
||||||
'display' => 'block',
|
'display' => 'block',
|
||||||
'transition' => "transform {$transitionDuration} ease",
|
'transition' => "transform {$transitionDuration} ease",
|
||||||
|
'position' => 'absolute',
|
||||||
|
'top' => '0',
|
||||||
|
'left' => '0',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Hover effect
|
// Hover effect
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ final class CriticalCSSService
|
|||||||
'top-notification-bar' => \ROITheme\Public\TopNotificationBar\Infrastructure\Ui\TopNotificationBarRenderer::class,
|
'top-notification-bar' => \ROITheme\Public\TopNotificationBar\Infrastructure\Ui\TopNotificationBarRenderer::class,
|
||||||
'navbar' => \ROITheme\Public\Navbar\Infrastructure\Ui\NavbarRenderer::class,
|
'navbar' => \ROITheme\Public\Navbar\Infrastructure\Ui\NavbarRenderer::class,
|
||||||
'hero' => \ROITheme\Public\Hero\Infrastructure\Ui\HeroRenderer::class,
|
'hero' => \ROITheme\Public\Hero\Infrastructure\Ui\HeroRenderer::class,
|
||||||
|
'featured-image' => \ROITheme\Public\FeaturedImage\Infrastructure\Ui\FeaturedImageRenderer::class,
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -194,7 +194,46 @@ function roi_render_component(string $componentName): string {
|
|||||||
// Crear Value Objects requeridos
|
// Crear Value Objects requeridos
|
||||||
$name = new \ROITheme\Shared\Domain\ValueObjects\ComponentName($componentName);
|
$name = new \ROITheme\Shared\Domain\ValueObjects\ComponentName($componentName);
|
||||||
$configuration = new \ROITheme\Shared\Domain\ValueObjects\ComponentConfiguration($data);
|
$configuration = new \ROITheme\Shared\Domain\ValueObjects\ComponentConfiguration($data);
|
||||||
$visibility = \ROITheme\Shared\Domain\ValueObjects\ComponentVisibility::allDevices(); // Default: visible en todas partes
|
|
||||||
|
// =====================================================================
|
||||||
|
// VALIDACIÓN DE VISIBILIDAD POR DISPOSITIVO (Previene CLS)
|
||||||
|
// =====================================================================
|
||||||
|
// Si el componente no debe mostrarse en el dispositivo actual,
|
||||||
|
// NO renderizar nada. Esto evita CLS causado por elementos que
|
||||||
|
// se renderizan y luego se ocultan con CSS.
|
||||||
|
// =====================================================================
|
||||||
|
|
||||||
|
// Leer configuración de visibilidad desde BD
|
||||||
|
$isEnabled = ($data['visibility']['is_enabled'] ?? true) === true;
|
||||||
|
$showOnDesktop = ($data['visibility']['show_on_desktop'] ?? true) === true;
|
||||||
|
$showOnMobile = ($data['visibility']['show_on_mobile'] ?? true) === true;
|
||||||
|
|
||||||
|
// Si no está habilitado, no renderizar
|
||||||
|
if (!$isEnabled) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detectar dispositivo actual y validar visibilidad
|
||||||
|
// wp_is_mobile() detecta móviles y tablets via User-Agent
|
||||||
|
$isMobileDevice = wp_is_mobile();
|
||||||
|
|
||||||
|
if ($isMobileDevice && !$showOnMobile) {
|
||||||
|
// Dispositivo móvil pero show_on_mobile = false → NO renderizar
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$isMobileDevice && !$showOnDesktop) {
|
||||||
|
// Dispositivo desktop pero show_on_desktop = false → NO renderizar
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Crear ComponentVisibility con datos reales de BD
|
||||||
|
$visibility = new \ROITheme\Shared\Domain\ValueObjects\ComponentVisibility(
|
||||||
|
enabled: $isEnabled,
|
||||||
|
visibleDesktop: $showOnDesktop,
|
||||||
|
visibleTablet: $showOnMobile, // tablet tratado como mobile
|
||||||
|
visibleMobile: $showOnMobile
|
||||||
|
);
|
||||||
|
|
||||||
// Crear instancia del componente
|
// Crear instancia del componente
|
||||||
$component = new \ROITheme\Shared\Domain\Entities\Component(
|
$component = new \ROITheme\Shared\Domain\Entities\Component(
|
||||||
|
|||||||
Reference in New Issue
Block a user