/** * Slider block inspector controls. * Matches the original SliderSettings structure using shared components. */ import type { ReactNode } from "react"; import { useEffect, useMemo, useState } from "@wordpress/element"; import { InspectorControls } from "@wordpress/block-editor"; import { __ } from "@wordpress/i18n"; import { PanelBody, RangeControl, ToggleControl, __experimentalToggleGroupControl as ToggleGroupControl, __experimentalToggleGroupControlOption as ToggleGroupControlOption, } from "@wordpress/components"; import { applyFilters } from "@wordpress/hooks"; import { UBSelectControl, ColorSettingsWithToolsPanel, TabsPanelControl, SpacingControl, } from "@sliderberg/shared"; import type { SliderAttributes } from "./constants"; import { isTransitionEffectAllowedInCarousel } from "./swiper-effects"; import { deriveMutedColor } from "./color-utils"; import { LockedControl } from "../../components/pro-upsell"; const isPro: boolean = window.sliderbergData?.isPro ?? false; interface SliderInspectorProps { attributes: SliderAttributes; setAttributes: (attrs: Partial) => void; } /** * Animation settings panel items */ function AnimationSettings({ attributes, setAttributes, }: SliderInspectorProps) { interface TransitionEffectOption { label: string; value: string; isPro?: boolean; supportsCarousel?: boolean; } const isCarouselMode = attributes.mode === "carousel"; const defaultEffects: TransitionEffectOption[] = [ { label: __("Slide", "sliderberg"), value: "slide", supportsCarousel: true, }, { label: __("Fade", "sliderberg"), value: "fade" }, { label: __("Zoom", "sliderberg"), value: "zoom" }, ]; const allEffects = applyFilters( "sliderberg.transitionEffects", defaultEffects, isCarouselMode, ) as TransitionEffectOption[]; const availableEffects = isCarouselMode ? allEffects.filter( (effect) => effect.supportsCarousel || isTransitionEffectAllowedInCarousel(effect.value), ) : allEffects; return ( <> setAttributes({ transitionEffect: value })} /> {isCarouselMode && (

{__( "Slide and Coverflow effects are available in carousel mode. Coverflow is previewed as Slide in the editor and works on the published page.", "sliderberg", )}

)} {!isCarouselMode && attributes.transitionEffect !== "slide" && (

{__( "Advanced transition effects are previewed as Slide in the editor. They will work correctly on the published page.", "sliderberg", )}

)} {!isPro && ( {}} /> )} setAttributes({ transitionDuration: value ?? 500, }) } min={200} max={2000} step={100} help={__("Duration of the transition in milliseconds", "sliderberg")} /> setAttributes({ transitionEasing: value })} /> ); } /** * Autoplay settings panel items */ function AutoplaySettings({ attributes, setAttributes }: SliderInspectorProps) { return ( <> setAttributes({ autoplay: value })} help={__( "Autoplay is only active on the published page, not in the editor.", "sliderberg", )} /> {attributes.autoplay && ( <> setAttributes({ autoplaySpeed: value ?? 5000, }) } min={1000} max={10000} step={500} /> setAttributes({ pauseOnHover: value })} /> )} ); } /** * Navigation settings panel items */ function NavigationSettings({ attributes, setAttributes, }: SliderInspectorProps) { const { navHorizontal, navVertical, navOutside, navFreePosition, dotsHorizontal, dotsVertical, prevFreeX, prevFreeY, nextFreeX, nextFreeY, dotsFreeX, dotsFreeY, navigationShape, navigationOpacity, hideNavigation, } = attributes; type FreeNavElement = "prev-arrow" | "next-arrow" | "dots"; const [activeNavEl, setActiveNavEl] = useState("prev-arrow"); return ( <> setAttributes({ navigationShape: value as string }) } >
{navFreePosition ? (
setActiveNavEl(value as FreeNavElement)} >
{activeNavEl === "prev-arrow" && ( <> setAttributes({ prevFreeX: value ?? 3 })} min={0} max={100} step={1} />
setAttributes({ prevFreeY: value ?? 50 }) } min={0} max={100} step={1} />
)} {activeNavEl === "next-arrow" && ( <> setAttributes({ nextFreeX: value ?? 97 }) } min={0} max={100} step={1} />
setAttributes({ nextFreeY: value ?? 50 }) } min={0} max={100} step={1} />
)} {activeNavEl === "dots" && ( <> setAttributes({ dotsFreeX: value ?? 50 }) } min={0} max={100} step={1} />
setAttributes({ dotsFreeY: value ?? 90 }) } min={0} max={100} step={1} />
)}
) : ( <> setAttributes({ navHorizontal: value as string }) } > setAttributes({ navVertical: value as string }) } > setAttributes({ dotsHorizontal: value as string }) } > setAttributes({ dotsVertical: value as string }) } > setAttributes({ navOutside: value })} help={__( "Position arrows outside the slide content area", "sliderberg", )} /> )} setAttributes({ navFreePosition: value })} help={__( "Set custom position for each element using percentage values", "sliderberg", )} /> setAttributes({ navigationOpacity: value ?? 1 })} min={0} max={1} step={0.1} /> setAttributes({ hideNavigation: value })} help={__("Hide the navigation arrow buttons", "sliderberg")} /> ); } function PaginationSettingsControls({ attributes, setAttributes, }: SliderInspectorProps) { return ( setAttributes({ hideDots: value })} help={__("Hide the slide indicator dots", "sliderberg")} /> ); } /** * Width control panel */ function WidthControl({ attributes, setAttributes }: SliderInspectorProps) { const widthPreset = attributes.widthPreset || "default"; const { customWidth } = attributes; const handlePresetChange = (preset: string) => { if (preset === "custom") { setAttributes({ widthPreset: preset, align: "", widthUnit: "px", }); } else { let alignValue: "wide" | "full" | "" = ""; if (preset === "wide") { alignValue = "wide"; } if (preset === "full") { alignValue = "full"; } setAttributes({ widthPreset: preset, align: alignValue, customWidth: "", widthUnit: "px", }); } }; const handleCustomWidthChange = (value?: number) => { const width = value ?? 600; let alignValue: "wide" | "full" | undefined; if (width > 0 && width < 1300) { alignValue = "wide"; } else if (width >= 1300) { alignValue = "full"; } setAttributes({ customWidth: String(width), widthUnit: "px", align: alignValue, }); }; return ( {widthPreset === "custom" && ( )} ); } /** * Responsive carousel settings with tabs */ function ResponsiveCarouselSettings({ attributes, setAttributes, }: SliderInspectorProps) { const { slidesToShow, slidesToScroll, slideSpacing, tabletSlidesToShow = 2, tabletSlidesToScroll = 1, tabletSlideSpacing = 15, mobileSlidesToShow = 1, mobileSlidesToScroll = 1, mobileSlideSpacing = 10, } = attributes; const tabs = [ { name: "desktop", title: __("Desktop", "sliderberg"), component: ( <>
setAttributes({ slidesToShow: value })} min={1} max={6} />
setAttributes({ slidesToScroll: value, }) } min={1} max={slidesToShow} />
setAttributes({ slideSpacing: value })} min={0} max={100} step={5} />
), }, { name: "tablet", title: __("Tablet", "sliderberg"), component: ( <>

{__("Settings for screens 768px - 1024px", "sliderberg")}

setAttributes({ tabletSlidesToShow: value, }) } min={1} max={4} />
setAttributes({ tabletSlidesToScroll: value, }) } min={1} max={tabletSlidesToShow} />
setAttributes({ tabletSlideSpacing: value, }) } min={0} max={100} step={5} />
), }, { name: "mobile", title: __("Mobile", "sliderberg"), component: ( <>

{__("Settings for screens below 768px", "sliderberg")}

setAttributes({ mobileSlidesToShow: value, }) } min={1} max={3} />
setAttributes({ mobileSlidesToScroll: value, }) } min={1} max={mobileSlidesToShow} />
setAttributes({ mobileSlideSpacing: value, }) } min={0} max={100} step={5} />
), }, ]; return (
); } interface CarouselSettingsPanelProps extends SliderInspectorProps { hideToggle?: boolean; panelInitialOpen?: boolean; } /** * Carousel settings panel */ function CarouselSettingsPanel({ attributes, setAttributes, hideToggle = false, panelInitialOpen = false, }: CarouselSettingsPanelProps) { const { mode, infiniteLoop } = attributes; const isCarouselMode = mode === "carousel"; return ( {!hideToggle && ( setAttributes({ mode: value ? "carousel" : "slider" }) } /> )} {isCarouselMode && ( <> setAttributes({ infiniteLoop: value })} help={__("Enable continuous looping of slides", "sliderberg")} /> )} ); } export default function SliderInspector({ attributes, setAttributes, }: SliderInspectorProps) { useEffect(() => { if ( attributes.mode === "carousel" && !isTransitionEffectAllowedInCarousel(attributes.transitionEffect) ) { setAttributes({ transitionEffect: "slide" }); } }, [attributes.mode, attributes.transitionEffect, setAttributes]); // Legacy: sync old navigationType/Placement from new attributes for backward compat useEffect(() => { const { prevArrowH, nextArrowH, prevArrowV, navOutside } = attributes; let legacyType = "split"; // If both arrows are not on opposite sides, derive legacy type from vertical if (!(prevArrowH === "left" && nextArrowH === "right")) { if (prevArrowV === "top") legacyType = "top"; else if (prevArrowV === "bottom") legacyType = "bottom"; } const legacyPlacement = navOutside ? "outside" : "overlay"; if ( attributes.navigationType !== legacyType || attributes.navigationPlacement !== legacyPlacement ) { setAttributes({ navigationType: legacyType, navigationPlacement: legacyPlacement, }); } }, [ attributes.prevArrowH, attributes.nextArrowH, attributes.prevArrowV, attributes.navOutside, attributes.navigationType, attributes.navigationPlacement, setAttributes, ]); // Allow pro plugin to control visible settings const visibleSettings = useMemo( () => applyFilters( "sliderberg.visibleSettings", ["width", "animation", "autoplay", "navigation", "carousel"], attributes.type, ) as string[], [attributes.type], ); const afterCoreSettings = useMemo( () => applyFilters( "sliderberg.afterCoreSettings", null, attributes, setAttributes, ) as ReactNode, [attributes, setAttributes], ); const paginationSettings = useMemo( () => applyFilters( "sliderberg.paginationSettings", null, attributes, setAttributes, ) as ReactNode, [attributes, setAttributes], ); return ( <> {!attributes.hideDots && ( <> { // Auto-derive a muted inactive color from the active one so // the pair stays visible together on any background. Users // who want a custom inactive color can still change the Dot // Color control afterward — it just won't re-derive until // Active Dot Color is changed again. if (newAttrs.dotActiveColor) { setAttributes({ dotColor: deriveMutedColor(newAttrs.dotActiveColor), }); } }} /> )} { applyFilters( "sliderberg.paginationColorControls", null, attributes, setAttributes, ) as ReactNode } {/* When carousel variation is active, promote its settings to the top */} {attributes.mode === "carousel" && visibleSettings.includes("carousel") && ( )} {visibleSettings.includes("width") && ( )} setAttributes({ minHeight: value ?? 400, }) } min={100} max={1000} step={10} /> { applyFilters( "sliderberg.layoutSettings", null, attributes, setAttributes, ) as ReactNode } {!isPro && ( {}} > )} {visibleSettings.includes("animation") && ( )} {visibleSettings.includes("autoplay") && ( )} {visibleSettings.includes("navigation") && ( { applyFilters( "sliderberg.navigationSettingsExtensions", null, attributes, setAttributes, ) as ReactNode } )} {paginationSettings} {!isPro && ( {}} /> )} {/* In slider mode, show the carousel toggle at the bottom as before */} {attributes.mode !== "carousel" && visibleSettings.includes("carousel") && ( )} {afterCoreSettings} ); }