@@ -21,24 +21,48 @@ const BASE_TEXT_CLASS = "text-sm font-normal tracking-tight font-mono";
2121 */
2222const isSpecificComponent = (
2323 element : React . ReactElement ,
24- target : React . ComponentType < any > ,
24+ target : React . ElementType ,
2525 displayName : string
2626) : boolean => {
27- const t : any = element . type ;
27+ const LAZY = Symbol . for ( "react.lazy" ) ;
28+
29+ const getTypeName = ( val : unknown ) : string | undefined => {
30+ if ( typeof val === "function" ) {
31+ const fn = val as { displayName ?: string ; name ?: string } ;
32+ return fn . displayName || fn . name ;
33+ }
34+ if ( val && typeof val === "object" && "displayName" in ( val as object ) ) {
35+ const dn = ( val as { displayName ?: unknown } ) . displayName ;
36+ return typeof dn === "string" ? dn : undefined ;
37+ }
38+ return undefined ;
39+ } ;
40+
41+ const isLazy = (
42+ val : unknown
43+ ) : val is { $$typeof : symbol ; _payload ?: { value ?: unknown } } => {
44+ return (
45+ typeof val === "object" &&
46+ val !== null &&
47+ "$$typeof" in ( val as object ) &&
48+ ( val as { $$typeof ?: unknown } ) . $$typeof === LAZY
49+ ) ;
50+ } ;
51+
52+ const t = element . type as unknown ;
2853 if ( ! t ) return false ;
2954
3055 // Direct reference or displayName/name match
3156 if ( t === target ) return true ;
32- const tName =
33- ( typeof t === "function" && ( t . displayName || t . name ) ) || t . displayName ;
57+ const tName = getTypeName ( t ) ;
3458 if ( tName === displayName ) return true ;
3559
3660 // Minimal lazy component support (mirrors previous behavior)
37- if ( t && t . $$typeof === Symbol . for ( "react.lazy" ) ) {
61+ if ( isLazy ( t ) ) {
3862 const payload = t . _payload ;
3963 const value = payload ?. value ;
4064 if ( typeof value === "function" ) {
41- const vName = value . displayName || value . name ;
65+ const vName = getTypeName ( value ) ;
4266 if ( value === target || vName === displayName ) return true ;
4367 }
4468 // Turbopack/SSR array metadata case
@@ -73,7 +97,10 @@ const extractPlainText = (node: React.ReactNode): string => {
7397 return ( node as React . ReactNode [ ] ) . map ( extractPlainText ) . join ( "" ) ;
7498 default :
7599 return React . isValidElement ( node )
76- ? extractPlainText ( ( node as React . ReactElement < any > ) . props ?. children )
100+ ? extractPlainText (
101+ ( node as React . ReactElement < { children ?: React . ReactNode } > ) . props
102+ ?. children
103+ )
77104 : "" ;
78105 }
79106} ;
@@ -116,13 +143,13 @@ const parseTypingChildren = (
116143 if ( ! React . isValidElement ( child ) ) continue ;
117144 if ( ! leadingCharComponent && isLeadingCharComponent ( child ) ) {
118145 leadingCharComponent = child ;
119- const c = child as React . ReactElement < any > ;
146+ const c = child as React . ReactElement < { children ?: React . ReactNode } > ;
120147 leadingCharText = extractPlainText ( c . props ?. children ) ;
121148 continue ;
122149 }
123150 if ( ! animatedContentComponent && isAnimatedContentComponent ( child ) ) {
124151 animatedContentComponent = child ;
125- const c = child as React . ReactElement < any > ;
152+ const c = child as React . ReactElement < { children ?: React . ReactNode } > ;
126153 const raw = extractPlainText ( c . props ?. children ) ;
127154 contentText = raw && ! raw . startsWith ( " " ) ? " " + raw : raw ;
128155 }
@@ -255,6 +282,7 @@ export const TypingAnimation = ({
255282 const animatedContentComponent = parsed . animatedContentComponent ;
256283 const leadingCharText = parsed . leadingCharText ;
257284 const fullTextToAnimate = parsed . fullText ;
285+ const hasLeadingChar = Boolean ( leadingCharComponent ) ;
258286
259287 useEffect ( ( ) => {
260288 const startTimeout = setTimeout ( ( ) => {
@@ -271,7 +299,7 @@ export const TypingAnimation = ({
271299 if ( i < fullTextToAnimate . length ) {
272300 const currentText = fullTextToAnimate . substring ( 0 , i + 1 ) ;
273301
274- if ( leadingCharComponent && i < leadingCharText . length ) {
302+ if ( hasLeadingChar && i < leadingCharText . length ) {
275303 setDisplayedLeadingChar ( currentText ) ;
276304 setDisplayedText ( "" ) ;
277305 } else {
@@ -288,7 +316,7 @@ export const TypingAnimation = ({
288316 return ( ) => {
289317 clearInterval ( typingEffect ) ;
290318 } ;
291- } , [ fullTextToAnimate , leadingCharText , duration , started ] ) ;
319+ } , [ fullTextToAnimate , leadingCharText , duration , started , hasLeadingChar ] ) ;
292320
293321 // Nothing to animate
294322 if ( ! fullTextToAnimate ) {
@@ -302,14 +330,16 @@ export const TypingAnimation = ({
302330 { ...props }
303331 >
304332 { leadingCharComponent
305- ? React . cloneElement ( leadingCharComponent as any , {
306- children : displayedLeadingChar ,
307- } )
333+ ? React . cloneElement < LeadingCharProps > (
334+ leadingCharComponent as React . ReactElement < LeadingCharProps > ,
335+ { children : displayedLeadingChar }
336+ )
308337 : null }
309338 { animatedContentComponent
310- ? React . cloneElement ( animatedContentComponent as any , {
311- children : displayedText ,
312- } )
339+ ? React . cloneElement < AnimatedContentProps > (
340+ animatedContentComponent as React . ReactElement < AnimatedContentProps > ,
341+ { children : displayedText }
342+ )
313343 : typeof children === "string"
314344 ? fullTextToAnimate . substring (
315345 0 ,
0 commit comments