@@ -38,6 +38,84 @@ export function emitIntrinsicWrappers(ctx: any): { emitted: any[]; needsReactTyp
3838 const emitted : any [ ] = [ ] ;
3939 let needsReactTypeImport = false ;
4040
41+ const mergeAsIntoPropsWithChildren = ( typeText : string ) : string | null => {
42+ const prefix = "React.PropsWithChildren<" ;
43+ if ( ! typeText . trim ( ) . startsWith ( prefix ) || ! typeText . trim ( ) . endsWith ( ">" ) ) {
44+ return null ;
45+ }
46+ const inner = typeText . trim ( ) . slice ( prefix . length , - 1 ) . trim ( ) ;
47+ if ( inner === "{}" ) {
48+ return `${ prefix } { as?: React.ElementType }>` ;
49+ }
50+ if ( inner . startsWith ( "{" ) && inner . endsWith ( "}" ) ) {
51+ let body = inner . slice ( 1 , - 1 ) . trim ( ) ;
52+ if ( body . endsWith ( ";" ) ) {
53+ body = body . slice ( 0 , - 1 ) . trim ( ) ;
54+ }
55+ const withAs = body . length > 0 ? `${ body } ; as?: React.ElementType` : "as?: React.ElementType" ;
56+ return `${ prefix } { ${ withAs } }>` ;
57+ }
58+ return null ;
59+ } ;
60+
61+ const addAsPropToExistingType = ( typeName : string ) : boolean => {
62+ if ( ! emitTypes ) {
63+ return false ;
64+ }
65+ let didUpdate = false ;
66+ const interfaces = root . find ( j . TSInterfaceDeclaration , {
67+ id : { type : "Identifier" , name : typeName } ,
68+ } as any ) ;
69+ interfaces . forEach ( ( path : any ) => {
70+ const iface = path . node ;
71+ const members = iface . body ?. body ?? [ ] ;
72+ const hasAs = members . some (
73+ ( m : any ) =>
74+ m . type === "TSPropertySignature" && m . key ?. type === "Identifier" && m . key . name === "as" ,
75+ ) ;
76+ if ( hasAs ) {
77+ didUpdate = true ;
78+ return ;
79+ }
80+ const parsed = j ( `interface X { as?: React.ElementType }` ) . get ( ) . node . program . body [ 0 ] as any ;
81+ const prop = parsed . body ?. body ?. [ 0 ] ;
82+ if ( prop ) {
83+ members . push ( prop ) ;
84+ didUpdate = true ;
85+ }
86+ } ) ;
87+ if ( didUpdate ) {
88+ return true ;
89+ }
90+ const typeAliases = root . find ( j . TSTypeAliasDeclaration , {
91+ id : { type : "Identifier" , name : typeName } ,
92+ } as any ) ;
93+ typeAliases . forEach ( ( path : any ) => {
94+ const alias = path . node ;
95+ const existing = alias . typeAnnotation ;
96+ if ( ! existing ) {
97+ return ;
98+ }
99+ const existingStr = j ( existing ) . toSource ( ) ;
100+ if ( existingStr . includes ( "as?:" ) || existingStr . includes ( "as :" ) ) {
101+ didUpdate = true ;
102+ return ;
103+ }
104+ const parsed = j ( `type X = { as?: React.ElementType };` ) . get ( ) . node . program . body [ 0 ] as any ;
105+ const asType = parsed . typeAnnotation ;
106+ if ( ! asType ) {
107+ return ;
108+ }
109+ if ( existing . type === "TSIntersectionType" ) {
110+ existing . types = [ ...( existing . types ?? [ ] ) , asType ] ;
111+ } else {
112+ alias . typeAnnotation = j . tsIntersectionType ( [ existing , asType ] ) ;
113+ }
114+ didUpdate = true ;
115+ } ) ;
116+ return didUpdate ;
117+ } ;
118+
41119 const inputWrapperDecls = wrapperDecls . filter (
42120 ( d : any ) =>
43121 d . base . kind === "intrinsic" && d . base . tagName === "input" && d . attrWrapper ?. kind === "input" ,
@@ -291,6 +369,7 @@ export function emitIntrinsicWrappers(ctx: any): { emitted: any[]; needsReactTyp
291369 const tagName = d . base . tagName ;
292370 const allowClassNameProp = shouldAllowClassNameProp ( d ) ;
293371 const allowStyleProp = shouldAllowStyleProp ( d ) ;
372+ const allowAsProp = ! VOID_TAGS . has ( tagName ) ;
294373 const explicit = stringifyTsType ( d . propsType ) ;
295374
296375 // Check if the explicit props type is a simple (non-generic) type reference.
@@ -321,8 +400,10 @@ export function emitIntrinsicWrappers(ctx: any): { emitted: any[]; needsReactTyp
321400 }
322401 const baseMaybeOmitted =
323402 omitted . length > 0 ? `Omit<${ base } , ${ omitted . join ( " | " ) } >` : base ;
324- const extra = "{ as?: C }" ;
325- return joinIntersection ( baseMaybeOmitted , extra ) ;
403+ if ( ! allowAsProp ) {
404+ return baseMaybeOmitted ;
405+ }
406+ return joinIntersection ( baseMaybeOmitted , "{ as?: C }" ) ;
326407 } ) ( ) ;
327408
328409 if ( ! isExplicitNonGenericType ) {
@@ -359,11 +440,6 @@ export function emitIntrinsicWrappers(ctx: any): { emitted: any[]; needsReactTyp
359440 }
360441 }
361442
362- const stylexPropsCall = j . callExpression (
363- j . memberExpression ( j . identifier ( "stylex" ) , j . identifier ( "props" ) ) ,
364- styleArgs ,
365- ) ;
366-
367443 const isVoidTag = VOID_TAGS . has ( tagName ) ;
368444 const propsParamId = j . identifier ( "props" ) ;
369445 if ( emitTypes ) {
@@ -386,17 +462,23 @@ export function emitIntrinsicWrappers(ctx: any): { emitted: any[]; needsReactTyp
386462 const propsId = j . identifier ( "props" ) ;
387463 const childrenId = j . identifier ( "children" ) ;
388464 const restId = j . identifier ( "rest" ) ;
465+ const classNameId = j . identifier ( "className" ) ;
389466 const styleId = j . identifier ( "style" ) ;
390467
391468 const declStmt = j . variableDeclaration ( "const" , [
392469 j . variableDeclarator (
393470 j . objectPattern ( [
394- j . property . from ( {
395- kind : "init" ,
396- key : j . identifier ( "as" ) ,
397- value : j . assignmentPattern ( j . identifier ( "Component" ) , j . literal ( tagName ) ) ,
398- shorthand : false ,
399- } ) ,
471+ ...( allowAsProp
472+ ? [
473+ j . property . from ( {
474+ kind : "init" ,
475+ key : j . identifier ( "as" ) ,
476+ value : j . assignmentPattern ( j . identifier ( "Component" ) , j . literal ( tagName ) ) ,
477+ shorthand : false ,
478+ } ) ,
479+ ]
480+ : [ ] ) ,
481+ ...( allowClassNameProp ? [ patternProp ( "className" , classNameId ) ] : [ ] ) ,
400482 ...( isVoidTag ? [ ] : [ patternProp ( "children" , childrenId ) ] ) ,
401483 ...( allowStyleProp ? [ patternProp ( "style" , styleId ) ] : [ ] ) ,
402484 // Add variant props to destructuring
@@ -407,29 +489,62 @@ export function emitIntrinsicWrappers(ctx: any): { emitted: any[]; needsReactTyp
407489 ) ,
408490 ] ) ;
409491
492+ const merging = emitStyleMerging ( {
493+ j,
494+ styleMerger,
495+ styleArgs,
496+ classNameId,
497+ styleId,
498+ allowClassNameProp,
499+ allowStyleProp,
500+ inlineStyleProps : [ ] ,
501+ } ) ;
502+
410503 const attrs : any [ ] = [
411504 j . jsxSpreadAttribute ( restId ) ,
412- j . jsxSpreadAttribute ( stylexPropsCall ) ,
413- ...( allowStyleProp
414- ? [ j . jsxAttribute ( j . jsxIdentifier ( "style" ) , j . jsxExpressionContainer ( styleId ) ) ]
415- : [ ] ) ,
505+ j . jsxSpreadAttribute ( merging . jsxSpreadExpr ) ,
416506 ] ;
417- const openingEl = j . jsxOpeningElement ( j . jsxIdentifier ( "Component" ) , attrs , isVoidTag ) ;
507+ if ( merging . classNameAttr ) {
508+ attrs . push (
509+ j . jsxAttribute (
510+ j . jsxIdentifier ( "className" ) ,
511+ j . jsxExpressionContainer ( merging . classNameAttr ) ,
512+ ) ,
513+ ) ;
514+ }
515+ if ( merging . styleAttr ) {
516+ attrs . push (
517+ j . jsxAttribute ( j . jsxIdentifier ( "style" ) , j . jsxExpressionContainer ( merging . styleAttr ) ) ,
518+ ) ;
519+ }
520+ const openingEl = j . jsxOpeningElement (
521+ j . jsxIdentifier ( allowAsProp ? "Component" : tagName ) ,
522+ attrs ,
523+ isVoidTag ,
524+ ) ;
418525 const jsx = isVoidTag
419526 ? ( {
420527 type : "JSXElement" ,
421528 openingElement : openingEl ,
422529 closingElement : null ,
423530 children : [ ] ,
424531 } as any )
425- : j . jsxElement ( openingEl , j . jsxClosingElement ( j . jsxIdentifier ( "Component" ) ) , [
426- j . jsxExpressionContainer ( childrenId ) ,
427- ] ) ;
532+ : j . jsxElement (
533+ openingEl ,
534+ j . jsxClosingElement ( j . jsxIdentifier ( allowAsProp ? "Component" : tagName ) ) ,
535+ [ j . jsxExpressionContainer ( childrenId ) ] ,
536+ ) ;
537+
538+ const fnBodyStmts : any [ ] = [ declStmt ] ;
539+ if ( merging . sxDecl ) {
540+ fnBodyStmts . push ( merging . sxDecl ) ;
541+ }
542+ fnBodyStmts . push ( j . returnStatement ( jsx as any ) ) ;
428543
429544 const fn = j . functionDeclaration (
430545 j . identifier ( d . localName ) ,
431546 [ propsParamId ] ,
432- j . blockStatement ( [ declStmt , j . returnStatement ( jsx as any ) ] ) ,
547+ j . blockStatement ( fnBodyStmts ) ,
433548 ) ;
434549 // Move the generic parameters from the param to the function node (parser puts it on FunctionDeclaration).
435550 if ( ( propsParamId as any ) . typeParameters ) {
@@ -1311,6 +1426,12 @@ export function emitIntrinsicWrappers(ctx: any): { emitted: any[]; needsReactTyp
13111426 const tagName = d . base . tagName ;
13121427 const allowClassNameProp = shouldAllowClassNameProp ( d ) ;
13131428 const allowStyleProp = shouldAllowStyleProp ( d ) ;
1429+ const usedAttrsForType = getUsedAttrs ( d . localName ) ;
1430+ const allowAsProp =
1431+ ! VOID_TAGS . has ( tagName ) &&
1432+ ( ( d . supportsExternalStyles ?? false ) ||
1433+ usedAttrsForType . has ( "as" ) ||
1434+ usedAttrsForType . has ( "forwardedAs" ) ) ;
13141435 let inlineTypeText : string | undefined ;
13151436 {
13161437 const explicit = stringifyTsType ( d . propsType ) ;
@@ -1323,7 +1444,6 @@ export function emitIntrinsicWrappers(ctx: any): { emitted: any[]; needsReactTyp
13231444 skipProps : explicitPropNames ,
13241445 } ) ;
13251446
1326- const usedAttrsForType = getUsedAttrs ( d . localName ) ;
13271447 const variantPropsForType = new Set (
13281448 Object . keys ( d . variantStyleKeys ?? { } ) . flatMap ( ( when : string ) => {
13291449 return when . split ( "&&" ) . flatMap ( ( part : string ) => {
@@ -1446,17 +1566,30 @@ export function emitIntrinsicWrappers(ctx: any): { emitted: any[]; needsReactTyp
14461566 }
14471567 return withChildren ( explicit ) ;
14481568 } ) ( ) ;
1449- const typeAliasEmitted = emitNamedPropsType ( d . localName , typeText ) ;
1569+ const asPropTypeText = allowAsProp ? "{ as?: React.ElementType }" : null ;
1570+ const mergedPropsWithChildren = allowAsProp ? mergeAsIntoPropsWithChildren ( typeText ) : null ;
1571+ const typeWithAs = mergedPropsWithChildren
1572+ ? mergedPropsWithChildren
1573+ : asPropTypeText
1574+ ? joinIntersection ( typeText , asPropTypeText )
1575+ : typeText ;
1576+ const typeAliasEmitted = emitNamedPropsType ( d . localName , typeWithAs ) ;
14501577 if ( ! typeAliasEmitted && explicit ) {
14511578 const propsTypeName = propsTypeNameFor ( d . localName ) ;
14521579 const interfaceExtended = extendExistingInterface ( propsTypeName , extendBaseTypeText ) ;
14531580 if ( ! interfaceExtended ) {
14541581 const typeAliasExtended = extendExistingTypeAlias ( propsTypeName , extendBaseTypeText ) ;
14551582 if ( ! typeAliasExtended ) {
14561583 inlineTypeText = VOID_TAGS . has ( tagName ) ? explicit : withChildren ( explicit ) ;
1584+ if ( asPropTypeText ) {
1585+ inlineTypeText = joinIntersection ( inlineTypeText , asPropTypeText ) ;
1586+ }
14571587 }
14581588 }
14591589 }
1590+ if ( ! typeAliasEmitted && asPropTypeText ) {
1591+ addAsPropToExistingType ( propsTypeNameFor ( d . localName ) ) ;
1592+ }
14601593 needsReactTypeImport = true ;
14611594 }
14621595 const styleArgs : any [ ] = [
@@ -1561,17 +1694,28 @@ export function emitIntrinsicWrappers(ctx: any): { emitted: any[]; needsReactTyp
15611694 return ! destructureProps . includes ( n ) ;
15621695 } ) ) ;
15631696
1564- if ( allowClassNameProp || allowStyleProp ) {
1697+ if ( allowAsProp || allowClassNameProp || allowStyleProp ) {
15651698 const isVoidTag = VOID_TAGS . has ( tagName ) ;
15661699 const propsParamId = j . identifier ( "props" ) ;
15671700 annotatePropsParam ( propsParamId , d . localName , inlineTypeText ) ;
15681701 const propsId = j . identifier ( "props" ) ;
1702+ const componentId = j . identifier ( "Component" ) ;
15691703 const classNameId = j . identifier ( "className" ) ;
15701704 const childrenId = j . identifier ( "children" ) ;
15711705 const styleId = j . identifier ( "style" ) ;
15721706 const restId = shouldIncludeRest ? j . identifier ( "rest" ) : null ;
15731707
15741708 const patternProps : any [ ] = [
1709+ ...( allowAsProp
1710+ ? [
1711+ j . property . from ( {
1712+ kind : "init" ,
1713+ key : j . identifier ( "as" ) ,
1714+ value : j . assignmentPattern ( componentId , j . literal ( tagName ) ) ,
1715+ shorthand : false ,
1716+ } ) ,
1717+ ]
1718+ : [ ] ) ,
15751719 ...( allowClassNameProp ? [ patternProp ( "className" , classNameId ) ] : [ ] ) ,
15761720 ...( isVoidTag ? [ ] : [ patternProp ( "children" , childrenId ) ] ) ,
15771721 ...( allowStyleProp ? [ patternProp ( "style" , styleId ) ] : [ ] ) ,
@@ -1614,7 +1758,11 @@ export function emitIntrinsicWrappers(ctx: any): { emitted: any[]; needsReactTyp
16141758 ) ;
16151759 }
16161760
1617- const openingEl = j . jsxOpeningElement ( j . jsxIdentifier ( tagName ) , openingAttrs , false ) ;
1761+ const openingEl = j . jsxOpeningElement (
1762+ j . jsxIdentifier ( allowAsProp ? "Component" : tagName ) ,
1763+ openingAttrs ,
1764+ false ,
1765+ ) ;
16181766
16191767 const jsx = isVoidTag
16201768 ? ( {
@@ -1623,9 +1771,11 @@ export function emitIntrinsicWrappers(ctx: any): { emitted: any[]; needsReactTyp
16231771 closingElement : null ,
16241772 children : [ ] ,
16251773 } as any )
1626- : j . jsxElement ( openingEl , j . jsxClosingElement ( j . jsxIdentifier ( tagName ) ) , [
1627- j . jsxExpressionContainer ( childrenId ) ,
1628- ] ) ;
1774+ : j . jsxElement (
1775+ openingEl ,
1776+ j . jsxClosingElement ( j . jsxIdentifier ( allowAsProp ? "Component" : tagName ) ) ,
1777+ [ j . jsxExpressionContainer ( childrenId ) ] ,
1778+ ) ;
16291779
16301780 const bodyStmts : any [ ] = [ declStmt ] ;
16311781 if ( merging . sxDecl ) {
0 commit comments