1327 lines
48 KiB
TypeScript
1327 lines
48 KiB
TypeScript
/**
|
|
* useElementSettingsForm
|
|
*
|
|
* Shared hook for managing element settings form state.
|
|
* Used by element-type-defaults, project-element-defaults, and constructor pages.
|
|
*/
|
|
|
|
import { useCallback, useState } from 'react';
|
|
import type { ElementStyleProperties } from '../../lib/elementStyles';
|
|
import type {
|
|
CanvasElement,
|
|
CanvasElementType,
|
|
GalleryCard,
|
|
CarouselSlide,
|
|
InfoPanelSectionType,
|
|
InfoPanelSectionInstance,
|
|
} from '../../types/constructor';
|
|
import {
|
|
DEFAULT_INFO_PANEL_SECTIONS,
|
|
generateSectionId,
|
|
} from '../../types/constructor';
|
|
import { parseJsonObject } from '../../lib/parseJson';
|
|
import {
|
|
extractNumericValue,
|
|
clampPercent,
|
|
parseNullableNumber,
|
|
toUnitValue,
|
|
toOptionalTrimmed,
|
|
} from './types';
|
|
import {
|
|
createLocalId,
|
|
isNavigationElementType,
|
|
isDescriptionElementType,
|
|
isGalleryElementType,
|
|
isCarouselElementType,
|
|
isMediaElementType,
|
|
isInfoPanelElementType,
|
|
} from '../../lib/elementDefaults';
|
|
|
|
interface UseElementSettingsFormOptions {
|
|
elementType: CanvasElementType | string;
|
|
}
|
|
|
|
interface FormState {
|
|
// Common settings
|
|
label: string;
|
|
xPercent: string;
|
|
yPercent: string;
|
|
appearDelaySec: string;
|
|
appearDurationSec: string;
|
|
|
|
// CSS style settings
|
|
width: string;
|
|
height: string;
|
|
minWidth: string;
|
|
maxWidth: string;
|
|
minHeight: string;
|
|
maxHeight: string;
|
|
margin: string;
|
|
padding: string;
|
|
gap: string;
|
|
fontSize: string;
|
|
lineHeight: string;
|
|
fontWeight: string;
|
|
border: string;
|
|
borderRadius: string;
|
|
opacity: string;
|
|
boxShadow: string;
|
|
display: string;
|
|
position: string;
|
|
justifyContent: string;
|
|
alignItems: string;
|
|
textAlign: string;
|
|
zIndex: string;
|
|
backgroundColor: string;
|
|
color: string;
|
|
|
|
// Effect settings
|
|
appearAnimation: string;
|
|
appearAnimationDuration: string;
|
|
appearAnimationEasing: string;
|
|
hoverScale: string;
|
|
hoverOpacity: string;
|
|
hoverBackgroundColor: string;
|
|
hoverColor: string;
|
|
hoverBoxShadow: string;
|
|
hoverTransitionDuration: string;
|
|
focusScale: string;
|
|
focusOpacity: string;
|
|
focusOutline: string;
|
|
focusBoxShadow: string;
|
|
activeScale: string;
|
|
activeOpacity: string;
|
|
activeBackgroundColor: string;
|
|
|
|
// Hover Reveal settings
|
|
hoverReveal: boolean;
|
|
hoverRevealInitialOpacity: string;
|
|
hoverRevealTargetOpacity: string;
|
|
hoverRevealDuration: string;
|
|
hoverRevealDelay: string;
|
|
hoverRevealPersist: boolean;
|
|
// Persist hover effects after click
|
|
hoverPersistOnClick: boolean;
|
|
|
|
// Navigation settings
|
|
iconUrl: string;
|
|
navLabel: string;
|
|
navLabelFontFamily: string;
|
|
navType: 'forward' | 'back';
|
|
navDisabled: boolean;
|
|
targetPageId: string;
|
|
targetPageSlug: string;
|
|
transitionVideoUrl: string;
|
|
transitionReverseMode: 'auto_reverse' | 'separate_video';
|
|
reverseVideoUrl: string;
|
|
|
|
// Description settings
|
|
descriptionTitle: string;
|
|
descriptionText: string;
|
|
descriptionTitleFontSize: string;
|
|
descriptionTextFontSize: string;
|
|
descriptionTitleFontFamily: string;
|
|
descriptionTextFontFamily: string;
|
|
descriptionTitleColor: string;
|
|
descriptionTextColor: string;
|
|
|
|
// Media settings
|
|
mediaUrl: string;
|
|
mediaAutoplay: boolean;
|
|
mediaLoop: boolean;
|
|
mediaMuted: boolean;
|
|
|
|
// Carousel settings
|
|
carouselPrevIconUrl: string;
|
|
carouselNextIconUrl: string;
|
|
carouselCaptionFontFamily: string;
|
|
|
|
// Gallery settings
|
|
galleryTitleFontFamily: string;
|
|
galleryTextFontFamily: string;
|
|
|
|
// Complex arrays
|
|
galleryCards: GalleryCard[];
|
|
carouselSlides: CarouselSlide[];
|
|
|
|
// Info Panel settings
|
|
infoPanelTriggerLabel: string;
|
|
infoPanelTriggerFontFamily: string;
|
|
infoPanelDisabled: boolean;
|
|
panelTitle: string;
|
|
panelText: string;
|
|
panelXPercent: string;
|
|
panelYPercent: string;
|
|
panelWidth: string;
|
|
panelHeight: string;
|
|
panelBackgroundColor: string;
|
|
panelBorderRadius: string;
|
|
panelPadding: string;
|
|
panelBackdropBlur: string;
|
|
panelTitleColor: string;
|
|
panelTitleFontSize: string;
|
|
panelTitleFontFamily: string;
|
|
panelTextColor: string;
|
|
panelTextFontSize: string;
|
|
panelTextFontFamily: string;
|
|
panelOverlayColor: string;
|
|
detailXPercent: string;
|
|
detailYPercent: string;
|
|
detailWidth: string;
|
|
detailHeight: string;
|
|
detailBackgroundColor: string;
|
|
detailBorderRadius: string;
|
|
detailPadding: string;
|
|
detailCaptionFontFamily: string;
|
|
// Note: detailOverlayColor removed - parent InfoPanelOverlay provides backdrop
|
|
|
|
// Info Panel - Header Section (like Gallery)
|
|
infoPanelHeaderImageUrl: string;
|
|
infoPanelHeaderText: string;
|
|
infoPanelHeaderBackgroundColor: string;
|
|
infoPanelHeaderColor: string;
|
|
infoPanelHeaderFontFamily: string;
|
|
infoPanelHeaderFontSize: string;
|
|
infoPanelHeaderFontWeight: string;
|
|
infoPanelHeaderPadding: string;
|
|
infoPanelHeaderBorderRadius: string;
|
|
infoPanelHeaderTextAlign: string;
|
|
infoPanelHeaderMinHeight: string;
|
|
|
|
// Info Panel - Title Section enhanced styling
|
|
infoPanelTitleBackgroundColor: string;
|
|
infoPanelTitlePadding: string;
|
|
infoPanelTitleFontWeight: string;
|
|
infoPanelTitleTextAlign: string;
|
|
|
|
// Info Panel - Span styling
|
|
infoPanelSpanBackgroundColor: string;
|
|
infoPanelSpanColor: string;
|
|
infoPanelSpanFontFamily: string;
|
|
infoPanelSpanFontSize: string;
|
|
infoPanelSpanPadding: string;
|
|
infoPanelSpanBorderRadius: string;
|
|
|
|
// Info Panel - Card styling
|
|
infoPanelCardBackgroundColor: string;
|
|
infoPanelCardBorderRadius: string;
|
|
infoPanelCardAspectRatio: string;
|
|
infoPanelCardMinHeight: string;
|
|
infoPanelCardTitleBackgroundColor: string;
|
|
infoPanelCardTitleColor: string;
|
|
infoPanelCardTitleFontFamily: string;
|
|
infoPanelCardTitleFontSize: string;
|
|
infoPanelCardTitlePadding: string;
|
|
|
|
// Info Panel - Section instances (order + per-section settings)
|
|
infoPanelSections: InfoPanelSectionInstance[];
|
|
}
|
|
|
|
const initialState: FormState = {
|
|
label: '',
|
|
xPercent: '0',
|
|
yPercent: '0',
|
|
appearDelaySec: '0',
|
|
appearDurationSec: '',
|
|
width: '',
|
|
height: '',
|
|
minWidth: '',
|
|
maxWidth: '',
|
|
minHeight: '',
|
|
maxHeight: '',
|
|
margin: '',
|
|
padding: '',
|
|
gap: '',
|
|
fontSize: '',
|
|
lineHeight: '',
|
|
fontWeight: '',
|
|
border: '',
|
|
borderRadius: '',
|
|
opacity: '',
|
|
boxShadow: '',
|
|
display: '',
|
|
position: '',
|
|
justifyContent: '',
|
|
alignItems: '',
|
|
textAlign: '',
|
|
zIndex: '',
|
|
backgroundColor: '',
|
|
color: '',
|
|
// Effect settings
|
|
appearAnimation: '',
|
|
appearAnimationDuration: '',
|
|
appearAnimationEasing: '',
|
|
hoverScale: '',
|
|
hoverOpacity: '',
|
|
hoverBackgroundColor: '',
|
|
hoverColor: '',
|
|
hoverBoxShadow: '',
|
|
hoverTransitionDuration: '',
|
|
focusScale: '',
|
|
focusOpacity: '',
|
|
focusOutline: '',
|
|
focusBoxShadow: '',
|
|
activeScale: '',
|
|
activeOpacity: '',
|
|
activeBackgroundColor: '',
|
|
// Hover Reveal settings
|
|
hoverReveal: false,
|
|
hoverRevealInitialOpacity: '',
|
|
hoverRevealTargetOpacity: '',
|
|
hoverRevealDuration: '',
|
|
hoverRevealDelay: '',
|
|
hoverRevealPersist: false,
|
|
hoverPersistOnClick: false,
|
|
iconUrl: '',
|
|
navLabel: '',
|
|
navLabelFontFamily: '',
|
|
navType: 'forward',
|
|
navDisabled: false,
|
|
targetPageId: '',
|
|
targetPageSlug: '',
|
|
transitionVideoUrl: '',
|
|
transitionReverseMode: 'auto_reverse',
|
|
reverseVideoUrl: '',
|
|
descriptionTitle: '',
|
|
descriptionText: '',
|
|
descriptionTitleFontSize: '',
|
|
descriptionTextFontSize: '',
|
|
descriptionTitleFontFamily: '',
|
|
descriptionTextFontFamily: '',
|
|
descriptionTitleColor: '',
|
|
descriptionTextColor: '',
|
|
mediaUrl: '',
|
|
mediaAutoplay: false,
|
|
mediaLoop: false,
|
|
mediaMuted: false,
|
|
carouselPrevIconUrl: '',
|
|
carouselNextIconUrl: '',
|
|
carouselCaptionFontFamily: '',
|
|
galleryTitleFontFamily: '',
|
|
galleryTextFontFamily: '',
|
|
galleryCards: [],
|
|
carouselSlides: [],
|
|
// Info Panel settings
|
|
infoPanelTriggerLabel: '',
|
|
infoPanelTriggerFontFamily: '',
|
|
infoPanelDisabled: false,
|
|
panelTitle: '',
|
|
panelText: '',
|
|
panelXPercent: '0',
|
|
panelYPercent: '0',
|
|
panelWidth: '',
|
|
panelHeight: '',
|
|
panelBackgroundColor: '',
|
|
panelBorderRadius: '',
|
|
panelPadding: '',
|
|
panelBackdropBlur: '',
|
|
panelTitleColor: '',
|
|
panelTitleFontSize: '',
|
|
panelTitleFontFamily: '',
|
|
panelTextColor: '',
|
|
panelTextFontSize: '',
|
|
panelTextFontFamily: '',
|
|
panelOverlayColor: '',
|
|
detailXPercent: '0',
|
|
detailYPercent: '0',
|
|
detailWidth: '',
|
|
detailHeight: '',
|
|
detailBackgroundColor: '',
|
|
detailBorderRadius: '',
|
|
detailPadding: '',
|
|
detailCaptionFontFamily: '',
|
|
// Info Panel - Header Section
|
|
infoPanelHeaderImageUrl: '',
|
|
infoPanelHeaderText: '',
|
|
infoPanelHeaderBackgroundColor: '',
|
|
infoPanelHeaderColor: '',
|
|
infoPanelHeaderFontFamily: '',
|
|
infoPanelHeaderFontSize: '',
|
|
infoPanelHeaderFontWeight: '',
|
|
infoPanelHeaderPadding: '',
|
|
infoPanelHeaderBorderRadius: '',
|
|
infoPanelHeaderTextAlign: '',
|
|
infoPanelHeaderMinHeight: '',
|
|
// Info Panel - Title Section enhanced
|
|
infoPanelTitleBackgroundColor: '',
|
|
infoPanelTitlePadding: '',
|
|
infoPanelTitleFontWeight: '',
|
|
infoPanelTitleTextAlign: '',
|
|
// Info Panel - Span styling
|
|
infoPanelSpanBackgroundColor: '',
|
|
infoPanelSpanColor: '',
|
|
infoPanelSpanFontFamily: '',
|
|
infoPanelSpanFontSize: '',
|
|
infoPanelSpanPadding: '',
|
|
infoPanelSpanBorderRadius: '',
|
|
// Info Panel - Card styling
|
|
infoPanelCardBackgroundColor: '',
|
|
infoPanelCardBorderRadius: '',
|
|
infoPanelCardAspectRatio: '',
|
|
infoPanelCardMinHeight: '',
|
|
infoPanelCardTitleBackgroundColor: '',
|
|
infoPanelCardTitleColor: '',
|
|
infoPanelCardTitleFontFamily: '',
|
|
infoPanelCardTitleFontSize: '',
|
|
infoPanelCardTitlePadding: '',
|
|
// Info Panel - Section instances
|
|
infoPanelSections: DEFAULT_INFO_PANEL_SECTIONS.map((s) => ({ ...s })),
|
|
};
|
|
|
|
export function useElementSettingsForm(options: UseElementSettingsFormOptions) {
|
|
const { elementType } = options;
|
|
const [state, setState] = useState<FormState>(initialState);
|
|
|
|
// Type detection using shared helpers from elementDefaults
|
|
const isNavigationType = isNavigationElementType(elementType);
|
|
const isDescriptionType = isDescriptionElementType(elementType);
|
|
const isGalleryType = isGalleryElementType(elementType);
|
|
const isCarouselType = isCarouselElementType(elementType);
|
|
const isMediaType = isMediaElementType(elementType);
|
|
const isInfoPanelType = isInfoPanelElementType(elementType);
|
|
|
|
/**
|
|
* Apply settings from JSON to form state
|
|
*/
|
|
const applySettings = useCallback(
|
|
(settingsValue?: string | Record<string, unknown>) => {
|
|
const settings = parseJsonObject<Record<string, unknown>>(
|
|
settingsValue,
|
|
{},
|
|
);
|
|
|
|
setState({
|
|
label: String(settings.label || ''),
|
|
xPercent: String(settings.xPercent ?? 0),
|
|
yPercent: String(settings.yPercent ?? 0),
|
|
width: extractNumericValue(settings.width),
|
|
height: extractNumericValue(settings.height),
|
|
minWidth: extractNumericValue(settings.minWidth),
|
|
maxWidth: extractNumericValue(settings.maxWidth),
|
|
minHeight: extractNumericValue(settings.minHeight),
|
|
maxHeight: extractNumericValue(settings.maxHeight),
|
|
margin: String(settings.margin || ''),
|
|
padding: String(settings.padding || ''),
|
|
gap: String(settings.gap || ''),
|
|
fontSize: String(settings.fontSize || ''),
|
|
lineHeight: String(settings.lineHeight || ''),
|
|
fontWeight: String(settings.fontWeight || ''),
|
|
border: extractNumericValue(settings.border),
|
|
borderRadius: extractNumericValue(settings.borderRadius),
|
|
opacity: settings.opacity === 0 ? '0' : String(settings.opacity || ''),
|
|
boxShadow: String(settings.boxShadow || ''),
|
|
display: String(settings.display || ''),
|
|
position: String(settings.position || ''),
|
|
justifyContent: String(settings.justifyContent || ''),
|
|
alignItems: String(settings.alignItems || ''),
|
|
textAlign: String(settings.textAlign || ''),
|
|
zIndex: String(settings.zIndex || ''),
|
|
backgroundColor: String(settings.backgroundColor || ''),
|
|
color: String(settings.color || ''),
|
|
// Effect settings
|
|
appearAnimation: String(settings.appearAnimation || ''),
|
|
appearAnimationDuration: String(settings.appearAnimationDuration || ''),
|
|
appearAnimationEasing: String(settings.appearAnimationEasing || ''),
|
|
hoverScale: String(settings.hoverScale || ''),
|
|
hoverOpacity: String(settings.hoverOpacity || ''),
|
|
hoverBackgroundColor: String(settings.hoverBackgroundColor || ''),
|
|
hoverColor: String(settings.hoverColor || ''),
|
|
hoverBoxShadow: String(settings.hoverBoxShadow || ''),
|
|
hoverTransitionDuration: String(settings.hoverTransitionDuration || ''),
|
|
focusScale: String(settings.focusScale || ''),
|
|
focusOpacity: String(settings.focusOpacity || ''),
|
|
focusOutline: String(settings.focusOutline || ''),
|
|
focusBoxShadow: String(settings.focusBoxShadow || ''),
|
|
activeScale: String(settings.activeScale || ''),
|
|
activeOpacity: String(settings.activeOpacity || ''),
|
|
activeBackgroundColor: String(settings.activeBackgroundColor || ''),
|
|
// Hover Reveal settings
|
|
hoverReveal: Boolean(settings.hoverReveal),
|
|
hoverRevealInitialOpacity: String(
|
|
settings.hoverRevealInitialOpacity || '',
|
|
),
|
|
hoverRevealTargetOpacity: String(
|
|
settings.hoverRevealTargetOpacity || '',
|
|
),
|
|
hoverRevealDuration: String(settings.hoverRevealDuration || ''),
|
|
hoverRevealDelay: String(settings.hoverRevealDelay || ''),
|
|
hoverRevealPersist: Boolean(settings.hoverRevealPersist),
|
|
hoverPersistOnClick: Boolean(settings.hoverPersistOnClick),
|
|
appearDelaySec: String(settings.appearDelaySec ?? 0),
|
|
appearDurationSec:
|
|
settings.appearDurationSec === null ||
|
|
settings.appearDurationSec === undefined
|
|
? ''
|
|
: String(settings.appearDurationSec),
|
|
iconUrl: String(settings.iconUrl || ''),
|
|
navLabel: String(settings.navLabel || ''),
|
|
navLabelFontFamily: String(settings.navLabelFontFamily || ''),
|
|
navType: settings.navType === 'back' ? 'back' : 'forward',
|
|
navDisabled: Boolean(settings.navDisabled),
|
|
targetPageId: String(settings.targetPageId || ''),
|
|
targetPageSlug: String(settings.targetPageSlug || ''),
|
|
transitionVideoUrl: String(settings.transitionVideoUrl || ''),
|
|
transitionReverseMode:
|
|
settings.transitionReverseMode === 'separate_video'
|
|
? 'separate_video'
|
|
: 'auto_reverse',
|
|
reverseVideoUrl: String(settings.reverseVideoUrl || ''),
|
|
descriptionTitle: String(settings.descriptionTitle || ''),
|
|
descriptionText: String(settings.descriptionText || ''),
|
|
descriptionTitleFontSize: String(
|
|
settings.descriptionTitleFontSize || '',
|
|
),
|
|
descriptionTextFontSize: String(settings.descriptionTextFontSize || ''),
|
|
descriptionTitleFontFamily: String(
|
|
settings.descriptionTitleFontFamily || '',
|
|
),
|
|
descriptionTextFontFamily: String(
|
|
settings.descriptionTextFontFamily || '',
|
|
),
|
|
descriptionTitleColor: String(settings.descriptionTitleColor || ''),
|
|
descriptionTextColor: String(settings.descriptionTextColor || ''),
|
|
mediaUrl: String(settings.mediaUrl || ''),
|
|
mediaAutoplay: Boolean(settings.mediaAutoplay),
|
|
mediaLoop: Boolean(settings.mediaLoop),
|
|
mediaMuted: Boolean(settings.mediaMuted),
|
|
carouselPrevIconUrl: String(settings.carouselPrevIconUrl || ''),
|
|
carouselNextIconUrl: String(settings.carouselNextIconUrl || ''),
|
|
carouselCaptionFontFamily: String(
|
|
settings.carouselCaptionFontFamily || '',
|
|
),
|
|
galleryTitleFontFamily: String(settings.galleryTitleFontFamily || ''),
|
|
galleryTextFontFamily: String(settings.galleryTextFontFamily || ''),
|
|
galleryCards: Array.isArray(settings.galleryCards)
|
|
? settings.galleryCards.map((card: Record<string, unknown>) => ({
|
|
id: String(card?.id || createLocalId()),
|
|
imageUrl: String(card?.imageUrl ?? ''),
|
|
title: String(card?.title ?? ''),
|
|
description: String(card?.description ?? ''),
|
|
}))
|
|
: [],
|
|
carouselSlides: Array.isArray(settings.carouselSlides)
|
|
? settings.carouselSlides.map((slide: Record<string, unknown>) => ({
|
|
id: String(slide?.id || createLocalId()),
|
|
imageUrl: String(slide?.imageUrl ?? ''),
|
|
caption: String(slide?.caption ?? ''),
|
|
}))
|
|
: [],
|
|
// Info Panel settings
|
|
infoPanelTriggerLabel: String(settings.infoPanelTriggerLabel || ''),
|
|
infoPanelTriggerFontFamily: String(
|
|
settings.infoPanelTriggerFontFamily || '',
|
|
),
|
|
infoPanelDisabled: Boolean(settings.infoPanelDisabled),
|
|
panelTitle: String(settings.panelTitle || ''),
|
|
panelText: String(settings.panelText || ''),
|
|
panelXPercent: String(settings.panelXPercent ?? 0),
|
|
panelYPercent: String(settings.panelYPercent ?? 0),
|
|
panelWidth: String(settings.panelWidth || ''),
|
|
panelHeight: String(settings.panelHeight || ''),
|
|
panelBackgroundColor: String(settings.panelBackgroundColor || ''),
|
|
panelBorderRadius: String(settings.panelBorderRadius || ''),
|
|
panelPadding: String(settings.panelPadding || ''),
|
|
panelBackdropBlur: String(settings.panelBackdropBlur || ''),
|
|
panelTitleColor: String(settings.panelTitleColor || ''),
|
|
panelTitleFontSize: String(settings.panelTitleFontSize || ''),
|
|
panelTitleFontFamily: String(settings.panelTitleFontFamily || ''),
|
|
panelTextColor: String(settings.panelTextColor || ''),
|
|
panelTextFontSize: String(settings.panelTextFontSize || ''),
|
|
panelTextFontFamily: String(settings.panelTextFontFamily || ''),
|
|
panelOverlayColor: String(settings.panelOverlayColor || ''),
|
|
detailXPercent: String(settings.detailXPercent ?? 0),
|
|
detailYPercent: String(settings.detailYPercent ?? 0),
|
|
detailWidth: String(settings.detailWidth || ''),
|
|
detailHeight: String(settings.detailHeight || ''),
|
|
detailBackgroundColor: String(settings.detailBackgroundColor || ''),
|
|
detailBorderRadius: String(settings.detailBorderRadius || ''),
|
|
detailPadding: String(settings.detailPadding || ''),
|
|
detailCaptionFontFamily: String(settings.detailCaptionFontFamily || ''),
|
|
// Info Panel - Header Section
|
|
infoPanelHeaderImageUrl: String(settings.infoPanelHeaderImageUrl || ''),
|
|
infoPanelHeaderText: String(settings.infoPanelHeaderText || ''),
|
|
infoPanelHeaderBackgroundColor: String(
|
|
settings.infoPanelHeaderBackgroundColor || '',
|
|
),
|
|
infoPanelHeaderColor: String(settings.infoPanelHeaderColor || ''),
|
|
infoPanelHeaderFontFamily: String(
|
|
settings.infoPanelHeaderFontFamily || '',
|
|
),
|
|
infoPanelHeaderFontSize: String(settings.infoPanelHeaderFontSize || ''),
|
|
infoPanelHeaderFontWeight: String(
|
|
settings.infoPanelHeaderFontWeight || '',
|
|
),
|
|
infoPanelHeaderPadding: String(settings.infoPanelHeaderPadding || ''),
|
|
infoPanelHeaderBorderRadius: String(
|
|
settings.infoPanelHeaderBorderRadius || '',
|
|
),
|
|
infoPanelHeaderTextAlign: String(
|
|
settings.infoPanelHeaderTextAlign || '',
|
|
),
|
|
infoPanelHeaderMinHeight: String(
|
|
settings.infoPanelHeaderMinHeight || '',
|
|
),
|
|
// Info Panel - Title Section enhanced
|
|
infoPanelTitleBackgroundColor: String(
|
|
settings.infoPanelTitleBackgroundColor || '',
|
|
),
|
|
infoPanelTitlePadding: String(settings.infoPanelTitlePadding || ''),
|
|
infoPanelTitleFontWeight: String(
|
|
settings.infoPanelTitleFontWeight || '',
|
|
),
|
|
infoPanelTitleTextAlign: String(settings.infoPanelTitleTextAlign || ''),
|
|
// Info Panel - Span styling
|
|
infoPanelSpanBackgroundColor: String(
|
|
settings.infoPanelSpanBackgroundColor || '',
|
|
),
|
|
infoPanelSpanColor: String(settings.infoPanelSpanColor || ''),
|
|
infoPanelSpanFontFamily: String(settings.infoPanelSpanFontFamily || ''),
|
|
infoPanelSpanFontSize: String(settings.infoPanelSpanFontSize || ''),
|
|
infoPanelSpanPadding: String(settings.infoPanelSpanPadding || ''),
|
|
infoPanelSpanBorderRadius: String(
|
|
settings.infoPanelSpanBorderRadius || '',
|
|
),
|
|
// Info Panel - Card styling
|
|
infoPanelCardBackgroundColor: String(
|
|
settings.infoPanelCardBackgroundColor || '',
|
|
),
|
|
infoPanelCardBorderRadius: String(
|
|
settings.infoPanelCardBorderRadius || '',
|
|
),
|
|
infoPanelCardAspectRatio: String(
|
|
settings.infoPanelCardAspectRatio || '',
|
|
),
|
|
infoPanelCardMinHeight: String(settings.infoPanelCardMinHeight || ''),
|
|
infoPanelCardTitleBackgroundColor: String(
|
|
settings.infoPanelCardTitleBackgroundColor || '',
|
|
),
|
|
infoPanelCardTitleColor: String(settings.infoPanelCardTitleColor || ''),
|
|
infoPanelCardTitleFontFamily: String(
|
|
settings.infoPanelCardTitleFontFamily || '',
|
|
),
|
|
infoPanelCardTitleFontSize: String(
|
|
settings.infoPanelCardTitleFontSize || '',
|
|
),
|
|
infoPanelCardTitlePadding: String(
|
|
settings.infoPanelCardTitlePadding || '',
|
|
),
|
|
// Info Panel - Section instances
|
|
infoPanelSections: Array.isArray(settings.infoPanelSections)
|
|
? (settings.infoPanelSections as InfoPanelSectionInstance[])
|
|
: DEFAULT_INFO_PANEL_SECTIONS.map((s) => ({ ...s })),
|
|
});
|
|
},
|
|
[],
|
|
);
|
|
|
|
/**
|
|
* Update a single field
|
|
*/
|
|
const setField = useCallback(
|
|
<K extends keyof FormState>(field: K, value: FormState[K]) => {
|
|
setState((prev) => ({ ...prev, [field]: value }));
|
|
},
|
|
[],
|
|
);
|
|
|
|
/**
|
|
* Update multiple fields at once
|
|
*/
|
|
const setFields = useCallback((updates: Partial<FormState>) => {
|
|
setState((prev) => ({ ...prev, ...updates }));
|
|
}, []);
|
|
|
|
/**
|
|
* Get CSS style values for StyleSettingsSection
|
|
*/
|
|
const getStyleValues = useCallback((): ElementStyleProperties => {
|
|
return {
|
|
width: state.width,
|
|
height: state.height,
|
|
minWidth: state.minWidth,
|
|
maxWidth: state.maxWidth,
|
|
minHeight: state.minHeight,
|
|
maxHeight: state.maxHeight,
|
|
margin: state.margin,
|
|
padding: state.padding,
|
|
gap: state.gap,
|
|
fontSize: state.fontSize,
|
|
lineHeight: state.lineHeight,
|
|
fontWeight: state.fontWeight,
|
|
border: state.border,
|
|
borderRadius: state.borderRadius,
|
|
opacity: state.opacity,
|
|
boxShadow: state.boxShadow,
|
|
display: state.display,
|
|
position: state.position,
|
|
justifyContent: state.justifyContent,
|
|
alignItems: state.alignItems,
|
|
textAlign: state.textAlign,
|
|
zIndex: state.zIndex,
|
|
backgroundColor: state.backgroundColor,
|
|
color: state.color,
|
|
};
|
|
}, [state]);
|
|
|
|
/**
|
|
* Get effect values for EffectsSettingsSection
|
|
*/
|
|
const getEffectValues = useCallback(() => {
|
|
return {
|
|
appearAnimation: state.appearAnimation,
|
|
appearAnimationDuration: state.appearAnimationDuration,
|
|
appearAnimationEasing: state.appearAnimationEasing,
|
|
hoverScale: state.hoverScale,
|
|
hoverOpacity: state.hoverOpacity,
|
|
hoverBackgroundColor: state.hoverBackgroundColor,
|
|
hoverColor: state.hoverColor,
|
|
hoverBoxShadow: state.hoverBoxShadow,
|
|
hoverTransitionDuration: state.hoverTransitionDuration,
|
|
focusScale: state.focusScale,
|
|
focusOpacity: state.focusOpacity,
|
|
focusOutline: state.focusOutline,
|
|
focusBoxShadow: state.focusBoxShadow,
|
|
activeScale: state.activeScale,
|
|
activeOpacity: state.activeOpacity,
|
|
activeBackgroundColor: state.activeBackgroundColor,
|
|
// Convert booleans to strings for form compatibility
|
|
hoverReveal: state.hoverReveal ? 'true' : '',
|
|
hoverRevealInitialOpacity: state.hoverRevealInitialOpacity,
|
|
hoverRevealTargetOpacity: state.hoverRevealTargetOpacity,
|
|
hoverRevealDuration: state.hoverRevealDuration,
|
|
hoverRevealDelay: state.hoverRevealDelay,
|
|
hoverRevealPersist: state.hoverRevealPersist ? 'true' : '',
|
|
hoverPersistOnClick: state.hoverPersistOnClick ? 'true' : '',
|
|
};
|
|
}, [state]);
|
|
|
|
/**
|
|
* Gallery card operations
|
|
*/
|
|
const addGalleryCard = useCallback(() => {
|
|
setState((prev) => ({
|
|
...prev,
|
|
galleryCards: [
|
|
...prev.galleryCards,
|
|
{
|
|
id: createLocalId(),
|
|
imageUrl: '',
|
|
title: `Card ${prev.galleryCards.length + 1}`,
|
|
description: '',
|
|
},
|
|
],
|
|
}));
|
|
}, []);
|
|
|
|
const removeGalleryCard = useCallback((cardId: string) => {
|
|
setState((prev) => ({
|
|
...prev,
|
|
galleryCards: prev.galleryCards.filter((card) => card.id !== cardId),
|
|
}));
|
|
}, []);
|
|
|
|
const updateGalleryCard = useCallback(
|
|
(cardId: string, field: keyof GalleryCard, value: string) => {
|
|
setState((prev) => ({
|
|
...prev,
|
|
galleryCards: prev.galleryCards.map((card) =>
|
|
card.id === cardId ? { ...card, [field]: value } : card,
|
|
),
|
|
}));
|
|
},
|
|
[],
|
|
);
|
|
|
|
/**
|
|
* Carousel slide operations
|
|
*/
|
|
const addCarouselSlide = useCallback(() => {
|
|
setState((prev) => ({
|
|
...prev,
|
|
carouselSlides: [
|
|
...prev.carouselSlides,
|
|
{
|
|
id: createLocalId(),
|
|
imageUrl: '',
|
|
caption: `Slide ${prev.carouselSlides.length + 1}`,
|
|
},
|
|
],
|
|
}));
|
|
}, []);
|
|
|
|
const removeCarouselSlide = useCallback((slideId: string) => {
|
|
setState((prev) => ({
|
|
...prev,
|
|
carouselSlides: prev.carouselSlides.filter(
|
|
(slide) => slide.id !== slideId,
|
|
),
|
|
}));
|
|
}, []);
|
|
|
|
const updateCarouselSlide = useCallback(
|
|
(slideId: string, field: keyof CarouselSlide, value: string) => {
|
|
setState((prev) => ({
|
|
...prev,
|
|
carouselSlides: prev.carouselSlides.map((slide) =>
|
|
slide.id === slideId ? { ...slide, [field]: value } : slide,
|
|
),
|
|
}));
|
|
},
|
|
[],
|
|
);
|
|
|
|
/**
|
|
* Info panel section operations
|
|
*/
|
|
const moveInfoPanelSection = useCallback(
|
|
(sectionId: string, direction: 'up' | 'down') => {
|
|
setState((prev) => {
|
|
const sections = [...prev.infoPanelSections];
|
|
const index = sections.findIndex((s) => s.id === sectionId);
|
|
if (index === -1) return prev;
|
|
|
|
const newIndex = direction === 'up' ? index - 1 : index + 1;
|
|
if (newIndex < 0 || newIndex >= sections.length) return prev;
|
|
|
|
[sections[index], sections[newIndex]] = [
|
|
sections[newIndex],
|
|
sections[index],
|
|
];
|
|
return { ...prev, infoPanelSections: sections };
|
|
});
|
|
},
|
|
[],
|
|
);
|
|
|
|
const removeInfoPanelSection = useCallback((sectionId: string) => {
|
|
setState((prev) => ({
|
|
...prev,
|
|
infoPanelSections: prev.infoPanelSections.filter(
|
|
(s) => s.id !== sectionId,
|
|
),
|
|
}));
|
|
}, []);
|
|
|
|
const addInfoPanelSection = useCallback(
|
|
(sectionType: InfoPanelSectionType) => {
|
|
setState((prev) => {
|
|
const newSection: InfoPanelSectionInstance = {
|
|
id: generateSectionId(),
|
|
type: sectionType,
|
|
// Initialize with default settings for data sections
|
|
...(sectionType === 'spans' && { columns: 3, gap: '8', spans: [] }),
|
|
...(sectionType === 'cards' && { columns: 2, gap: '8', images: [] }),
|
|
...(sectionType === 'images' && { images: [] }),
|
|
};
|
|
return {
|
|
...prev,
|
|
infoPanelSections: [...prev.infoPanelSections, newSection],
|
|
};
|
|
});
|
|
},
|
|
[],
|
|
);
|
|
|
|
/**
|
|
* Build settings JSON for saving
|
|
*/
|
|
const buildSettingsJson = useCallback((): Record<string, unknown> => {
|
|
const borderWidthValue = toUnitValue(state.border, 'px');
|
|
const borderRadiusValue = toUnitValue(state.borderRadius, 'px');
|
|
const settings: Record<string, unknown> = {
|
|
label: state.label.trim(),
|
|
xPercent: clampPercent(state.xPercent),
|
|
yPercent: clampPercent(state.yPercent),
|
|
border: borderWidthValue
|
|
? `${borderWidthValue} solid currentColor`
|
|
: 'none',
|
|
appearDelaySec:
|
|
Number(state.appearDelaySec) >= 0 ? Number(state.appearDelaySec) : 0,
|
|
appearDurationSec: parseNullableNumber(state.appearDurationSec),
|
|
};
|
|
|
|
// borderRadius: only include if has value (allows cascade from global defaults)
|
|
if (borderRadiusValue) settings.borderRadius = borderRadiusValue;
|
|
|
|
// Dimensional CSS properties
|
|
const widthValue = toUnitValue(state.width, 'vw');
|
|
const heightValue = toUnitValue(state.height, 'vh');
|
|
const minWidthValue = toUnitValue(state.minWidth, 'vw');
|
|
const maxWidthValue = toUnitValue(state.maxWidth, 'vw');
|
|
const minHeightValue = toUnitValue(state.minHeight, 'vh');
|
|
const maxHeightValue = toUnitValue(state.maxHeight, 'vh');
|
|
|
|
if (widthValue) settings.width = widthValue;
|
|
if (heightValue) settings.height = heightValue;
|
|
if (minWidthValue) settings.minWidth = minWidthValue;
|
|
if (maxWidthValue) settings.maxWidth = maxWidthValue;
|
|
if (minHeightValue) settings.minHeight = minHeightValue;
|
|
if (maxHeightValue) settings.maxHeight = maxHeightValue;
|
|
|
|
// Other CSS properties
|
|
const marginValue = toOptionalTrimmed(state.margin);
|
|
const paddingValue = toOptionalTrimmed(state.padding);
|
|
const gapValue = toOptionalTrimmed(state.gap);
|
|
const fontSizeValue = toOptionalTrimmed(state.fontSize);
|
|
const lineHeightValue = toOptionalTrimmed(state.lineHeight);
|
|
const fontWeightValue = toOptionalTrimmed(state.fontWeight);
|
|
const opacityValue = toOptionalTrimmed(state.opacity);
|
|
const boxShadowValue = toOptionalTrimmed(state.boxShadow);
|
|
const displayValue = toOptionalTrimmed(state.display);
|
|
const positionValue = toOptionalTrimmed(state.position);
|
|
const justifyContentValue = toOptionalTrimmed(state.justifyContent);
|
|
const alignItemsValue = toOptionalTrimmed(state.alignItems);
|
|
const textAlignValue = toOptionalTrimmed(state.textAlign);
|
|
const zIndexValue = toOptionalTrimmed(state.zIndex);
|
|
|
|
if (marginValue) settings.margin = marginValue;
|
|
if (paddingValue) settings.padding = paddingValue;
|
|
if (gapValue) settings.gap = gapValue;
|
|
if (fontSizeValue) settings.fontSize = fontSizeValue;
|
|
if (lineHeightValue) settings.lineHeight = lineHeightValue;
|
|
if (fontWeightValue) settings.fontWeight = fontWeightValue;
|
|
if (opacityValue) settings.opacity = opacityValue;
|
|
if (boxShadowValue) settings.boxShadow = boxShadowValue;
|
|
if (displayValue) settings.display = displayValue;
|
|
if (positionValue) settings.position = positionValue;
|
|
if (justifyContentValue) settings.justifyContent = justifyContentValue;
|
|
if (alignItemsValue) settings.alignItems = alignItemsValue;
|
|
if (textAlignValue) settings.textAlign = textAlignValue;
|
|
if (zIndexValue) settings.zIndex = zIndexValue;
|
|
|
|
// Additional CSS properties
|
|
const backgroundColorValue = toOptionalTrimmed(state.backgroundColor);
|
|
const colorValue = toOptionalTrimmed(state.color);
|
|
|
|
if (backgroundColorValue) settings.backgroundColor = backgroundColorValue;
|
|
if (colorValue) settings.color = colorValue;
|
|
|
|
// Effect properties
|
|
const appearAnimationValue = toOptionalTrimmed(state.appearAnimation);
|
|
const appearAnimationDurationValue = toOptionalTrimmed(
|
|
state.appearAnimationDuration,
|
|
);
|
|
const appearAnimationEasingValue = toOptionalTrimmed(
|
|
state.appearAnimationEasing,
|
|
);
|
|
const hoverScaleValue = toOptionalTrimmed(state.hoverScale);
|
|
const hoverOpacityValue = toOptionalTrimmed(state.hoverOpacity);
|
|
const hoverBackgroundColorValue = toOptionalTrimmed(
|
|
state.hoverBackgroundColor,
|
|
);
|
|
const hoverColorValue = toOptionalTrimmed(state.hoverColor);
|
|
const hoverBoxShadowValue = toOptionalTrimmed(state.hoverBoxShadow);
|
|
const hoverTransitionDurationValue = toOptionalTrimmed(
|
|
state.hoverTransitionDuration,
|
|
);
|
|
const focusScaleValue = toOptionalTrimmed(state.focusScale);
|
|
const focusOpacityValue = toOptionalTrimmed(state.focusOpacity);
|
|
const focusOutlineValue = toOptionalTrimmed(state.focusOutline);
|
|
const focusBoxShadowValue = toOptionalTrimmed(state.focusBoxShadow);
|
|
const activeScaleValue = toOptionalTrimmed(state.activeScale);
|
|
const activeOpacityValue = toOptionalTrimmed(state.activeOpacity);
|
|
const activeBackgroundColorValue = toOptionalTrimmed(
|
|
state.activeBackgroundColor,
|
|
);
|
|
|
|
if (appearAnimationValue) settings.appearAnimation = appearAnimationValue;
|
|
if (appearAnimationDurationValue)
|
|
settings.appearAnimationDuration = appearAnimationDurationValue;
|
|
if (appearAnimationEasingValue)
|
|
settings.appearAnimationEasing = appearAnimationEasingValue;
|
|
if (hoverScaleValue) settings.hoverScale = hoverScaleValue;
|
|
if (hoverOpacityValue) settings.hoverOpacity = hoverOpacityValue;
|
|
if (hoverBackgroundColorValue)
|
|
settings.hoverBackgroundColor = hoverBackgroundColorValue;
|
|
if (hoverColorValue) settings.hoverColor = hoverColorValue;
|
|
if (hoverBoxShadowValue) settings.hoverBoxShadow = hoverBoxShadowValue;
|
|
if (hoverTransitionDurationValue)
|
|
settings.hoverTransitionDuration = hoverTransitionDurationValue;
|
|
if (focusScaleValue) settings.focusScale = focusScaleValue;
|
|
if (focusOpacityValue) settings.focusOpacity = focusOpacityValue;
|
|
if (focusOutlineValue) settings.focusOutline = focusOutlineValue;
|
|
if (focusBoxShadowValue) settings.focusBoxShadow = focusBoxShadowValue;
|
|
if (activeScaleValue) settings.activeScale = activeScaleValue;
|
|
if (activeOpacityValue) settings.activeOpacity = activeOpacityValue;
|
|
if (activeBackgroundColorValue)
|
|
settings.activeBackgroundColor = activeBackgroundColorValue;
|
|
|
|
// Hover Reveal properties (booleans saved directly, strings trimmed)
|
|
if (state.hoverReveal) settings.hoverReveal = true;
|
|
const hoverRevealInitialOpacityValue = toOptionalTrimmed(
|
|
state.hoverRevealInitialOpacity,
|
|
);
|
|
const hoverRevealTargetOpacityValue = toOptionalTrimmed(
|
|
state.hoverRevealTargetOpacity,
|
|
);
|
|
const hoverRevealDurationValue = toOptionalTrimmed(
|
|
state.hoverRevealDuration,
|
|
);
|
|
const hoverRevealDelayValue = toOptionalTrimmed(state.hoverRevealDelay);
|
|
|
|
if (hoverRevealInitialOpacityValue)
|
|
settings.hoverRevealInitialOpacity = hoverRevealInitialOpacityValue;
|
|
if (hoverRevealTargetOpacityValue)
|
|
settings.hoverRevealTargetOpacity = hoverRevealTargetOpacityValue;
|
|
if (hoverRevealDurationValue)
|
|
settings.hoverRevealDuration = hoverRevealDurationValue;
|
|
if (hoverRevealDelayValue)
|
|
settings.hoverRevealDelay = hoverRevealDelayValue;
|
|
if (state.hoverRevealPersist) settings.hoverRevealPersist = true;
|
|
if (state.hoverPersistOnClick) settings.hoverPersistOnClick = true;
|
|
|
|
// Navigation type settings
|
|
if (isNavigationType) {
|
|
settings.iconUrl = state.iconUrl.trim();
|
|
settings.navLabel = state.navLabel.trim();
|
|
settings.navLabelFontFamily = state.navLabelFontFamily.trim();
|
|
settings.navType = state.navType;
|
|
settings.navDisabled = state.navDisabled;
|
|
settings.targetPageId = state.targetPageId.trim();
|
|
settings.targetPageSlug = state.targetPageSlug.trim();
|
|
settings.transitionVideoUrl = state.transitionVideoUrl.trim();
|
|
settings.transitionReverseMode = state.transitionReverseMode;
|
|
settings.reverseVideoUrl = state.reverseVideoUrl.trim();
|
|
}
|
|
|
|
// Description type settings
|
|
// Note: Color/fontSize/fontWeight cascade from General Element Styles via CSS inheritance
|
|
// Only set section-specific values if explicitly configured (allows inheritance)
|
|
if (isDescriptionType) {
|
|
settings.iconUrl = state.iconUrl.trim();
|
|
settings.descriptionTitle = state.descriptionTitle.trim();
|
|
settings.descriptionText = state.descriptionText;
|
|
// Only include if explicitly set - allows CSS inheritance from wrapper
|
|
if (state.descriptionTitleFontSize.trim()) {
|
|
settings.descriptionTitleFontSize =
|
|
state.descriptionTitleFontSize.trim();
|
|
}
|
|
if (state.descriptionTextFontSize.trim()) {
|
|
settings.descriptionTextFontSize = state.descriptionTextFontSize.trim();
|
|
}
|
|
if (state.descriptionTitleFontFamily.trim()) {
|
|
settings.descriptionTitleFontFamily =
|
|
state.descriptionTitleFontFamily.trim();
|
|
}
|
|
if (state.descriptionTextFontFamily.trim()) {
|
|
settings.descriptionTextFontFamily =
|
|
state.descriptionTextFontFamily.trim();
|
|
}
|
|
if (state.descriptionTitleColor.trim()) {
|
|
settings.descriptionTitleColor = state.descriptionTitleColor.trim();
|
|
}
|
|
if (state.descriptionTextColor.trim()) {
|
|
settings.descriptionTextColor = state.descriptionTextColor.trim();
|
|
}
|
|
}
|
|
|
|
// Gallery type settings
|
|
if (isGalleryType) {
|
|
settings.galleryCards = state.galleryCards.map((card, index) => ({
|
|
id: String(card.id || createLocalId()),
|
|
imageUrl: card.imageUrl.trim(),
|
|
title: card.title.trim(),
|
|
description: card.description,
|
|
}));
|
|
settings.galleryTitleFontFamily = state.galleryTitleFontFamily.trim();
|
|
settings.galleryTextFontFamily = state.galleryTextFontFamily.trim();
|
|
}
|
|
|
|
// Carousel type settings
|
|
if (isCarouselType) {
|
|
settings.carouselSlides = state.carouselSlides.map((slide) => ({
|
|
id: String(slide.id || createLocalId()),
|
|
imageUrl: slide.imageUrl.trim(),
|
|
caption: slide.caption.trim(),
|
|
}));
|
|
settings.carouselPrevIconUrl = state.carouselPrevIconUrl.trim();
|
|
settings.carouselNextIconUrl = state.carouselNextIconUrl.trim();
|
|
settings.carouselCaptionFontFamily =
|
|
state.carouselCaptionFontFamily.trim();
|
|
}
|
|
|
|
// Media type settings
|
|
if (isMediaType) {
|
|
settings.mediaUrl = state.mediaUrl.trim();
|
|
settings.mediaAutoplay = state.mediaAutoplay;
|
|
settings.mediaLoop = state.mediaLoop;
|
|
settings.mediaMuted = state.mediaMuted;
|
|
}
|
|
|
|
// Info Panel type settings
|
|
if (isInfoPanelType) {
|
|
// Trigger button settings
|
|
settings.iconUrl = state.iconUrl.trim();
|
|
settings.infoPanelTriggerLabel = state.infoPanelTriggerLabel.trim();
|
|
settings.infoPanelTriggerFontFamily =
|
|
state.infoPanelTriggerFontFamily.trim();
|
|
settings.infoPanelDisabled = state.infoPanelDisabled;
|
|
|
|
// Panel content
|
|
settings.panelTitle = state.panelTitle.trim();
|
|
settings.panelText = state.panelText;
|
|
|
|
// Panel position & styling
|
|
settings.panelXPercent = clampPercent(state.panelXPercent);
|
|
settings.panelYPercent = clampPercent(state.panelYPercent);
|
|
const panelWidthValue = toOptionalTrimmed(state.panelWidth);
|
|
const panelHeightValue = toOptionalTrimmed(state.panelHeight);
|
|
if (panelWidthValue) settings.panelWidth = panelWidthValue;
|
|
if (panelHeightValue) settings.panelHeight = panelHeightValue;
|
|
const panelBgColorValue = toOptionalTrimmed(state.panelBackgroundColor);
|
|
const panelBorderRadiusValue = toOptionalTrimmed(state.panelBorderRadius);
|
|
const panelPaddingValue = toOptionalTrimmed(state.panelPadding);
|
|
const panelBackdropBlurValue = toOptionalTrimmed(state.panelBackdropBlur);
|
|
if (panelBgColorValue) settings.panelBackgroundColor = panelBgColorValue;
|
|
if (panelBorderRadiusValue)
|
|
settings.panelBorderRadius = panelBorderRadiusValue;
|
|
if (panelPaddingValue) settings.panelPadding = panelPaddingValue;
|
|
if (panelBackdropBlurValue)
|
|
settings.panelBackdropBlur = panelBackdropBlurValue;
|
|
|
|
// Panel text styling
|
|
const panelTitleColorValue = toOptionalTrimmed(state.panelTitleColor);
|
|
const panelTitleFontSizeValue = toOptionalTrimmed(
|
|
state.panelTitleFontSize,
|
|
);
|
|
const panelTitleFontFamilyValue = toOptionalTrimmed(
|
|
state.panelTitleFontFamily,
|
|
);
|
|
const panelTextColorValue = toOptionalTrimmed(state.panelTextColor);
|
|
const panelTextFontSizeValue = toOptionalTrimmed(state.panelTextFontSize);
|
|
const panelTextFontFamilyValue = toOptionalTrimmed(
|
|
state.panelTextFontFamily,
|
|
);
|
|
const panelOverlayColorValue = toOptionalTrimmed(state.panelOverlayColor);
|
|
if (panelTitleColorValue) settings.panelTitleColor = panelTitleColorValue;
|
|
if (panelTitleFontSizeValue)
|
|
settings.panelTitleFontSize = panelTitleFontSizeValue;
|
|
if (panelTitleFontFamilyValue)
|
|
settings.panelTitleFontFamily = panelTitleFontFamilyValue;
|
|
if (panelTextColorValue) settings.panelTextColor = panelTextColorValue;
|
|
if (panelTextFontSizeValue)
|
|
settings.panelTextFontSize = panelTextFontSizeValue;
|
|
if (panelTextFontFamilyValue)
|
|
settings.panelTextFontFamily = panelTextFontFamilyValue;
|
|
if (panelOverlayColorValue)
|
|
settings.panelOverlayColor = panelOverlayColorValue;
|
|
|
|
// Detail panel position & styling
|
|
settings.detailXPercent = clampPercent(state.detailXPercent);
|
|
settings.detailYPercent = clampPercent(state.detailYPercent);
|
|
const detailWidthValue = toOptionalTrimmed(state.detailWidth);
|
|
const detailHeightValue = toOptionalTrimmed(state.detailHeight);
|
|
if (detailWidthValue) settings.detailWidth = detailWidthValue;
|
|
if (detailHeightValue) settings.detailHeight = detailHeightValue;
|
|
const detailBgColorValue = toOptionalTrimmed(state.detailBackgroundColor);
|
|
const detailBorderRadiusValue = toOptionalTrimmed(
|
|
state.detailBorderRadius,
|
|
);
|
|
const detailPaddingValue = toOptionalTrimmed(state.detailPadding);
|
|
if (detailBgColorValue)
|
|
settings.detailBackgroundColor = detailBgColorValue;
|
|
if (detailBorderRadiusValue)
|
|
settings.detailBorderRadius = detailBorderRadiusValue;
|
|
if (detailPaddingValue) settings.detailPadding = detailPaddingValue;
|
|
const detailCaptionFontFamilyValue = toOptionalTrimmed(
|
|
state.detailCaptionFontFamily,
|
|
);
|
|
if (detailCaptionFontFamilyValue)
|
|
settings.detailCaptionFontFamily = detailCaptionFontFamilyValue;
|
|
// Note: detailOverlayColor removed - parent InfoPanelOverlay provides backdrop
|
|
|
|
// Header Section (like Gallery)
|
|
const headerImageUrlValue = toOptionalTrimmed(
|
|
state.infoPanelHeaderImageUrl,
|
|
);
|
|
const headerTextValue = toOptionalTrimmed(state.infoPanelHeaderText);
|
|
if (headerImageUrlValue)
|
|
settings.infoPanelHeaderImageUrl = headerImageUrlValue;
|
|
if (headerTextValue) settings.infoPanelHeaderText = headerTextValue;
|
|
const headerBgColorValue = toOptionalTrimmed(
|
|
state.infoPanelHeaderBackgroundColor,
|
|
);
|
|
const headerColorValue = toOptionalTrimmed(state.infoPanelHeaderColor);
|
|
const headerFontFamilyValue = toOptionalTrimmed(
|
|
state.infoPanelHeaderFontFamily,
|
|
);
|
|
const headerFontSizeValue = toOptionalTrimmed(
|
|
state.infoPanelHeaderFontSize,
|
|
);
|
|
const headerFontWeightValue = toOptionalTrimmed(
|
|
state.infoPanelHeaderFontWeight,
|
|
);
|
|
const headerPaddingValue = toOptionalTrimmed(
|
|
state.infoPanelHeaderPadding,
|
|
);
|
|
const headerBorderRadiusValue = toOptionalTrimmed(
|
|
state.infoPanelHeaderBorderRadius,
|
|
);
|
|
const headerTextAlignValue = toOptionalTrimmed(
|
|
state.infoPanelHeaderTextAlign,
|
|
);
|
|
const headerMinHeightValue = toOptionalTrimmed(
|
|
state.infoPanelHeaderMinHeight,
|
|
);
|
|
if (headerBgColorValue)
|
|
settings.infoPanelHeaderBackgroundColor = headerBgColorValue;
|
|
if (headerColorValue) settings.infoPanelHeaderColor = headerColorValue;
|
|
if (headerFontFamilyValue)
|
|
settings.infoPanelHeaderFontFamily = headerFontFamilyValue;
|
|
if (headerFontSizeValue)
|
|
settings.infoPanelHeaderFontSize = headerFontSizeValue;
|
|
if (headerFontWeightValue)
|
|
settings.infoPanelHeaderFontWeight = headerFontWeightValue;
|
|
if (headerPaddingValue)
|
|
settings.infoPanelHeaderPadding = headerPaddingValue;
|
|
if (headerBorderRadiusValue)
|
|
settings.infoPanelHeaderBorderRadius = headerBorderRadiusValue;
|
|
if (headerTextAlignValue)
|
|
settings.infoPanelHeaderTextAlign = headerTextAlignValue;
|
|
if (headerMinHeightValue)
|
|
settings.infoPanelHeaderMinHeight = headerMinHeightValue;
|
|
|
|
// Title Section enhanced styling
|
|
const titleBgColorValue = toOptionalTrimmed(
|
|
state.infoPanelTitleBackgroundColor,
|
|
);
|
|
const titlePaddingValue = toOptionalTrimmed(state.infoPanelTitlePadding);
|
|
const titleFontWeightValue = toOptionalTrimmed(
|
|
state.infoPanelTitleFontWeight,
|
|
);
|
|
const titleTextAlignValue = toOptionalTrimmed(
|
|
state.infoPanelTitleTextAlign,
|
|
);
|
|
if (titleBgColorValue)
|
|
settings.infoPanelTitleBackgroundColor = titleBgColorValue;
|
|
if (titlePaddingValue) settings.infoPanelTitlePadding = titlePaddingValue;
|
|
if (titleFontWeightValue)
|
|
settings.infoPanelTitleFontWeight = titleFontWeightValue;
|
|
if (titleTextAlignValue)
|
|
settings.infoPanelTitleTextAlign = titleTextAlignValue;
|
|
|
|
// Span styling
|
|
const spanBgColorValue = toOptionalTrimmed(
|
|
state.infoPanelSpanBackgroundColor,
|
|
);
|
|
const spanColorValue = toOptionalTrimmed(state.infoPanelSpanColor);
|
|
const spanFontFamilyValue = toOptionalTrimmed(
|
|
state.infoPanelSpanFontFamily,
|
|
);
|
|
const spanFontSizeValue = toOptionalTrimmed(state.infoPanelSpanFontSize);
|
|
const spanPaddingValue = toOptionalTrimmed(state.infoPanelSpanPadding);
|
|
const spanBorderRadiusValue = toOptionalTrimmed(
|
|
state.infoPanelSpanBorderRadius,
|
|
);
|
|
if (spanBgColorValue)
|
|
settings.infoPanelSpanBackgroundColor = spanBgColorValue;
|
|
if (spanColorValue) settings.infoPanelSpanColor = spanColorValue;
|
|
if (spanFontFamilyValue)
|
|
settings.infoPanelSpanFontFamily = spanFontFamilyValue;
|
|
if (spanFontSizeValue) settings.infoPanelSpanFontSize = spanFontSizeValue;
|
|
if (spanPaddingValue) settings.infoPanelSpanPadding = spanPaddingValue;
|
|
if (spanBorderRadiusValue)
|
|
settings.infoPanelSpanBorderRadius = spanBorderRadiusValue;
|
|
|
|
// Card styling
|
|
const cardBgColorValue = toOptionalTrimmed(
|
|
state.infoPanelCardBackgroundColor,
|
|
);
|
|
const cardBorderRadiusValue = toOptionalTrimmed(
|
|
state.infoPanelCardBorderRadius,
|
|
);
|
|
const cardAspectRatioValue = toOptionalTrimmed(
|
|
state.infoPanelCardAspectRatio,
|
|
);
|
|
const cardMinHeightValue = toOptionalTrimmed(
|
|
state.infoPanelCardMinHeight,
|
|
);
|
|
if (cardBgColorValue)
|
|
settings.infoPanelCardBackgroundColor = cardBgColorValue;
|
|
if (cardBorderRadiusValue)
|
|
settings.infoPanelCardBorderRadius = cardBorderRadiusValue;
|
|
if (cardAspectRatioValue)
|
|
settings.infoPanelCardAspectRatio = cardAspectRatioValue;
|
|
if (cardMinHeightValue)
|
|
settings.infoPanelCardMinHeight = cardMinHeightValue;
|
|
|
|
// Card title styling
|
|
const cardTitleBgColorValue = toOptionalTrimmed(
|
|
state.infoPanelCardTitleBackgroundColor,
|
|
);
|
|
const cardTitleColorValue = toOptionalTrimmed(
|
|
state.infoPanelCardTitleColor,
|
|
);
|
|
const cardTitleFontFamilyValue = toOptionalTrimmed(
|
|
state.infoPanelCardTitleFontFamily,
|
|
);
|
|
const cardTitleFontSizeValue = toOptionalTrimmed(
|
|
state.infoPanelCardTitleFontSize,
|
|
);
|
|
const cardTitlePaddingValue = toOptionalTrimmed(
|
|
state.infoPanelCardTitlePadding,
|
|
);
|
|
if (cardTitleBgColorValue)
|
|
settings.infoPanelCardTitleBackgroundColor = cardTitleBgColorValue;
|
|
if (cardTitleColorValue)
|
|
settings.infoPanelCardTitleColor = cardTitleColorValue;
|
|
if (cardTitleFontFamilyValue)
|
|
settings.infoPanelCardTitleFontFamily = cardTitleFontFamilyValue;
|
|
if (cardTitleFontSizeValue)
|
|
settings.infoPanelCardTitleFontSize = cardTitleFontSizeValue;
|
|
if (cardTitlePaddingValue)
|
|
settings.infoPanelCardTitlePadding = cardTitlePaddingValue;
|
|
|
|
// Section instances - always save (contains order and per-section settings)
|
|
settings.infoPanelSections = state.infoPanelSections;
|
|
}
|
|
|
|
return settings;
|
|
}, [
|
|
state,
|
|
isNavigationType,
|
|
isDescriptionType,
|
|
isGalleryType,
|
|
isCarouselType,
|
|
isMediaType,
|
|
isInfoPanelType,
|
|
]);
|
|
|
|
return {
|
|
state,
|
|
setField,
|
|
setFields,
|
|
applySettings,
|
|
getStyleValues,
|
|
getEffectValues,
|
|
|
|
// Type checks
|
|
isNavigationType,
|
|
isDescriptionType,
|
|
isGalleryType,
|
|
isCarouselType,
|
|
isMediaType,
|
|
isInfoPanelType,
|
|
|
|
// Gallery operations
|
|
addGalleryCard,
|
|
removeGalleryCard,
|
|
updateGalleryCard,
|
|
|
|
// Carousel operations
|
|
addCarouselSlide,
|
|
removeCarouselSlide,
|
|
updateCarouselSlide,
|
|
|
|
// Info panel section ordering operations
|
|
moveInfoPanelSection,
|
|
removeInfoPanelSection,
|
|
addInfoPanelSection,
|
|
|
|
// Build JSON
|
|
buildSettingsJson,
|
|
};
|
|
}
|
|
|
|
export type { FormState };
|