fixed positioning

This commit is contained in:
Dmitri 2026-05-28 10:45:18 +02:00
parent 08365ca09e
commit 7dd3384a46
2 changed files with 83 additions and 95 deletions

View File

@ -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<CanvasElementProps> = ({
pageTransitionSettings,
preloadCache,
}) => {
// Extract effect properties from element using helper
const effectProperties = extractEffectProperties(
element as unknown as Record<string, unknown>,
);
// 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<ElementEffectProperties> = {
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<CanvasElementProps> = ({
}
};
// Check if appear animation uses transform (slide/scale need separate wrapper)
const needsAnimationWrapper =
effectProperties.appearAnimation &&
effectProperties.appearAnimation !== 'fade';
// Render content
const content = (
<UiElementRenderer
element={element}
resolveUrl={resolveUrl}
isSelected={isSelected}
isEditMode={isEditMode}
onGalleryCardClick={onGalleryCardClick}
onCarouselButtonPositionChange={onCarouselButtonPositionChange}
letterboxStyles={letterboxStyles}
pageTransitionSettings={pageTransitionSettings}
preloadCache={preloadCache}
/>
);
// For slide/scale (uses transform), use separate wrapper to avoid conflict with positioning
if (needsAnimationWrapper) {
return (
<div
role='button'
tabIndex={0}
data-constructor-element-id={element.id}
className='absolute cursor-pointer'
style={positionStyle}
onMouseDown={isEditMode ? onMouseDown : undefined}
onClick={handleClick}
onKeyDown={handleKeyDown}
{...(!isEditMode ? eventHandlers : {})}
>
<div style={animationStyle} onAnimationEnd={onAnimationEnd}>
{content}
</div>
</div>
);
}
// 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 (
<div
@ -181,16 +139,40 @@ const CanvasElement: React.FC<CanvasElementProps> = ({
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
<div style={innerEffectStyle} {...eventHandlers}>
<UiElementRenderer
element={element}
resolveUrl={resolveUrl}
isSelected={isSelected}
isEditMode={isEditMode}
onGalleryCardClick={onGalleryCardClick}
onCarouselButtonPositionChange={onCarouselButtonPositionChange}
letterboxStyles={letterboxStyles}
pageTransitionSettings={pageTransitionSettings}
preloadCache={preloadCache}
/>
</div>
) : (
<UiElementRenderer
element={element}
resolveUrl={resolveUrl}
isSelected={isSelected}
isEditMode={isEditMode}
onGalleryCardClick={onGalleryCardClick}
onCarouselButtonPositionChange={onCarouselButtonPositionChange}
letterboxStyles={letterboxStyles}
pageTransitionSettings={pageTransitionSettings}
preloadCache={preloadCache}
/>
)}
</div>
);
};

View File

@ -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);
}
}