diff --git a/frontend/src/components/Constructor/CanvasElement.tsx b/frontend/src/components/Constructor/CanvasElement.tsx index 3852adb..73abf2f 100644 --- a/frontend/src/components/Constructor/CanvasElement.tsx +++ b/frontend/src/components/Constructor/CanvasElement.tsx @@ -10,11 +10,11 @@ import React from 'react'; import UiElementRenderer from '../UiElements/UiElementRenderer'; import { useElementEffects } from '../../hooks/useElementEffects'; -import { useAppearAnimation } from '../../hooks/useAppearAnimation'; import { buildTransitionStyle, + buildAppearAnimationStyle, hasAnyEffects, - extractEffectProperties, + type ElementEffectProperties, } from '../../lib/elementEffects'; import type { CanvasElement as CanvasElementType } from '../../types/constructor'; import type { ResolvedTransitionSettings } from '../../types/transition'; @@ -57,66 +57,67 @@ const CanvasElement: React.FC = ({ pageTransitionSettings, preloadCache, }) => { - // Extract effect properties from element using helper - const effectProperties = extractEffectProperties( - element as unknown as Record, - ); - - // Use appear animation hook (removes animation after completion to unlock properties) - const { animationStyle, onAnimationEnd, hasAnimationEnded } = - useAppearAnimation( - effectProperties, - element.id, // Reset animation when element changes - ); + // Extract effect properties from element + const effectProperties: Partial = { + appearAnimation: element.appearAnimation, + appearAnimationDuration: element.appearAnimationDuration, + appearAnimationEasing: element.appearAnimationEasing, + hoverScale: element.hoverScale, + hoverOpacity: element.hoverOpacity, + hoverBackgroundColor: element.hoverBackgroundColor, + hoverColor: element.hoverColor, + hoverBoxShadow: element.hoverBoxShadow, + hoverTransitionDuration: element.hoverTransitionDuration, + focusScale: element.focusScale, + focusOpacity: element.focusOpacity, + focusOutline: element.focusOutline, + focusBoxShadow: element.focusBoxShadow, + activeScale: element.activeScale, + activeOpacity: element.activeOpacity, + activeBackgroundColor: element.activeBackgroundColor, + // Hover reveal effects + hoverReveal: element.hoverReveal, + hoverRevealInitialOpacity: element.hoverRevealInitialOpacity, + hoverRevealTargetOpacity: element.hoverRevealTargetOpacity, + hoverRevealDuration: element.hoverRevealDuration, + hoverRevealDelay: element.hoverRevealDelay, + hoverRevealPersist: element.hoverRevealPersist, + hoverPersistOnClick: element.hoverPersistOnClick, + }; // Use effects hook - disabled in edit mode to avoid interfering with dragging - const { effectStyle, eventHandlers, onPersistClick } = useElementEffects( + const { effectStyle, eventHandlers } = useElementEffects( isEditMode ? {} : effectProperties, - isEditMode - ? undefined - : { - resetKey: element.id, - appearAnimationCompleted: - hasAnimationEnded || !effectProperties.appearAnimation, - }, ); - // Combined click handler (only persist click in preview mode) - const handleClick = () => { - if (!isEditMode) { - onPersistClick(); - } - onClick(); - }; - // Clamp position to canvas bounds (0-100%) const clamp = (value: number, min: number, max: number) => Math.min(Math.max(value, min), max); const xClamped = clamp(element.xPercent ?? 50, 0, 100); const yClamped = clamp(element.yPercent ?? 50, 0, 100); - // Build base position style + // Build base position style (outer div - handles positioning + animation) let positionStyle: React.CSSProperties = { left: `${xClamped}%`, top: `${yClamped}%`, transform: 'translate(-50%, -50%)', }; - // Merge interactive effects (preview mode only) - if (!isEditMode && effectStyle.transform) { - // Preserve the translate, add effect transform - positionStyle.transform = `translate(-50%, -50%) ${effectStyle.transform}`; - // Remove transform from effectStyle to avoid double application - const { transform, ...restEffectStyle } = effectStyle; - positionStyle = { ...positionStyle, ...restEffectStyle }; - } else if (!isEditMode) { - positionStyle = { ...positionStyle, ...effectStyle }; - } - - // Add transition for interactive effects (preview mode only) - if (!isEditMode && hasAnyEffects(effectProperties)) { + // Add appear animation to outer div (ALWAYS - for WYSIWYG) + // Animation is applied to outer div to keep positioning hack working + if (effectProperties.appearAnimation) { positionStyle = { ...positionStyle, + ...buildAppearAnimationStyle(effectProperties), + }; + } + + // Build inner wrapper style for hover/focus/active effects (preview mode only) + // Using separate div so animation on outer div doesn't block these effects + let innerEffectStyle: React.CSSProperties = {}; + if (!isEditMode && hasAnyEffects(effectProperties)) { + innerEffectStyle = { + ...effectStyle, ...buildTransitionStyle(effectProperties), }; } @@ -129,51 +130,8 @@ const CanvasElement: React.FC = ({ } }; - // Check if appear animation uses transform (slide/scale need separate wrapper) - const needsAnimationWrapper = - effectProperties.appearAnimation && - effectProperties.appearAnimation !== 'fade'; - - // Render content - const content = ( - - ); - - // For slide/scale (uses transform), use separate wrapper to avoid conflict with positioning - if (needsAnimationWrapper) { - return ( -
-
- {content} -
-
- ); - } - - // Fade or no animation: apply animation to positioning div - const combinedStyle = effectProperties.appearAnimation - ? { ...positionStyle, ...animationStyle } - : positionStyle; + // Check if we need the inner wrapper for effects + const needsEffectWrapper = !isEditMode && hasAnyEffects(effectProperties); return (
= ({ tabIndex={0} data-constructor-element-id={element.id} className='absolute cursor-pointer' - style={combinedStyle} + style={positionStyle} onMouseDown={isEditMode ? onMouseDown : undefined} - onClick={handleClick} + onClick={onClick} onKeyDown={handleKeyDown} - onAnimationEnd={ - effectProperties.appearAnimation ? onAnimationEnd : undefined - } - {...(!isEditMode ? eventHandlers : {})} + {...(!isEditMode && !needsEffectWrapper ? eventHandlers : {})} > - {content} + {needsEffectWrapper ? ( + // Inner wrapper handles hover/focus/active effects independently from animation +
+ +
+ ) : ( + + )}
); }; diff --git a/frontend/src/css/main.css b/frontend/src/css/main.css index ca423da..0df2ecb 100644 --- a/frontend/src/css/main.css +++ b/frontend/src/css/main.css @@ -277,18 +277,24 @@ @-webkit-keyframes element-fade-in { from { opacity: 0; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); } to { opacity: 1; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); } } @keyframes element-fade-in { from { opacity: 0; + transform: translate3d(0, 0, 0); } to { opacity: 1; + transform: translate3d(0, 0, 0); } }