fixed carusel adaptivity issue
This commit is contained in:
parent
4a61fd1a69
commit
4b7fed5914
@ -35,6 +35,8 @@ interface CanvasElementProps {
|
|||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
) => void;
|
) => void;
|
||||||
|
/** Letterbox styles for constraining fullscreen elements to canvas bounds */
|
||||||
|
letterboxStyles?: React.CSSProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CanvasElement: React.FC<CanvasElementProps> = ({
|
const CanvasElement: React.FC<CanvasElementProps> = ({
|
||||||
@ -47,6 +49,7 @@ const CanvasElement: React.FC<CanvasElementProps> = ({
|
|||||||
resolveUrl,
|
resolveUrl,
|
||||||
onGalleryCardClick,
|
onGalleryCardClick,
|
||||||
onCarouselButtonPositionChange,
|
onCarouselButtonPositionChange,
|
||||||
|
letterboxStyles,
|
||||||
}) => {
|
}) => {
|
||||||
// Extract effect properties from element
|
// Extract effect properties from element
|
||||||
const effectProperties: Partial<ElementEffectProperties> = {
|
const effectProperties: Partial<ElementEffectProperties> = {
|
||||||
@ -73,10 +76,16 @@ const CanvasElement: React.FC<CanvasElementProps> = ({
|
|||||||
isEditMode ? {} : effectProperties,
|
isEditMode ? {} : effectProperties,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 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
|
||||||
let positionStyle: React.CSSProperties = {
|
let positionStyle: React.CSSProperties = {
|
||||||
left: `${element.xPercent}%`,
|
left: `${xClamped}%`,
|
||||||
top: `${element.yPercent}%`,
|
top: `${yClamped}%`,
|
||||||
transform: 'translate(-50%, -50%)',
|
transform: 'translate(-50%, -50%)',
|
||||||
// Reset button defaults to let UiElementRenderer control styling
|
// Reset button defaults to let UiElementRenderer control styling
|
||||||
background: 'transparent',
|
background: 'transparent',
|
||||||
@ -130,6 +139,7 @@ const CanvasElement: React.FC<CanvasElementProps> = ({
|
|||||||
isDisabled={isDisabled}
|
isDisabled={isDisabled}
|
||||||
onGalleryCardClick={onGalleryCardClick}
|
onGalleryCardClick={onGalleryCardClick}
|
||||||
onCarouselButtonPositionChange={onCarouselButtonPositionChange}
|
onCarouselButtonPositionChange={onCarouselButtonPositionChange}
|
||||||
|
letterboxStyles={letterboxStyles}
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -24,16 +24,24 @@ interface RuntimeElementProps {
|
|||||||
resolveUrl?: (url: string | undefined) => string;
|
resolveUrl?: (url: string | undefined) => string;
|
||||||
/** Gallery card click handler */
|
/** Gallery card click handler */
|
||||||
onGalleryCardClick?: (cardIndex: number) => void;
|
onGalleryCardClick?: (cardIndex: number) => void;
|
||||||
|
/** Letterbox styles for constraining fullscreen elements to canvas bounds */
|
||||||
|
letterboxStyles?: React.CSSProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clamp position to canvas bounds (0-100%)
|
||||||
|
const clamp = (value: number, min: number, max: number) =>
|
||||||
|
Math.min(Math.max(value, min), max);
|
||||||
|
|
||||||
const RuntimeElement: React.FC<RuntimeElementProps> = ({
|
const RuntimeElement: React.FC<RuntimeElementProps> = ({
|
||||||
element,
|
element,
|
||||||
onClick,
|
onClick,
|
||||||
resolveUrl,
|
resolveUrl,
|
||||||
onGalleryCardClick,
|
onGalleryCardClick,
|
||||||
|
letterboxStyles,
|
||||||
}) => {
|
}) => {
|
||||||
const xPercent = element.xPercent ?? 0;
|
// Clamp coordinates to canvas bounds
|
||||||
const yPercent = element.yPercent ?? 0;
|
const xPercent = clamp(element.xPercent ?? 50, 0, 100);
|
||||||
|
const yPercent = clamp(element.yPercent ?? 50, 0, 100);
|
||||||
const rotation = element.rotation ?? 0;
|
const rotation = element.rotation ?? 0;
|
||||||
|
|
||||||
// Extract effect properties from element
|
// Extract effect properties from element
|
||||||
@ -101,6 +109,7 @@ const RuntimeElement: React.FC<RuntimeElementProps> = ({
|
|||||||
element={element}
|
element={element}
|
||||||
resolveUrl={resolveUrl}
|
resolveUrl={resolveUrl}
|
||||||
onGalleryCardClick={onGalleryCardClick}
|
onGalleryCardClick={onGalleryCardClick}
|
||||||
|
letterboxStyles={letterboxStyles}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -713,6 +713,7 @@ export default function RuntimePresentation({
|
|||||||
onGalleryCardClick={(cardIndex) =>
|
onGalleryCardClick={(cardIndex) =>
|
||||||
handleGalleryCardClick(element, cardIndex)
|
handleGalleryCardClick(element, cardIndex)
|
||||||
}
|
}
|
||||||
|
letterboxStyles={letterboxStyles}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@ -794,6 +795,7 @@ export default function RuntimePresentation({
|
|||||||
backHeight={
|
backHeight={
|
||||||
activeGalleryCarousel.element.galleryCarouselBackHeight
|
activeGalleryCarousel.element.galleryCarouselBackHeight
|
||||||
}
|
}
|
||||||
|
letterboxStyles={letterboxStyles}
|
||||||
isEditMode={false}
|
isEditMode={false}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -44,6 +44,8 @@ interface GalleryCarouselOverlayProps {
|
|||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
) => void;
|
) => void;
|
||||||
|
// Letterbox styles for constraining overlay to canvas bounds
|
||||||
|
letterboxStyles?: React.CSSProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
const clamp = (value: number, min: number, max: number) =>
|
const clamp = (value: number, min: number, max: number) =>
|
||||||
@ -72,28 +74,38 @@ const GalleryCarouselOverlay: React.FC<GalleryCarouselOverlayProps> = ({
|
|||||||
backHeight,
|
backHeight,
|
||||||
isEditMode = false,
|
isEditMode = false,
|
||||||
onButtonPositionChange,
|
onButtonPositionChange,
|
||||||
|
letterboxStyles,
|
||||||
}) => {
|
}) => {
|
||||||
const resolve = resolveUrl ?? resolveAssetPlaybackUrl;
|
const resolve = resolveUrl ?? resolveAssetPlaybackUrl;
|
||||||
const [currentIndex, setCurrentIndex] = useState(initialIndex);
|
const [currentIndex, setCurrentIndex] = useState(initialIndex);
|
||||||
const [draggingButton, setDraggingButton] = useState<
|
const [draggingButton, setDraggingButton] = useState<
|
||||||
'prev' | 'next' | 'back' | null
|
'prev' | 'next' | 'back' | null
|
||||||
>(null);
|
>(null);
|
||||||
|
// Clamp positions to canvas bounds (0-100%)
|
||||||
const [positions, setPositions] = useState({
|
const [positions, setPositions] = useState({
|
||||||
prevX,
|
prevX: clamp(prevX, 0, 100),
|
||||||
prevY,
|
prevY: clamp(prevY, 0, 100),
|
||||||
nextX,
|
nextX: clamp(nextX, 0, 100),
|
||||||
nextY,
|
nextY: clamp(nextY, 0, 100),
|
||||||
backX,
|
backX: clamp(backX, 0, 100),
|
||||||
backY,
|
backY: clamp(backY, 0, 100),
|
||||||
});
|
});
|
||||||
const overlayRef = useRef<HTMLDivElement>(null);
|
const overlayRef = useRef<HTMLDivElement>(null);
|
||||||
|
const canvasContainerRef = useRef<HTMLDivElement>(null);
|
||||||
const touchStartRef = useRef<{ x: number; y: number } | null>(null);
|
const touchStartRef = useRef<{ x: number; y: number } | null>(null);
|
||||||
const positionsRef = useRef(positions);
|
const positionsRef = useRef(positions);
|
||||||
positionsRef.current = positions;
|
positionsRef.current = positions;
|
||||||
|
|
||||||
// Update positions when props change (e.g., when element is re-selected)
|
// Update positions when props change (clamped to canvas bounds)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setPositions({ prevX, prevY, nextX, nextY, backX, backY });
|
setPositions({
|
||||||
|
prevX: clamp(prevX, 0, 100),
|
||||||
|
prevY: clamp(prevY, 0, 100),
|
||||||
|
nextX: clamp(nextX, 0, 100),
|
||||||
|
nextY: clamp(nextY, 0, 100),
|
||||||
|
backX: clamp(backX, 0, 100),
|
||||||
|
backY: clamp(backY, 0, 100),
|
||||||
|
});
|
||||||
}, [prevX, prevY, nextX, nextY, backX, backY]);
|
}, [prevX, prevY, nextX, nextY, backX, backY]);
|
||||||
|
|
||||||
// Navigation handlers
|
// Navigation handlers
|
||||||
@ -169,10 +181,16 @@ const GalleryCarouselOverlay: React.FC<GalleryCarouselOverlayProps> = ({
|
|||||||
if (!isEditMode || !draggingButton) return;
|
if (!isEditMode || !draggingButton) return;
|
||||||
|
|
||||||
const handleMove = (e: MouseEvent) => {
|
const handleMove = (e: MouseEvent) => {
|
||||||
const x = (e.clientX / window.innerWidth) * 100;
|
// Calculate position relative to canvas container (strict - no viewport fallback)
|
||||||
const y = (e.clientY / window.innerHeight) * 100;
|
const container = canvasContainerRef.current;
|
||||||
const clampedX = clamp(x, 2, 98);
|
if (!container) return;
|
||||||
const clampedY = clamp(y, 2, 98);
|
|
||||||
|
const rect = container.getBoundingClientRect();
|
||||||
|
const x = ((e.clientX - rect.left) / rect.width) * 100;
|
||||||
|
const y = ((e.clientY - rect.top) / rect.height) * 100;
|
||||||
|
// Clamp to canvas bounds (0-100%)
|
||||||
|
const clampedX = clamp(x, 0, 100);
|
||||||
|
const clampedY = clamp(y, 0, 100);
|
||||||
|
|
||||||
setPositions((prev) => {
|
setPositions((prev) => {
|
||||||
// Prev and next buttons share the same Y coordinate
|
// Prev and next buttons share the same Y coordinate
|
||||||
@ -346,52 +364,59 @@ const GalleryCarouselOverlay: React.FC<GalleryCarouselOverlayProps> = ({
|
|||||||
onTouchStart={handleTouchStart}
|
onTouchStart={handleTouchStart}
|
||||||
onTouchEnd={handleTouchEnd}
|
onTouchEnd={handleTouchEnd}
|
||||||
>
|
>
|
||||||
{/* Fullscreen image */}
|
{/* Inner container constrained to canvas bounds - no conflicting CSS classes */}
|
||||||
{imageUrl && (
|
<div
|
||||||
// eslint-disable-next-line @next/next/no-img-element
|
ref={canvasContainerRef}
|
||||||
<img
|
className='overflow-hidden'
|
||||||
src={imageUrl}
|
style={letterboxStyles || { position: 'absolute', inset: 0 }}
|
||||||
alt={currentCard?.title || ''}
|
>
|
||||||
className='absolute inset-0 h-full w-full object-cover'
|
{/* Fullscreen image */}
|
||||||
draggable={false}
|
{imageUrl && (
|
||||||
/>
|
// eslint-disable-next-line @next/next/no-img-element
|
||||||
)}
|
<img
|
||||||
|
src={imageUrl}
|
||||||
|
alt={currentCard?.title || ''}
|
||||||
|
className='absolute inset-0 h-full w-full object-contain'
|
||||||
|
draggable={false}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Prev button */}
|
{/* Prev button */}
|
||||||
{renderNavButton(
|
{renderNavButton(
|
||||||
'prev',
|
'prev',
|
||||||
positions.prevX,
|
positions.prevX,
|
||||||
positions.prevY,
|
positions.prevY,
|
||||||
prevIconUrl,
|
prevIconUrl,
|
||||||
mdiChevronLeft,
|
mdiChevronLeft,
|
||||||
undefined,
|
undefined,
|
||||||
prevWidth,
|
prevWidth,
|
||||||
prevHeight,
|
prevHeight,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Next button */}
|
{/* Next button */}
|
||||||
{renderNavButton(
|
{renderNavButton(
|
||||||
'next',
|
'next',
|
||||||
positions.nextX,
|
positions.nextX,
|
||||||
positions.nextY,
|
positions.nextY,
|
||||||
nextIconUrl,
|
nextIconUrl,
|
||||||
mdiChevronRight,
|
mdiChevronRight,
|
||||||
undefined,
|
undefined,
|
||||||
nextWidth,
|
nextWidth,
|
||||||
nextHeight,
|
nextHeight,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Back button */}
|
{/* Back button */}
|
||||||
{renderNavButton(
|
{renderNavButton(
|
||||||
'back',
|
'back',
|
||||||
positions.backX,
|
positions.backX,
|
||||||
positions.backY,
|
positions.backY,
|
||||||
backIconUrl,
|
backIconUrl,
|
||||||
mdiArrowLeft,
|
mdiArrowLeft,
|
||||||
backLabel,
|
backLabel,
|
||||||
backWidth,
|
backWidth,
|
||||||
backHeight,
|
backHeight,
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -51,6 +51,8 @@ export interface UiElementRendererProps {
|
|||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
) => void;
|
) => void;
|
||||||
|
// Letterbox styles for constraining fullscreen elements to canvas bounds
|
||||||
|
letterboxStyles?: React.CSSProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -67,6 +69,7 @@ export const UiElementRenderer: React.FC<UiElementRendererProps> = ({
|
|||||||
isDisabled = false,
|
isDisabled = false,
|
||||||
onGalleryCardClick,
|
onGalleryCardClick,
|
||||||
onCarouselButtonPositionChange,
|
onCarouselButtonPositionChange,
|
||||||
|
letterboxStyles,
|
||||||
}) => {
|
}) => {
|
||||||
const { className, style } = useElementWrapperStyle({
|
const { className, style } = useElementWrapperStyle({
|
||||||
element,
|
element,
|
||||||
@ -97,6 +100,7 @@ export const UiElementRenderer: React.FC<UiElementRendererProps> = ({
|
|||||||
{...commonProps}
|
{...commonProps}
|
||||||
isEditMode={isEditMode}
|
isEditMode={isEditMode}
|
||||||
onButtonPositionChange={onCarouselButtonPositionChange}
|
onButtonPositionChange={onCarouselButtonPositionChange}
|
||||||
|
letterboxStyles={letterboxStyles}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -42,6 +42,8 @@ interface CarouselElementProps {
|
|||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
) => void;
|
) => void;
|
||||||
|
// Letterbox styles for constraining full-width carousel to canvas bounds
|
||||||
|
letterboxStyles?: CSSProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
const clamp = (value: number, min: number, max: number) =>
|
const clamp = (value: number, min: number, max: number) =>
|
||||||
@ -54,6 +56,7 @@ const CarouselElement: React.FC<CarouselElementProps> = ({
|
|||||||
style,
|
style,
|
||||||
isEditMode = false,
|
isEditMode = false,
|
||||||
onButtonPositionChange,
|
onButtonPositionChange,
|
||||||
|
letterboxStyles,
|
||||||
}) => {
|
}) => {
|
||||||
const resolve = resolveUrl ?? resolveAssetPlaybackUrl;
|
const resolve = resolveUrl ?? resolveAssetPlaybackUrl;
|
||||||
const slides: CarouselSlide[] = element.carouselSlides || [];
|
const slides: CarouselSlide[] = element.carouselSlides || [];
|
||||||
@ -65,11 +68,12 @@ const CarouselElement: React.FC<CarouselElementProps> = ({
|
|||||||
const [draggingButton, setDraggingButton] = useState<'prev' | 'next' | null>(
|
const [draggingButton, setDraggingButton] = useState<'prev' | 'next' | null>(
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
|
// Clamp positions to canvas bounds (0-100%)
|
||||||
const [positions, setPositions] = useState({
|
const [positions, setPositions] = useState({
|
||||||
prevX: element.carouselPrevX ?? 5,
|
prevX: clamp(element.carouselPrevX ?? 5, 0, 100),
|
||||||
prevY: element.carouselPrevY ?? 50,
|
prevY: clamp(element.carouselPrevY ?? 50, 0, 100),
|
||||||
nextX: element.carouselNextX ?? 95,
|
nextX: clamp(element.carouselNextX ?? 95, 0, 100),
|
||||||
nextY: element.carouselNextY ?? 50,
|
nextY: clamp(element.carouselNextY ?? 50, 0, 100),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Touch swipe ref
|
// Touch swipe ref
|
||||||
@ -78,13 +82,13 @@ const CarouselElement: React.FC<CarouselElementProps> = ({
|
|||||||
const positionsRef = useRef(positions);
|
const positionsRef = useRef(positions);
|
||||||
positionsRef.current = positions;
|
positionsRef.current = positions;
|
||||||
|
|
||||||
// Update positions when props change
|
// Update positions when props change (clamped to canvas bounds)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setPositions({
|
setPositions({
|
||||||
prevX: element.carouselPrevX ?? 5,
|
prevX: clamp(element.carouselPrevX ?? 5, 0, 100),
|
||||||
prevY: element.carouselPrevY ?? 50,
|
prevY: clamp(element.carouselPrevY ?? 50, 0, 100),
|
||||||
nextX: element.carouselNextX ?? 95,
|
nextX: clamp(element.carouselNextX ?? 95, 0, 100),
|
||||||
nextY: element.carouselNextY ?? 50,
|
nextY: clamp(element.carouselNextY ?? 50, 0, 100),
|
||||||
});
|
});
|
||||||
}, [
|
}, [
|
||||||
element.carouselPrevX,
|
element.carouselPrevX,
|
||||||
@ -199,10 +203,16 @@ const CarouselElement: React.FC<CarouselElementProps> = ({
|
|||||||
if (!isEditMode || !draggingButton) return;
|
if (!isEditMode || !draggingButton) return;
|
||||||
|
|
||||||
const handleMove = (e: MouseEvent) => {
|
const handleMove = (e: MouseEvent) => {
|
||||||
const x = (e.clientX / window.innerWidth) * 100;
|
// Calculate position relative to canvas container (strict - no viewport fallback)
|
||||||
const y = (e.clientY / window.innerHeight) * 100;
|
const container = overlayRef.current;
|
||||||
const clampedX = clamp(x, 2, 98);
|
if (!container) return;
|
||||||
const clampedY = clamp(y, 2, 98);
|
|
||||||
|
const rect = container.getBoundingClientRect();
|
||||||
|
const x = ((e.clientX - rect.left) / rect.width) * 100;
|
||||||
|
const y = ((e.clientY - rect.top) / rect.height) * 100;
|
||||||
|
// Clamp to canvas bounds (0-100%)
|
||||||
|
const clampedX = clamp(x, 0, 100);
|
||||||
|
const clampedY = clamp(y, 0, 100);
|
||||||
|
|
||||||
setPositions((prev) => ({
|
setPositions((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
@ -240,11 +250,8 @@ const CarouselElement: React.FC<CarouselElementProps> = ({
|
|||||||
}, [isEditMode, draggingButton, onButtonPositionChange]);
|
}, [isEditMode, draggingButton, onButtonPositionChange]);
|
||||||
|
|
||||||
// Convert numeric value to canvas units for responsive scaling
|
// Convert numeric value to canvas units for responsive scaling
|
||||||
// Previously used vw/vh but now uses canvas units (--cu) for uniform scaling
|
// All dimensions use canvas units (--cu) for uniform scaling within project bounds
|
||||||
const toCanvasUnit = (
|
const toCanvasUnit = (value?: string): string | undefined => {
|
||||||
value?: string,
|
|
||||||
dimension: 'width' | 'height' = 'width',
|
|
||||||
): string | undefined => {
|
|
||||||
if (!value || value.trim() === '') return undefined;
|
if (!value || value.trim() === '') return undefined;
|
||||||
const trimmed = value.trim();
|
const trimmed = value.trim();
|
||||||
// If value already uses canvas units or calc, preserve it
|
// If value already uses canvas units or calc, preserve it
|
||||||
@ -272,10 +279,8 @@ const CarouselElement: React.FC<CarouselElementProps> = ({
|
|||||||
return toCU(num);
|
return toCU(num);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Alias for backward compatibility
|
|
||||||
const toViewportUnit = toCanvasUnit;
|
|
||||||
|
|
||||||
// Render navigation button for full-width mode
|
// Render navigation button for full-width mode
|
||||||
|
// Coordinates are clamped to 0-100% to stay within canvas bounds
|
||||||
const renderNavButton = (
|
const renderNavButton = (
|
||||||
type: 'prev' | 'next',
|
type: 'prev' | 'next',
|
||||||
x: number,
|
x: number,
|
||||||
@ -287,8 +292,12 @@ const CarouselElement: React.FC<CarouselElementProps> = ({
|
|||||||
) => {
|
) => {
|
||||||
const isDragging = draggingButton === type;
|
const isDragging = draggingButton === type;
|
||||||
const hasCustomIcon = iconUrl && iconUrl.trim() !== '';
|
const hasCustomIcon = iconUrl && iconUrl.trim() !== '';
|
||||||
const widthValue = toCanvasUnit(buttonWidth, 'width');
|
const widthValue = toCanvasUnit(buttonWidth);
|
||||||
const heightValue = toCanvasUnit(buttonHeight, 'height');
|
const heightValue = toCanvasUnit(buttonHeight);
|
||||||
|
|
||||||
|
// Clamp coordinates to canvas bounds (0-100%)
|
||||||
|
const clampedX = clamp(x, 0, 100);
|
||||||
|
const clampedY = clamp(y, 0, 100);
|
||||||
|
|
||||||
// Navigation-style: custom icon fills button (no backdrop)
|
// Navigation-style: custom icon fills button (no backdrop)
|
||||||
const useNavigationStyle = hasCustomIcon && (widthValue || heightValue);
|
const useNavigationStyle = hasCustomIcon && (widthValue || heightValue);
|
||||||
@ -302,8 +311,8 @@ const CarouselElement: React.FC<CarouselElementProps> = ({
|
|||||||
: 'cursor-pointer hover:scale-105'
|
: 'cursor-pointer hover:scale-105'
|
||||||
} ${isDragging ? 'scale-110 z-[60]' : ''}`}
|
} ${isDragging ? 'scale-110 z-[60]' : ''}`}
|
||||||
style={{
|
style={{
|
||||||
left: `${x}%`,
|
left: `${clampedX}%`,
|
||||||
top: `${y}%`,
|
top: `${clampedY}%`,
|
||||||
transform: 'translate(-50%, -50%)',
|
transform: 'translate(-50%, -50%)',
|
||||||
...(widthValue && { width: widthValue }),
|
...(widthValue && { width: widthValue }),
|
||||||
...(heightValue && { height: heightValue }),
|
...(heightValue && { height: heightValue }),
|
||||||
@ -342,60 +351,71 @@ const CarouselElement: React.FC<CarouselElementProps> = ({
|
|||||||
// Full-width carousel - two layers:
|
// Full-width carousel - two layers:
|
||||||
// 1. Background image layer at z-10 (behind canvas z-[46])
|
// 1. Background image layer at z-10 (behind canvas z-[46])
|
||||||
// 2. Navigation/caption layer at z-[47] (above canvas z-[46], below UI controls z-50)
|
// 2. Navigation/caption layer at z-[47] (above canvas z-[46], below UI controls z-50)
|
||||||
|
// Both layers use letterboxStyles to constrain content to canvas bounds
|
||||||
const fullWidthBackground = (
|
const fullWidthBackground = (
|
||||||
<div className='fixed inset-0 z-10 overflow-hidden bg-black pointer-events-none'>
|
<div className='fixed inset-0 z-10 overflow-hidden bg-black pointer-events-none'>
|
||||||
{currentSlide?.imageUrl && (
|
{/* Inner container respects letterbox dimensions - overflow:hidden clips content to canvas */}
|
||||||
// eslint-disable-next-line @next/next/no-img-element
|
<div
|
||||||
<img
|
className='overflow-hidden'
|
||||||
src={resolve(currentSlide.imageUrl)}
|
style={letterboxStyles || { position: 'absolute', inset: 0 }}
|
||||||
alt={currentSlide.caption || 'Carousel slide'}
|
>
|
||||||
className='absolute inset-0 w-full h-full object-cover'
|
{currentSlide?.imageUrl && (
|
||||||
draggable={false}
|
// eslint-disable-next-line @next/next/no-img-element
|
||||||
/>
|
<img
|
||||||
)}
|
src={resolve(currentSlide.imageUrl)}
|
||||||
|
alt={currentSlide.caption || 'Carousel slide'}
|
||||||
|
className='absolute inset-0 w-full h-full object-contain'
|
||||||
|
draggable={false}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
const fullWidthControls = (
|
const fullWidthControls = (
|
||||||
<div
|
<div className='fixed inset-0 z-[47] pointer-events-none'>
|
||||||
ref={overlayRef}
|
{/* Inner container respects letterbox dimensions - overflow:hidden clips content to canvas */}
|
||||||
className='fixed inset-0 z-[47] pointer-events-none'
|
<div
|
||||||
onTouchStart={handleTouchStart}
|
ref={overlayRef}
|
||||||
onTouchEnd={handleTouchEnd}
|
className='overflow-hidden'
|
||||||
>
|
style={letterboxStyles || { position: 'absolute', inset: 0 }}
|
||||||
{/* Navigation buttons */}
|
onTouchStart={handleTouchStart}
|
||||||
{showNavigation && (
|
onTouchEnd={handleTouchEnd}
|
||||||
<>
|
>
|
||||||
{renderNavButton(
|
{/* Navigation buttons */}
|
||||||
'prev',
|
{showNavigation && (
|
||||||
positions.prevX,
|
<>
|
||||||
positions.prevY,
|
{renderNavButton(
|
||||||
element.carouselPrevIconUrl,
|
'prev',
|
||||||
mdiChevronLeft,
|
positions.prevX,
|
||||||
element.carouselPrevWidth,
|
positions.prevY,
|
||||||
element.carouselPrevHeight,
|
element.carouselPrevIconUrl,
|
||||||
)}
|
mdiChevronLeft,
|
||||||
{renderNavButton(
|
element.carouselPrevWidth,
|
||||||
'next',
|
element.carouselPrevHeight,
|
||||||
positions.nextX,
|
)}
|
||||||
positions.nextY,
|
{renderNavButton(
|
||||||
element.carouselNextIconUrl,
|
'next',
|
||||||
mdiChevronRight,
|
positions.nextX,
|
||||||
element.carouselNextWidth,
|
positions.nextY,
|
||||||
element.carouselNextHeight,
|
element.carouselNextIconUrl,
|
||||||
)}
|
mdiChevronRight,
|
||||||
</>
|
element.carouselNextWidth,
|
||||||
)}
|
element.carouselNextHeight,
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Caption */}
|
{/* Caption */}
|
||||||
{currentSlide?.caption && (
|
{currentSlide?.caption && (
|
||||||
<div
|
<div
|
||||||
className='absolute bottom-8 left-0 right-0 text-center text-white text-lg px-4'
|
className='absolute bottom-8 left-0 right-0 text-center text-white text-lg px-4'
|
||||||
style={captionFontStyle}
|
style={captionFontStyle}
|
||||||
>
|
>
|
||||||
{currentSlide.caption}
|
{currentSlide.caption}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -1569,6 +1569,7 @@ const ConstructorPage = ({ mode = 'constructor' }: ConstructorPageProps) => {
|
|||||||
y,
|
y,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
letterboxStyles={letterboxStyles}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
@ -1648,6 +1649,7 @@ const ConstructorPage = ({ mode = 'constructor' }: ConstructorPageProps) => {
|
|||||||
nextHeight={activeGalleryCarousel.element.galleryCarouselNextHeight}
|
nextHeight={activeGalleryCarousel.element.galleryCarouselNextHeight}
|
||||||
backWidth={activeGalleryCarousel.element.galleryCarouselBackWidth}
|
backWidth={activeGalleryCarousel.element.galleryCarouselBackWidth}
|
||||||
backHeight={activeGalleryCarousel.element.galleryCarouselBackHeight}
|
backHeight={activeGalleryCarousel.element.galleryCarouselBackHeight}
|
||||||
|
letterboxStyles={letterboxStyles}
|
||||||
isEditMode={isConstructorEditMode}
|
isEditMode={isConstructorEditMode}
|
||||||
onButtonPositionChange={handleGalleryCarouselButtonPositionChange}
|
onButtonPositionChange={handleGalleryCarouselButtonPositionChange}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user