761 lines
26 KiB
TypeScript
761 lines
26 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,
|
|
} from '../../types/constructor';
|
|
import { parseJsonObject } from '../../lib/parseJson';
|
|
import {
|
|
extractNumericValue,
|
|
clampPercent,
|
|
parseNullableNumber,
|
|
toUnitValue,
|
|
toOptionalTrimmed,
|
|
} from './types';
|
|
import {
|
|
createLocalId,
|
|
isNavigationElementType,
|
|
isTooltipElementType,
|
|
isDescriptionElementType,
|
|
isGalleryElementType,
|
|
isCarouselElementType,
|
|
isMediaElementType,
|
|
} 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;
|
|
|
|
// 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;
|
|
|
|
// Tooltip settings
|
|
tooltipTitle: string;
|
|
tooltipText: string;
|
|
tooltipTitleFontFamily: string;
|
|
tooltipTextFontFamily: 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[];
|
|
}
|
|
|
|
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: '',
|
|
iconUrl: '',
|
|
navLabel: '',
|
|
navLabelFontFamily: '',
|
|
navType: 'forward',
|
|
navDisabled: false,
|
|
targetPageId: '',
|
|
targetPageSlug: '',
|
|
transitionVideoUrl: '',
|
|
transitionReverseMode: 'auto_reverse',
|
|
reverseVideoUrl: '',
|
|
tooltipTitle: '',
|
|
tooltipText: '',
|
|
tooltipTitleFontFamily: '',
|
|
tooltipTextFontFamily: '',
|
|
descriptionTitle: '',
|
|
descriptionText: '',
|
|
descriptionTitleFontSize: '',
|
|
descriptionTextFontSize: '',
|
|
descriptionTitleFontFamily: '',
|
|
descriptionTextFontFamily: '',
|
|
descriptionTitleColor: '',
|
|
descriptionTextColor: '',
|
|
mediaUrl: '',
|
|
mediaAutoplay: false,
|
|
mediaLoop: false,
|
|
mediaMuted: false,
|
|
carouselPrevIconUrl: '',
|
|
carouselNextIconUrl: '',
|
|
carouselCaptionFontFamily: '',
|
|
galleryTitleFontFamily: '',
|
|
galleryTextFontFamily: '',
|
|
galleryCards: [],
|
|
carouselSlides: [],
|
|
};
|
|
|
|
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 isTooltipType = isTooltipElementType(elementType);
|
|
const isDescriptionType = isDescriptionElementType(elementType);
|
|
const isGalleryType = isGalleryElementType(elementType);
|
|
const isCarouselType = isCarouselElementType(elementType);
|
|
const isMediaType = isMediaElementType(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 || ''),
|
|
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 || ''),
|
|
tooltipTitle: String(settings.tooltipTitle || ''),
|
|
tooltipText: String(settings.tooltipText || ''),
|
|
tooltipTitleFontFamily: String(settings.tooltipTitleFontFamily || ''),
|
|
tooltipTextFontFamily: String(settings.tooltipTextFontFamily || ''),
|
|
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 ?? ''),
|
|
}),
|
|
)
|
|
: [],
|
|
});
|
|
},
|
|
[],
|
|
);
|
|
|
|
/**
|
|
* 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,
|
|
};
|
|
}, [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,
|
|
),
|
|
}));
|
|
},
|
|
[],
|
|
);
|
|
|
|
/**
|
|
* 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;
|
|
|
|
// 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();
|
|
}
|
|
|
|
// Tooltip type settings
|
|
if (isTooltipType) {
|
|
settings.iconUrl = state.iconUrl.trim();
|
|
settings.tooltipTitle = state.tooltipTitle.trim();
|
|
settings.tooltipText = state.tooltipText;
|
|
settings.tooltipTitleFontFamily = state.tooltipTitleFontFamily.trim();
|
|
settings.tooltipTextFontFamily = state.tooltipTextFontFamily.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;
|
|
}
|
|
|
|
return settings;
|
|
}, [
|
|
state,
|
|
isNavigationType,
|
|
isTooltipType,
|
|
isDescriptionType,
|
|
isGalleryType,
|
|
isCarouselType,
|
|
isMediaType,
|
|
]);
|
|
|
|
return {
|
|
state,
|
|
setField,
|
|
setFields,
|
|
applySettings,
|
|
getStyleValues,
|
|
getEffectValues,
|
|
|
|
// Type checks
|
|
isNavigationType,
|
|
isTooltipType,
|
|
isDescriptionType,
|
|
isGalleryType,
|
|
isCarouselType,
|
|
isMediaType,
|
|
|
|
// Gallery operations
|
|
addGalleryCard,
|
|
removeGalleryCard,
|
|
updateGalleryCard,
|
|
|
|
// Carousel operations
|
|
addCarouselSlide,
|
|
removeCarouselSlide,
|
|
updateCarouselSlide,
|
|
|
|
// Build JSON
|
|
buildSettingsJson,
|
|
};
|
|
}
|
|
|
|
export type { FormState };
|