@@ -160,28 +160,6 @@ def set_bind_dict(self, name: str, expression: Callable[[], dict[str, Any]]):
160160 """
161161 self ._binds .append ((name , expression , False ))
162162
163- def set_type (self , expression : Callable [[], str | Callable ]):
164- """
165- Set a dynamic type/tag based on the expression.
166- """
167-
168- @weak (self )
169- def update_type (self , tag ):
170- anchor = self .anchor ()
171- self .unmount (destroy = False )
172- self .tag = tag
173- self .mount (self .target , anchor )
174-
175- # Set the tag immediately
176- # TODO: In case of a component tag, do we maybe want to wait???
177- # So that we can build up a reactive props object or something?
178- self .tag = expression ()
179- self ._watchers ["type" ] = watch (
180- expression ,
181- update_type ,
182- immediate = False ,
183- )
184-
185163 def set_condition (self , expression : Callable [[], bool ]):
186164 """
187165 Set a expression that determines whether this fragment
@@ -301,9 +279,6 @@ def _remove(self):
301279 self .renderer .remove (self .element , self .target )
302280 self .element = None
303281
304- def _has_content (self ):
305- return bool (self .element )
306-
307282 def unmount (self , destroy = True ):
308283 self ._mounted = False
309284
@@ -605,6 +580,12 @@ def register_child(self, child: Fragment) -> None:
605580 else :
606581 self .children .append (child )
607582
583+ def first (self ) -> Any | None :
584+ """Return the first element from the rendered component fragment"""
585+ if self .fragment :
586+ return self .fragment .first ()
587+ return super ().first ()
588+
608589 def create (self ):
609590 if self .tag is None :
610591 return
@@ -683,12 +664,14 @@ def _rem_attr(self, attr):
683664 def _remove (self ):
684665 self .props = None
685666
686- def _has_content (self ):
687- return bool (self .props )
688-
689667 def unmount (self , destroy = True ):
690668 if self .component :
691669 self .component .before_unmount ()
670+
671+ # Unmount slot contents before calling super
672+ for slot_content in self .slot_contents :
673+ slot_content .unmount (destroy = destroy )
674+
692675 super ().unmount (destroy = destroy )
693676
694677
@@ -736,3 +719,185 @@ def mount(self, target: Any, anchor: Any | None = None):
736719 item .mount (target , anchor )
737720 else :
738721 super ().mount (target , anchor )
722+
723+
724+ class DynamicFragment (Fragment ):
725+ """
726+ Fragment for dynamic component tags: <component :is="expression" />
727+
728+ Handles switching between different tag types (components or elements)
729+ based on a reactive expression.
730+ """
731+
732+ def __init__ (
733+ self , renderer : Renderer , expression : Callable , parent : Fragment | None = None
734+ ):
735+ # Don't pass tag to parent - it will be dynamic
736+ super ().__init__ (renderer , tag = None , parent = parent )
737+
738+ # Store the expression that determines the tag
739+ self ._expression = expression
740+
741+ # Current active fragment (ComponentFragment or regular Fragment)
742+ self ._active_fragment : Fragment | None = None
743+
744+ # Watcher for the expression
745+ self ._type_watcher : Watcher | None = None
746+
747+ def create (self ):
748+ """Create the initial fragment based on expression value"""
749+ # Evaluate expression to get initial tag
750+ tag = self ._expression ()
751+
752+ # Create fragment for this tag
753+ self ._create_fragment_for_tag (tag )
754+
755+ # Set up watcher for tag changes
756+ @weak (self )
757+ def update_type (self , new_tag ):
758+ # Calculate anchor before unmounting
759+ anchor = self .anchor ()
760+
761+ # Unmount current fragment
762+ if self ._active_fragment :
763+ # Note: _active_fragment is no longer in self.children
764+ # (it's removed in _create_fragment_for_tag)
765+ self ._active_fragment .unmount (destroy = False )
766+
767+ # Create new fragment
768+ self ._create_fragment_for_tag (new_tag )
769+
770+ # Mount it
771+ if self ._active_fragment :
772+ self ._active_fragment .mount (self .target , anchor )
773+
774+ self ._type_watcher = watch (
775+ self ._expression ,
776+ update_type ,
777+ immediate = False ,
778+ )
779+
780+ def _create_fragment_for_tag (self , tag ):
781+ """Create appropriate fragment for the given tag"""
782+ # Save existing children before creating active fragment
783+ # If we had a ComponentFragment, children are in its slot_contents
784+ if (
785+ self ._active_fragment
786+ and isinstance (self ._active_fragment , ComponentFragment )
787+ and self ._active_fragment .tag is not None
788+ ):
789+ # Get children from previous ComponentFragment's slot_contents
790+ existing_children = self ._active_fragment .slot_contents .copy ()
791+ else :
792+ # Get children from DynamicFragment's children
793+ existing_children = self .children .copy ()
794+
795+ if callable (tag ):
796+ # Component class - create ComponentFragment
797+ # Don't pass parent, so that the register_child method is skipped
798+ # so that the _active_fragment won't be part of the children
799+ # which are whatever is specified in the template
800+ self ._active_fragment = ComponentFragment (
801+ self .renderer ,
802+ tag = tag ,
803+ props = reactive ({}),
804+ )
805+ # Manually set the parent
806+ self ._active_fragment ._parent = ref (self )
807+
808+ # Transfer existing children as slot content
809+ # ComponentFragment.register_child() adds them to slot_contents
810+ # when tag is set
811+ for child in existing_children :
812+ child ._parent = ref (self ._active_fragment )
813+ # Set slot_name to "default" if not already set (e.g., by v-slot:name)
814+ if not hasattr (child , "slot_name" ) or child .slot_name is None :
815+ child .slot_name = "default"
816+ self ._active_fragment .register_child (child )
817+ else :
818+ # String tag - create regular Fragment
819+ # Also don't pass parent here
820+ self ._active_fragment = Fragment (
821+ self .renderer ,
822+ tag = tag ,
823+ )
824+ # Manually set the parent
825+ self ._active_fragment ._parent = ref (self )
826+
827+ # Transfer existing children to the active fragment
828+ for child in existing_children :
829+ child ._parent = ref (self ._active_fragment )
830+ self ._active_fragment .children .append (child )
831+
832+ # Transfer attributes, bindings, events from self to active fragment
833+ self ._active_fragment ._attributes .update (self ._attributes )
834+ self ._active_fragment ._binds .extend (self ._binds )
835+ self ._active_fragment ._events .update (self ._events )
836+
837+ # Create the fragment
838+ self ._active_fragment .create ()
839+
840+ def mount (self , target : Any , anchor : Any | None = None ):
841+ if self ._mounted :
842+ return
843+
844+ self .target = target
845+ self .create ()
846+
847+ # Mount the active fragment
848+ if self ._active_fragment :
849+ self ._active_fragment .mount (target , anchor )
850+
851+ self ._mounted = True
852+
853+ def unmount (self , destroy = True ):
854+ self ._mounted = False
855+
856+ # Unmount active fragment
857+ if self ._active_fragment :
858+ self ._active_fragment .unmount (destroy = destroy )
859+ if destroy :
860+ self ._active_fragment = None
861+
862+ # Standard cleanup
863+ for child in self .children :
864+ child .unmount (destroy = destroy )
865+
866+ self ._remove ()
867+
868+ if destroy :
869+ self .element = None
870+ self .target = None
871+ self ._attributes = {}
872+ self ._events = {}
873+ if self ._type_watcher :
874+ self ._type_watcher .fn = lambda : ()
875+ self ._type_watcher .callback = None
876+ self ._type_watcher = None
877+ self ._condition = None
878+ self ._expression = None
879+ else :
880+ self .element = None
881+ # Keep _type_watcher alive for dynamic tag switching
882+
883+ def first (self ) -> Any | None :
884+ if self ._active_fragment :
885+ return self ._active_fragment .first ()
886+ return None
887+
888+ def _set_attr (self , attr , value ):
889+ # Update our attributes (for transfer to next fragment)
890+ self ._attributes [attr ] = value
891+ # Apply to active fragment
892+ if self ._active_fragment :
893+ self ._active_fragment ._set_attr (attr , value )
894+
895+ def _rem_attr (self , attr ):
896+ if attr in self ._attributes :
897+ del self ._attributes [attr ]
898+ if self ._active_fragment :
899+ self ._active_fragment ._rem_attr (attr )
900+
901+ def _remove (self ):
902+ if self ._active_fragment :
903+ self ._active_fragment ._remove ()
0 commit comments