fixed slides titles issue

This commit is contained in:
Dmitri 2026-04-03 17:50:02 +04:00
parent fd57a3bf10
commit b5f8f30360
4 changed files with 76 additions and 61 deletions

View File

@ -334,20 +334,20 @@ export function useElementSettingsForm(options: UseElementSettingsFormOptions) {
galleryTextFontFamily: String(settings.galleryTextFontFamily || ''), galleryTextFontFamily: String(settings.galleryTextFontFamily || ''),
galleryCards: Array.isArray(settings.galleryCards) galleryCards: Array.isArray(settings.galleryCards)
? settings.galleryCards.map( ? settings.galleryCards.map(
(card: Record<string, unknown>, index: number) => ({ (card: Record<string, unknown>) => ({
id: String(card?.id || createLocalId()), id: String(card?.id || createLocalId()),
imageUrl: String(card?.imageUrl || ''), imageUrl: String(card?.imageUrl ?? ''),
title: String(card?.title || `Card ${index + 1}`), title: String(card?.title ?? ''),
description: String(card?.description || ''), description: String(card?.description ?? ''),
}), }),
) )
: [], : [],
carouselSlides: Array.isArray(settings.carouselSlides) carouselSlides: Array.isArray(settings.carouselSlides)
? settings.carouselSlides.map( ? settings.carouselSlides.map(
(slide: Record<string, unknown>, index: number) => ({ (slide: Record<string, unknown>) => ({
id: String(slide?.id || createLocalId()), id: String(slide?.id || createLocalId()),
imageUrl: String(slide?.imageUrl || ''), imageUrl: String(slide?.imageUrl ?? ''),
caption: String(slide?.caption || `Slide ${index + 1}`), caption: String(slide?.caption ?? ''),
}), }),
) )
: [], : [],
@ -687,7 +687,7 @@ export function useElementSettingsForm(options: UseElementSettingsFormOptions) {
settings.galleryCards = state.galleryCards.map((card, index) => ({ settings.galleryCards = state.galleryCards.map((card, index) => ({
id: String(card.id || createLocalId()), id: String(card.id || createLocalId()),
imageUrl: card.imageUrl.trim(), imageUrl: card.imageUrl.trim(),
title: card.title.trim() || `Card ${index + 1}`, title: card.title.trim(),
description: card.description, description: card.description,
})); }));
settings.galleryTitleFontFamily = state.galleryTitleFontFamily.trim(); settings.galleryTitleFontFamily = state.galleryTitleFontFamily.trim();
@ -696,10 +696,10 @@ export function useElementSettingsForm(options: UseElementSettingsFormOptions) {
// Carousel type settings // Carousel type settings
if (isCarouselType) { if (isCarouselType) {
settings.carouselSlides = state.carouselSlides.map((slide, index) => ({ settings.carouselSlides = state.carouselSlides.map((slide) => ({
id: String(slide.id || createLocalId()), id: String(slide.id || createLocalId()),
imageUrl: slide.imageUrl.trim(), imageUrl: slide.imageUrl.trim(),
caption: slide.caption.trim() || `Slide ${index + 1}`, caption: slide.caption.trim(),
})); }));
settings.carouselPrevIconUrl = state.carouselPrevIconUrl.trim(); settings.carouselPrevIconUrl = state.carouselPrevIconUrl.trim();
settings.carouselNextIconUrl = state.carouselNextIconUrl.trim(); settings.carouselNextIconUrl = state.carouselNextIconUrl.trim();

View File

@ -70,8 +70,12 @@ interface UseConstructorElementsResult {
clearSelection: () => void; clearSelection: () => void;
/** Add a new element of the given type */ /** Add a new element of the given type */
addElement: (type: CanvasElementType) => void; addElement: (type: CanvasElementType) => void;
/** Update the selected element with a partial patch */ /** Update the selected element with a partial patch (or callback for fresh state) */
updateSelectedElement: (patch: Partial<CanvasElement>) => void; updateSelectedElement: (
patchOrFn:
| Partial<CanvasElement>
| ((current: CanvasElement) => Partial<CanvasElement>),
) => void;
/** Update an element by ID with a partial patch */ /** Update an element by ID with a partial patch */
updateElement: (elementId: string, patch: Partial<CanvasElement>) => void; updateElement: (elementId: string, patch: Partial<CanvasElement>) => void;
/** Remove the selected element */ /** Remove the selected element */
@ -227,13 +231,20 @@ export function useConstructorElements({
); );
const updateSelectedElement = useCallback( const updateSelectedElement = useCallback(
(patch: Partial<CanvasElement>) => { (
patchOrFn:
| Partial<CanvasElement>
| ((current: CanvasElement) => Partial<CanvasElement>),
) => {
if (!selectedElementId) return; if (!selectedElementId) return;
setElements((prev) => { setElements((prev) => {
const next = prev.map((el) => const next = prev.map((el) => {
el.id === selectedElementId ? { ...el, ...patch } : el, if (el.id !== selectedElementId) return el;
); const patch =
typeof patchOrFn === 'function' ? patchOrFn(el) : patchOrFn;
return { ...el, ...patch };
});
onElementsChange?.(next); onElementsChange?.(next);
return next; return next;
}); });
@ -328,10 +339,12 @@ export function useConstructorElements({
update: (cardId: string, patch: Partial<GalleryCard>) => { update: (cardId: string, patch: Partial<GalleryCard>) => {
if (!selectedElement || !isGalleryElementType(selectedElement.type)) if (!selectedElement || !isGalleryElementType(selectedElement.type))
return; return;
const nextCards = (selectedElement.galleryCards || []).map((card) => // Use callback to get fresh element state (avoids stale closure on rapid updates)
card.id === cardId ? { ...card, ...patch } : card, updateSelectedElement((current) => ({
); galleryCards: (current.galleryCards || []).map((card) =>
updateSelectedElement({ galleryCards: nextCards }); card.id === cardId ? { ...card, ...patch } : card,
),
}));
}, },
remove: (cardId: string) => { remove: (cardId: string) => {
if (!selectedElement || !isGalleryElementType(selectedElement.type)) if (!selectedElement || !isGalleryElementType(selectedElement.type))
@ -363,10 +376,12 @@ export function useConstructorElements({
update: (spanId: string, patch: Partial<GalleryInfoSpan>) => { update: (spanId: string, patch: Partial<GalleryInfoSpan>) => {
if (!selectedElement || !isGalleryElementType(selectedElement.type)) if (!selectedElement || !isGalleryElementType(selectedElement.type))
return; return;
const nextSpans = (selectedElement.galleryInfoSpans || []).map( // Use callback to get fresh element state (avoids stale closure on rapid updates)
(span) => (span.id === spanId ? { ...span, ...patch } : span), updateSelectedElement((current) => ({
); galleryInfoSpans: (current.galleryInfoSpans || []).map((span) =>
updateSelectedElement({ galleryInfoSpans: nextSpans }); span.id === spanId ? { ...span, ...patch } : span,
),
}));
}, },
remove: (spanId: string) => { remove: (spanId: string) => {
if (!selectedElement || !isGalleryElementType(selectedElement.type)) if (!selectedElement || !isGalleryElementType(selectedElement.type))
@ -399,10 +414,12 @@ export function useConstructorElements({
update: (slideId: string, patch: Partial<CarouselSlide>) => { update: (slideId: string, patch: Partial<CarouselSlide>) => {
if (!selectedElement || !isCarouselElementType(selectedElement.type)) if (!selectedElement || !isCarouselElementType(selectedElement.type))
return; return;
const nextSlides = (selectedElement.carouselSlides || []).map( // Use callback to get fresh element state (avoids stale closure on rapid updates)
(slide) => (slide.id === slideId ? { ...slide, ...patch } : slide), updateSelectedElement((current) => ({
); carouselSlides: (current.carouselSlides || []).map((slide) =>
updateSelectedElement({ carouselSlides: nextSlides }); slide.id === slideId ? { ...slide, ...patch } : slide,
),
}));
}, },
remove: (slideId: string) => { remove: (slideId: string) => {
if (!selectedElement || !isCarouselElementType(selectedElement.type)) if (!selectedElement || !isCarouselElementType(selectedElement.type))

View File

@ -224,12 +224,11 @@ export const createDefaultElement = (
*/ */
export const normalizeGalleryCard = ( export const normalizeGalleryCard = (
card: Record<string, unknown>, card: Record<string, unknown>,
index: number,
): GalleryCard => ({ ): GalleryCard => ({
id: String(card?.id || createLocalId()), id: String(card?.id || createLocalId()),
imageUrl: String(card?.imageUrl || ''), imageUrl: String(card?.imageUrl ?? ''),
title: String(card?.title || `Card ${index + 1}`), title: String(card?.title ?? ''),
description: String(card?.description || ''), description: String(card?.description ?? ''),
}); });
/** /**
@ -239,7 +238,7 @@ export const normalizeGalleryInfoSpan = (
span: Record<string, unknown>, span: Record<string, unknown>,
): GalleryInfoSpan => ({ ): GalleryInfoSpan => ({
id: String(span?.id || createLocalId()), id: String(span?.id || createLocalId()),
text: String(span?.text || ''), text: String(span?.text ?? ''),
iconUrl: span?.iconUrl ? String(span.iconUrl) : undefined, iconUrl: span?.iconUrl ? String(span.iconUrl) : undefined,
}); });
@ -248,11 +247,10 @@ export const normalizeGalleryInfoSpan = (
*/ */
export const normalizeCarouselSlide = ( export const normalizeCarouselSlide = (
slide: Record<string, unknown>, slide: Record<string, unknown>,
index: number,
): CarouselSlide => ({ ): CarouselSlide => ({
id: String(slide?.id || createLocalId()), id: String(slide?.id || createLocalId()),
imageUrl: String(slide?.imageUrl || ''), imageUrl: String(slide?.imageUrl ?? ''),
caption: String(slide?.caption || `Slide ${index + 1}`), caption: String(slide?.caption ?? ''),
}); });
/** /**
@ -323,8 +321,8 @@ export const mergeElementWithDefaults = (
: Array.isArray(defaults.galleryCards) : Array.isArray(defaults.galleryCards)
? defaults.galleryCards ? defaults.galleryCards
: element.galleryCards || []; : element.galleryCards || [];
merged.galleryCards = cards.map((card, i) => merged.galleryCards = cards.map((card) =>
normalizeGalleryCard(card as unknown as Record<string, unknown>, i), normalizeGalleryCard(card as unknown as Record<string, unknown>),
); );
// Handle gallery info spans array // Handle gallery info spans array
@ -349,8 +347,8 @@ export const mergeElementWithDefaults = (
: Array.isArray(defaults.carouselSlides) : Array.isArray(defaults.carouselSlides)
? defaults.carouselSlides ? defaults.carouselSlides
: element.carouselSlides || []; : element.carouselSlides || [];
merged.carouselSlides = slides.map((slide, i) => merged.carouselSlides = slides.map((slide) =>
normalizeCarouselSlide(slide as unknown as Record<string, unknown>, i), normalizeCarouselSlide(slide as unknown as Record<string, unknown>),
); );
} }
@ -380,8 +378,8 @@ export const parseElementSettings = (
// Parse gallery cards if present // Parse gallery cards if present
if (Array.isArray(settings.galleryCards)) { if (Array.isArray(settings.galleryCards)) {
settings.galleryCards = settings.galleryCards.map((card, i) => settings.galleryCards = settings.galleryCards.map((card) =>
normalizeGalleryCard(card as Record<string, unknown>, i), normalizeGalleryCard(card as Record<string, unknown>),
); );
} }
@ -394,8 +392,8 @@ export const parseElementSettings = (
// Parse carousel slides if present // Parse carousel slides if present
if (Array.isArray(settings.carouselSlides)) { if (Array.isArray(settings.carouselSlides)) {
settings.carouselSlides = settings.carouselSlides.map((slide, i) => settings.carouselSlides = settings.carouselSlides.map((slide) =>
normalizeCarouselSlide(slide as Record<string, unknown>, i), normalizeCarouselSlide(slide as Record<string, unknown>),
); );
} }
@ -550,18 +548,18 @@ export const buildElementSettings = (
// Gallery type settings // Gallery type settings
if (isGalleryElementType(elementType)) { if (isGalleryElementType(elementType)) {
if (Array.isArray(element.galleryCards)) { if (Array.isArray(element.galleryCards)) {
settings.galleryCards = element.galleryCards.map((card, i) => ({ settings.galleryCards = element.galleryCards.map((card) => ({
id: String(card.id || createLocalId()), id: String(card.id || createLocalId()),
imageUrl: card.imageUrl || '', imageUrl: card.imageUrl ?? '',
title: card.title || `Card ${i + 1}`, title: card.title ?? '',
description: card.description || '', description: card.description ?? '',
})); }));
} }
if (Array.isArray(element.galleryInfoSpans)) { if (Array.isArray(element.galleryInfoSpans)) {
settings.galleryInfoSpans = element.galleryInfoSpans.map((span) => ({ settings.galleryInfoSpans = element.galleryInfoSpans.map((span) => ({
id: String(span.id || createLocalId()), id: String(span.id || createLocalId()),
text: span.text || '', text: span.text ?? '',
iconUrl: span.iconUrl || undefined, iconUrl: span.iconUrl ?? undefined,
})); }));
} }
addIfNotEmpty( addIfNotEmpty(
@ -592,10 +590,10 @@ export const buildElementSettings = (
// Carousel type settings // Carousel type settings
if (isCarouselElementType(elementType)) { if (isCarouselElementType(elementType)) {
if (Array.isArray(element.carouselSlides)) { if (Array.isArray(element.carouselSlides)) {
settings.carouselSlides = element.carouselSlides.map((slide, i) => ({ settings.carouselSlides = element.carouselSlides.map((slide) => ({
id: String(slide.id || createLocalId()), id: String(slide.id || createLocalId()),
imageUrl: slide.imageUrl || '', imageUrl: slide.imageUrl ?? '',
caption: slide.caption || `Slide ${i + 1}`, caption: slide.caption ?? '',
})); }));
} }
addIfNotEmpty(settings, 'carouselPrevIconUrl', element.carouselPrevIconUrl); addIfNotEmpty(settings, 'carouselPrevIconUrl', element.carouselPrevIconUrl);

View File

@ -820,11 +820,11 @@ const ConstructorPage = ({ mode = 'constructor' }: ConstructorPageProps) => {
item.appearDurationSec, item.appearDurationSec,
), ),
galleryCards: Array.isArray(item.galleryCards) galleryCards: Array.isArray(item.galleryCards)
? item.galleryCards.map((card: any, index: number) => ({ ? item.galleryCards.map((card: any) => ({
id: String(card?.id || createLocalId()), id: String(card?.id || createLocalId()),
imageUrl: String(card?.imageUrl || ''), imageUrl: String(card?.imageUrl ?? ''),
title: String(card?.title || `Card ${index + 1}`), title: String(card?.title ?? ''),
description: String(card?.description || ''), description: String(card?.description ?? ''),
})) }))
: undefined, : undefined,
galleryHeaderImageUrl: galleryHeaderImageUrl:
@ -838,7 +838,7 @@ const ConstructorPage = ({ mode = 'constructor' }: ConstructorPageProps) => {
galleryInfoSpans: Array.isArray(item.galleryInfoSpans) galleryInfoSpans: Array.isArray(item.galleryInfoSpans)
? item.galleryInfoSpans.map((span: any) => ({ ? item.galleryInfoSpans.map((span: any) => ({
id: String(span?.id || createLocalId()), id: String(span?.id || createLocalId()),
text: String(span?.text || ''), text: String(span?.text ?? ''),
})) }))
: undefined, : undefined,
galleryColumns: galleryColumns:
@ -846,10 +846,10 @@ const ConstructorPage = ({ mode = 'constructor' }: ConstructorPageProps) => {
? item.galleryColumns ? item.galleryColumns
: undefined, : undefined,
carouselSlides: Array.isArray(item.carouselSlides) carouselSlides: Array.isArray(item.carouselSlides)
? item.carouselSlides.map((slide: any, index: number) => ({ ? item.carouselSlides.map((slide: any) => ({
id: String(slide?.id || createLocalId()), id: String(slide?.id || createLocalId()),
imageUrl: String(slide?.imageUrl || ''), imageUrl: String(slide?.imageUrl ?? ''),
caption: String(slide?.caption || `Slide ${index + 1}`), caption: String(slide?.caption ?? ''),
})) }))
: undefined, : undefined,
iconUrl: typeof item.iconUrl === 'string' ? item.iconUrl : '', iconUrl: typeof item.iconUrl === 'string' ? item.iconUrl : '',