@@ -105,7 +105,7 @@ class VisualObject {
105105 return ;
106106 }
107107
108- let config : LiveUpdateOptions | undefined ;
108+ let config : LiveUpdateOptions | ( ( value : any ) => void ) | undefined ;
109109
110110 if ( block && mappings . blocks ?. [ block . type ] ) {
111111 config = mappings . blocks [ block . type ] [ settingId ] ;
@@ -117,6 +117,12 @@ class VisualObject {
117117 return ;
118118 }
119119
120+ if ( typeof config === 'function' ) {
121+ // then use it as a custom handler, no target
122+ config ( settingValue ) ;
123+ return skipRefresh ( ) ;
124+ }
125+
120126 const targetEl = config . target ? ( container . querySelector ( config . target ) as HTMLElement ) : null ;
121127
122128 if ( ! targetEl ) {
@@ -161,6 +167,8 @@ class ThemeEditor {
161167 private hoverDebounce = 0 ;
162168 private reorderingSectionId : string | null = null ;
163169
170+ private sectionContainers = new Map < string , HTMLElement > ( ) ;
171+
164172 private discardLivewireComponentNotFoundError = false ;
165173
166174 private messageHandlers : Record < string , ( data : any , messageId ?: string ) => void > = {
@@ -191,6 +199,7 @@ class ThemeEditor {
191199 } ) ;
192200
193201 this . sectionsOrder = window . themeData . sectionsOrder ;
202+ this . buildSectionContainers ( ) ;
194203
195204 window . addEventListener ( 'message' , ( { data } ) => this . handleMessage ( data ) ) ;
196205 window . addEventListener ( 'resize' , ( ) => this . handleWindowResize ( ) ) ;
@@ -219,6 +228,23 @@ class ThemeEditor {
219228 this . removeBtn = this . sectionOverlay . querySelector ( '#remove' ) as HTMLButtonElement ;
220229 }
221230
231+ private buildSectionContainers ( ) {
232+ document . querySelectorAll ( `[${ ATTRS . SectionId } ]` ) . forEach ( ( el ) => {
233+ this . sectionContainers . set ( ( el as HTMLElement ) . dataset . sectionId ! , el . parentNode as HTMLElement ) ;
234+ } ) ;
235+ }
236+
237+ private findCommentParent ( text : string ) : Node | null {
238+ const iterator = document . createNodeIterator ( document . body , NodeFilter . SHOW_COMMENT , {
239+ acceptNode ( node ) {
240+ return node . nodeValue ?. trim ( ) === text ? NodeFilter . FILTER_ACCEPT : NodeFilter . FILTER_SKIP ;
241+ } ,
242+ } ) ;
243+
244+ const commentNode = iterator . nextNode ( ) ;
245+ return commentNode ?. parentNode ?? null ;
246+ }
247+
222248 private attachButtonEvents ( ) {
223249 this . moveUpBtn . onclick = ( ) => this . postMessage ( ACTIONS . MOVE_SECTION_UP , this . activeSectionId ) ;
224250 this . moveDownBtn . onclick = ( ) => this . postMessage ( ACTIONS . MOVE_SECTION_DOWN , this . activeSectionId ) ;
@@ -568,13 +594,59 @@ class ThemeEditor {
568594 } ) ;
569595 }
570596
597+ private patchScripts ( existingContainer : Element , newContainer : Element ) {
598+ const existingScripts = Array . from ( existingContainer . querySelectorAll ( 'script' ) ) ;
599+ const newScripts = Array . from ( newContainer . querySelectorAll ( 'script' ) ) ;
600+
601+ newScripts . forEach ( ( newScript ) => {
602+ const isInline = ! newScript . src ;
603+ const matchesExisting = existingScripts . some ( ( existingScript ) => {
604+ // Match external scripts by src
605+ if ( ! isInline && existingScript . src === newScript . src ) return true ;
606+
607+ // Match inline scripts by content and attributes
608+ if ( isInline && existingScript . textContent === newScript . textContent ) {
609+ return Array . from ( newScript . attributes ) . every (
610+ ( attr ) => existingScript . getAttribute ( attr . name ) === attr . value
611+ ) ;
612+ }
613+
614+ return false ;
615+ } ) ;
616+
617+ if ( ! matchesExisting ) {
618+ const executableScript = document . createElement ( 'script' ) ;
619+
620+ // Copy attributes
621+ Array . from ( newScript . attributes ) . forEach ( ( attr ) => {
622+ executableScript . setAttribute ( attr . name , attr . value ) ;
623+ } ) ;
624+
625+ // Copy inline content
626+ if ( isInline ) {
627+ executableScript . textContent = newScript . textContent ;
628+ }
629+
630+ // Append to existing container to execute
631+ existingContainer . appendChild ( executableScript ) ;
632+ }
633+ } ) ;
634+ }
635+
571636 private refreshPreviewer ( { html, updatedSections } : { html : string ; updatedSections : Map < string , any > } ) {
572637 const newDoc = new DOMParser ( ) . parseFromString ( html , 'text/html' ) ;
638+ const sectionContainers = this . sectionContainers ;
639+
640+ morphdom ( document . head , newDoc . head ) ;
573641
574642 if ( updatedSections . size === 0 ) {
575- this . patchNode ( document . documentElement , newDoc . documentElement ) ;
643+ this . patchNode ( document . body , newDoc . body ) ;
644+ window . Visual . _dispatch ( 'page:load' , { } ) ;
576645 // document.documentElement.innerHTML = newDoc.documentElement.innerHTML;
577646 } else {
647+ const templateContainer = this . findCommentParent ( 'BEGIN: template' ) as HTMLElement ;
648+ const sections = document . querySelectorAll ( `[${ ATTRS . SectionType } ]` ) ;
649+
578650 updatedSections . forEach ( ( context : any , sectionId : string ) => {
579651 const oldEl = document . querySelector ( `[data-section-id="${ sectionId } "]` ) ;
580652 const newEl = newDoc . querySelector ( `[data-section-id="${ sectionId } "]` ) ;
@@ -583,20 +655,26 @@ class ThemeEditor {
583655 window . Visual . _dispatch ( EVENTS . SECTION_UNLOAD , context ) ;
584656 this . patchNode ( oldEl , newEl ) ;
585657 } else if ( ! oldEl && newEl ) {
586- const sections = document . querySelectorAll ( `[${ ATTRS . SectionType } ]` ) ;
587658 const position = context . position ?? sections . length ;
588- const parent = sections [ 0 ] ?. parentNode ;
589-
590- if ( ! parent ) {
591- return ;
592- }
659+ const sectionId = ( newEl as HTMLElement ) . dataset . sectionId as string ;
593660
594661 if ( position <= 0 ) {
595- parent . insertBefore ( newEl , sections [ 0 ] ) ;
662+ let parent = sectionContainers . get ( sectionId ) ?? templateContainer ;
663+ parent . insertBefore ( newEl , parent . firstChild ) ;
664+
665+ if ( ! sectionContainers . has ( sectionId ) ) {
666+ sectionContainers . set ( sectionId , parent ) ;
667+ }
596668 } else if ( position >= sections . length ) {
669+ let parent = sectionContainers . get ( sectionId ) ?? templateContainer ;
597670 parent . appendChild ( newEl ) ;
671+
672+ if ( ! sectionContainers . has ( sectionId ) ) {
673+ sectionContainers . set ( sectionId , parent ) ;
674+ }
598675 } else {
599- parent . insertBefore ( newEl , sections [ position ] ) ;
676+ const nextSection = sections [ position ] ;
677+ nextSection . parentNode ?. insertBefore ( newEl , nextSection ) ;
600678 }
601679 } else {
602680 return ;
@@ -606,6 +684,8 @@ class ThemeEditor {
606684 } ) ;
607685 }
608686
687+ this . patchScripts ( document . body , newDoc . body ) ;
688+
609689 if ( this . activeSectionId ) {
610690 const el = document . querySelector ( `[${ ATTRS . SectionId } ="${ this . activeSectionId } "]` ) as HTMLElement ;
611691 if ( el ) {
0 commit comments