44 useInnerBlocksProps ,
55 InspectorControls ,
66 InspectorAdvancedControls ,
7+ BlockControls ,
78} from '@wordpress/block-editor' ;
89import {
910 PanelBody ,
@@ -13,25 +14,26 @@ import {
1314 BaseControl ,
1415 TextControl ,
1516 RangeControl ,
17+ Placeholder ,
18+ Button ,
19+ ToolbarButton ,
1620} from '@wordpress/components' ;
17- import { useSelect } from '@wordpress/data' ;
18- import { useState , useMemo } from '@wordpress/element' ;
21+ import { plus } from '@wordpress/icons' ;
22+ import { useSelect , useDispatch } from '@wordpress/data' ;
23+ import { useState , useMemo , useCallback } from '@wordpress/element' ;
24+ import { createBlock , type BlockConfiguration } from '@wordpress/blocks' ;
1925import type { CarouselAttributes } from './types' ;
20- import type { BlockConfiguration , Template } from '@wordpress/blocks' ;
2126import { EditorCarouselContext } from './editor-context' ;
2227import type { EmblaCarouselType } from 'embla-carousel' ;
2328
24- const TEMPLATE : Template [ ] = [
25- [ 'carousel-kit/carousel-viewport' , { } ] ,
26- [ 'carousel-kit/carousel-controls' , { } ] ,
27- ] ;
28-
2929export default function Edit ( {
3030 attributes,
3131 setAttributes,
32+ clientId,
3233} : {
3334 attributes : CarouselAttributes ;
3435 setAttributes : ( attrs : Partial < CarouselAttributes > ) => void ;
36+ clientId : string ;
3537} ) {
3638 const {
3739 loop,
@@ -54,12 +56,37 @@ export default function Edit( {
5456 const [ canScrollPrev , setCanScrollPrev ] = useState ( false ) ;
5557 const [ canScrollNext , setCanScrollNext ] = useState ( false ) ;
5658
57- // Fetch all registered block types for suggestions
58- const blockTypes = useSelect ( ( select ) => {
59- return (
59+ const { replaceInnerBlocks, insertBlock } = useDispatch ( 'core/block-editor' ) ;
60+
61+ const hasInnerBlocks = useSelect (
62+ ( select ) =>
6063 // eslint-disable-next-line @typescript-eslint/no-explicit-any
61- select ( 'core/blocks' ) as any
62- ) . getBlockTypes ( ) as BlockConfiguration [ ] ;
64+ ( select ( 'core/block-editor' ) as any ) . getBlockCount ( clientId ) > 0 ,
65+ [ clientId ] ,
66+ ) ;
67+
68+ const viewportClientId = useSelect (
69+ ( select ) => {
70+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
71+ const innerBlocks = ( select ( 'core/block-editor' ) as any ) . getBlocks ( clientId ) as Array < { name : string ; clientId : string } > ;
72+ return innerBlocks . find ( ( b ) => b . name === 'carousel-kit/carousel-viewport' ) ?. clientId ;
73+ } ,
74+ [ clientId ] ,
75+ ) ;
76+
77+ const addSlide = useCallback ( ( ) => {
78+ if ( ! viewportClientId ) {
79+ return ;
80+ }
81+ insertBlock ( createBlock ( 'carousel-kit/carousel-slide' ) , undefined , viewportClientId ) ;
82+ } , [ insertBlock , viewportClientId ] ) ;
83+
84+ const showSetup = ! hasInnerBlocks ;
85+
86+ // Fetch registered block types for the allowed-blocks token field
87+ const blockTypes = useSelect ( ( select ) => {
88+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
89+ return ( select ( 'core/blocks' ) as any ) . getBlockTypes ( ) as BlockConfiguration [ ] ;
6390 } , [ ] ) ;
6491
6592 const suggestions = blockTypes ?. map ( ( block ) => block . name ) || [ ] ;
@@ -75,11 +102,8 @@ export default function Edit( {
75102 } as React . CSSProperties ,
76103 } ) ;
77104
78- const innerBlocksProps = useInnerBlocksProps ( blockProps , {
79- template : TEMPLATE ,
80- } ) ;
105+ const innerBlocksProps = useInnerBlocksProps ( blockProps , { } ) ;
81106
82- // Memoize carouselOptions separately to prevent excessive viewport reinitializations
83107 const carouselOptions = useMemo (
84108 ( ) => ( {
85109 loop,
@@ -94,8 +118,6 @@ export default function Edit( {
94118 [ loop , dragFree , carouselAlign , containScroll , direction , axis , height , slidesToScroll ] ,
95119 ) ;
96120
97- // Memoize the context value to prevent infinite re-renders in children
98- // Note: setState functions are stable and don't need to be in dependencies
99121 const contextValue = useMemo (
100122 ( ) => ( {
101123 emblaApi,
@@ -117,8 +139,49 @@ export default function Edit( {
117139 ] ,
118140 ) ;
119141
120- return (
121- < EditorCarouselContext . Provider value = { contextValue } >
142+ const createNavGroup = ( ) =>
143+ createBlock (
144+ 'core/group' ,
145+ {
146+ layout : {
147+ type : 'flex' ,
148+ flexWrap : 'nowrap' ,
149+ justifyContent : 'space-between' ,
150+ } ,
151+ } ,
152+ [
153+ createBlock ( 'carousel-kit/carousel-controls' , { } ) ,
154+ createBlock ( 'carousel-kit/carousel-dots' , { } ) ,
155+ ] ,
156+ ) ;
157+
158+ const handleSetup = ( slideCount : number ) => {
159+ const slides = Array . from ( { length : slideCount } , ( ) =>
160+ createBlock ( 'carousel-kit/carousel-slide' , { } , [
161+ createBlock ( 'core/paragraph' , { } ) ,
162+ ] ) ,
163+ ) ;
164+
165+ replaceInnerBlocks (
166+ clientId ,
167+ [ createBlock ( 'carousel-kit/carousel-viewport' , { } , slides ) , createNavGroup ( ) ] ,
168+ false ,
169+ ) ;
170+ } ;
171+
172+ /**
173+ * Skip — still creates the correct structure, just without slides.
174+ */
175+ const handleSkip = ( ) => {
176+ replaceInnerBlocks (
177+ clientId ,
178+ [ createBlock ( 'carousel-kit/carousel-viewport' , { } ) , createNavGroup ( ) ] ,
179+ false ,
180+ ) ;
181+ } ;
182+
183+ const inspectorControls = (
184+ < >
122185 < InspectorControls >
123186 < PanelBody title = { __ ( 'Carousel Settings' , 'carousel-kit' ) } >
124187 < ToggleControl
@@ -145,9 +208,7 @@ export default function Edit( {
145208 { label : __ ( 'End' , 'carousel-kit' ) , value : 'end' } ,
146209 ] }
147210 onChange = { ( value ) =>
148- setAttributes ( {
149- carouselAlign : value as CarouselAttributes [ 'carouselAlign' ] ,
150- } )
211+ setAttributes ( { carouselAlign : value as CarouselAttributes [ 'carouselAlign' ] } )
151212 }
152213 />
153214 < SelectControl
@@ -159,9 +220,7 @@ export default function Edit( {
159220 { label : __ ( 'None' , 'carousel-kit' ) , value : '' } ,
160221 ] }
161222 onChange = { ( value ) =>
162- setAttributes ( {
163- containScroll : value as CarouselAttributes [ 'containScroll' ] ,
164- } )
223+ setAttributes ( { containScroll : value as CarouselAttributes [ 'containScroll' ] } )
165224 }
166225 help = { __ (
167226 'Prevents excess scrolling at the beginning or end.' ,
@@ -171,8 +230,13 @@ export default function Edit( {
171230 < ToggleControl
172231 label = { __ ( 'Scroll Auto' , 'carousel-kit' ) }
173232 checked = { slidesToScroll === 'auto' }
174- onChange = { ( isAuto ) => setAttributes ( { slidesToScroll : isAuto ? 'auto' : '1' } ) }
175- help = { __ ( 'Scrolls the number of slides currently visible in the viewport.' , 'carousel-kit' ) }
233+ onChange = { ( isAuto ) =>
234+ setAttributes ( { slidesToScroll : isAuto ? 'auto' : '1' } )
235+ }
236+ help = { __ (
237+ 'Scrolls the number of slides currently visible in the viewport.' ,
238+ 'carousel-kit' ,
239+ ) }
176240 />
177241 { slidesToScroll !== 'auto' && (
178242 < RangeControl
@@ -193,9 +257,7 @@ export default function Edit( {
193257 { label : __ ( 'Right to Left (RTL)' , 'carousel-kit' ) , value : 'rtl' } ,
194258 ] }
195259 onChange = { ( value ) =>
196- setAttributes ( {
197- direction : value as CarouselAttributes [ 'direction' ] ,
198- } )
260+ setAttributes ( { direction : value as CarouselAttributes [ 'direction' ] } )
199261 }
200262 help = { __ (
201263 'Choose content direction. RTL is typically used for Arabic, Hebrew, and other right-to-left languages.' ,
@@ -210,9 +272,7 @@ export default function Edit( {
210272 { label : __ ( 'Vertical' , 'carousel-kit' ) , value : 'y' } ,
211273 ] }
212274 onChange = { ( value ) =>
213- setAttributes ( {
214- axis : value as CarouselAttributes [ 'axis' ] ,
215- } )
275+ setAttributes ( { axis : value as CarouselAttributes [ 'axis' ] } )
216276 }
217277 />
218278 { axis === 'y' && (
@@ -322,6 +382,57 @@ export default function Edit( {
322382 />
323383 </ PanelBody >
324384 </ InspectorControls >
385+ </ >
386+ ) ;
387+
388+ if ( showSetup ) {
389+ return (
390+ < EditorCarouselContext . Provider value = { contextValue } >
391+ { inspectorControls }
392+ < div { ...blockProps } >
393+ < Placeholder
394+ icon = "columns"
395+ label = { __ ( 'Carousel' , 'carousel-kit' ) }
396+ instructions = { __ ( 'How many slides would you like to start with?' , 'carousel-kit' ) }
397+ className = "carousel-kit-setup"
398+ >
399+ < div className = "carousel-kit-setup__options" >
400+ { [ 1 , 2 , 3 , 4 ] . map ( ( count ) => (
401+ < Button
402+ key = { count }
403+ variant = "secondary"
404+ className = "carousel-kit-setup__option"
405+ onClick = { ( ) => handleSetup ( count ) }
406+ >
407+ { count === 1
408+ ? __ ( '1 Slide' , 'carousel-kit' )
409+ : `${ count } ${ __ ( 'Slides' , 'carousel-kit' ) } ` }
410+ </ Button >
411+ ) ) }
412+ </ div >
413+ < Button
414+ variant = "link"
415+ className = "carousel-kit-setup__skip"
416+ onClick = { handleSkip }
417+ >
418+ { __ ( 'Skip' , 'carousel-kit' ) }
419+ </ Button >
420+ </ Placeholder >
421+ </ div >
422+ </ EditorCarouselContext . Provider >
423+ ) ;
424+ }
425+
426+ return (
427+ < EditorCarouselContext . Provider value = { contextValue } >
428+ < BlockControls >
429+ < ToolbarButton
430+ icon = { plus }
431+ label = { __ ( 'Add Slide' , 'carousel-kit' ) }
432+ onClick = { addSlide }
433+ />
434+ </ BlockControls >
435+ { inspectorControls }
325436 < div { ...innerBlocksProps } />
326437 </ EditorCarouselContext . Provider >
327438 ) ;
0 commit comments