@@ -883,20 +883,20 @@ fn create_host_directives_feature_arg<'a>(
883883
884884 // inputs (if any)
885885 if !hd. inputs . is_empty ( ) {
886- let inputs_map = create_string_map ( allocator, & hd. inputs ) ;
886+ let inputs_array = create_host_directive_mappings_array ( allocator, & hd. inputs ) ;
887887 entries. push ( LiteralMapEntry {
888888 key : Atom :: from ( "inputs" ) ,
889- value : inputs_map ,
889+ value : inputs_array ,
890890 quoted : false ,
891891 } ) ;
892892 }
893893
894894 // outputs (if any)
895895 if !hd. outputs . is_empty ( ) {
896- let outputs_map = create_string_map ( allocator, & hd. outputs ) ;
896+ let outputs_array = create_host_directive_mappings_array ( allocator, & hd. outputs ) ;
897897 entries. push ( LiteralMapEntry {
898898 key : Atom :: from ( "outputs" ) ,
899- value : outputs_map ,
899+ value : outputs_array ,
900900 quoted : false ,
901901 } ) ;
902902 }
@@ -913,26 +913,31 @@ fn create_host_directives_feature_arg<'a>(
913913 ) )
914914}
915915
916- /// Creates a string-to-string map expression.
917- fn create_string_map < ' a > (
916+ /// Creates a host directive mappings array.
917+ ///
918+ /// Format: `['publicName', 'internalName', 'publicName2', 'internalName2']`
919+ ///
920+ /// Shared between directive and component compilers, mirroring Angular's
921+ /// `createHostDirectivesMappingArray` in `view/compiler.ts`.
922+ pub ( crate ) fn create_host_directive_mappings_array < ' a > (
918923 allocator : & ' a Allocator ,
919- pairs : & [ ( Atom < ' a > , Atom < ' a > ) ] ,
924+ mappings : & [ ( Atom < ' a > , Atom < ' a > ) ] ,
920925) -> OutputExpression < ' a > {
921- let mut entries = Vec :: new_in ( allocator) ;
926+ let mut entries = Vec :: with_capacity_in ( mappings . len ( ) * 2 , allocator) ;
922927
923- for ( key , value ) in pairs {
924- entries. push ( LiteralMapEntry {
925- key : key . clone ( ) ,
926- value : OutputExpression :: Literal ( Box :: new_in (
927- LiteralExpr { value : LiteralValue :: String ( value . clone ( ) ) , source_span : None } ,
928- allocator ,
929- ) ) ,
930- quoted : false ,
931- } ) ;
928+ for ( public_name , internal_name ) in mappings {
929+ entries. push ( OutputExpression :: Literal ( Box :: new_in (
930+ LiteralExpr { value : LiteralValue :: String ( public_name . clone ( ) ) , source_span : None } ,
931+ allocator ,
932+ ) ) ) ;
933+ entries . push ( OutputExpression :: Literal ( Box :: new_in (
934+ LiteralExpr { value : LiteralValue :: String ( internal_name . clone ( ) ) , source_span : None } ,
935+ allocator ,
936+ ) ) ) ;
932937 }
933938
934- OutputExpression :: LiteralMap ( Box :: new_in (
935- LiteralMapExpr { entries, source_span : None } ,
939+ OutputExpression :: LiteralArray ( Box :: new_in (
940+ LiteralArrayExpr { entries, source_span : None } ,
936941 allocator,
937942 ) )
938943}
@@ -1419,4 +1424,127 @@ mod tests {
14191424 output
14201425 ) ;
14211426 }
1427+
1428+ #[ test]
1429+ fn test_host_directives_input_output_mappings_use_flat_array ( ) {
1430+ // Issue #67: hostDirectives input/output mappings must be flat arrays
1431+ // ["publicName", "internalName"], NOT objects {publicName: "internalName"}
1432+ let allocator = Allocator :: default ( ) ;
1433+ let type_expr = OutputExpression :: ReadVar ( Box :: new_in (
1434+ ReadVarExpr { name : Atom :: from ( "TooltipTrigger" ) , source_span : None } ,
1435+ & allocator,
1436+ ) ) ;
1437+
1438+ let directive_expr = OutputExpression :: ReadVar ( Box :: new_in (
1439+ ReadVarExpr { name : Atom :: from ( "BrnTooltipTrigger" ) , source_span : None } ,
1440+ & allocator,
1441+ ) ) ;
1442+
1443+ let mut host_directive_inputs = Vec :: new_in ( & allocator) ;
1444+ host_directive_inputs. push ( ( Atom :: from ( "uTooltip" ) , Atom :: from ( "brnTooltipTrigger" ) ) ) ;
1445+
1446+ let mut host_directives = Vec :: new_in ( & allocator) ;
1447+ host_directives. push ( R3HostDirectiveMetadata {
1448+ directive : directive_expr,
1449+ is_forward_reference : false ,
1450+ inputs : host_directive_inputs,
1451+ outputs : Vec :: new_in ( & allocator) ,
1452+ } ) ;
1453+
1454+ let metadata = R3DirectiveMetadata {
1455+ name : Atom :: from ( "TooltipTrigger" ) ,
1456+ r#type : type_expr,
1457+ type_argument_count : 0 ,
1458+ deps : None ,
1459+ selector : Some ( Atom :: from ( "[uTooltip]" ) ) ,
1460+ queries : Vec :: new_in ( & allocator) ,
1461+ view_queries : Vec :: new_in ( & allocator) ,
1462+ host : R3HostMetadata :: new ( & allocator) ,
1463+ uses_on_changes : false ,
1464+ inputs : Vec :: new_in ( & allocator) ,
1465+ outputs : Vec :: new_in ( & allocator) ,
1466+ uses_inheritance : false ,
1467+ export_as : Vec :: new_in ( & allocator) ,
1468+ providers : None ,
1469+ is_standalone : true ,
1470+ is_signal : false ,
1471+ host_directives,
1472+ } ;
1473+
1474+ let result = compile_directive ( & allocator, & metadata, 0 ) ;
1475+ let emitter = JsEmitter :: new ( ) ;
1476+ let output = emitter. emit_expression ( & result. expression ) ;
1477+ let normalized = output. replace ( [ ' ' , '\n' , '\t' ] , "" ) ;
1478+
1479+ // Must contain flat array format: inputs:["uTooltip","brnTooltipTrigger"]
1480+ assert ! (
1481+ normalized. contains( r#"inputs:["uTooltip","brnTooltipTrigger"]"# ) ,
1482+ "Host directive inputs should be flat array [\" publicName\" ,\" internalName\" ], not object. Got:\n {}" ,
1483+ output
1484+ ) ;
1485+ // Must NOT contain object format: inputs:{uTooltip:"brnTooltipTrigger"}
1486+ assert ! (
1487+ !normalized. contains( r#"inputs:{uTooltip:"brnTooltipTrigger"}"# ) ,
1488+ "Host directive inputs should NOT be object format. Got:\n {}" ,
1489+ output
1490+ ) ;
1491+ }
1492+
1493+ #[ test]
1494+ fn test_host_directives_output_mappings_use_flat_array ( ) {
1495+ // Issue #67: output mappings must also be flat arrays
1496+ let allocator = Allocator :: default ( ) ;
1497+ let type_expr = OutputExpression :: ReadVar ( Box :: new_in (
1498+ ReadVarExpr { name : Atom :: from ( "MyDirective" ) , source_span : None } ,
1499+ & allocator,
1500+ ) ) ;
1501+
1502+ let directive_expr = OutputExpression :: ReadVar ( Box :: new_in (
1503+ ReadVarExpr { name : Atom :: from ( "ClickTracker" ) , source_span : None } ,
1504+ & allocator,
1505+ ) ) ;
1506+
1507+ let mut host_directive_outputs = Vec :: new_in ( & allocator) ;
1508+ host_directive_outputs. push ( ( Atom :: from ( "clicked" ) , Atom :: from ( "trackClick" ) ) ) ;
1509+
1510+ let mut host_directives = Vec :: new_in ( & allocator) ;
1511+ host_directives. push ( R3HostDirectiveMetadata {
1512+ directive : directive_expr,
1513+ is_forward_reference : false ,
1514+ inputs : Vec :: new_in ( & allocator) ,
1515+ outputs : host_directive_outputs,
1516+ } ) ;
1517+
1518+ let metadata = R3DirectiveMetadata {
1519+ name : Atom :: from ( "MyDirective" ) ,
1520+ r#type : type_expr,
1521+ type_argument_count : 0 ,
1522+ deps : None ,
1523+ selector : Some ( Atom :: from ( "[myDir]" ) ) ,
1524+ queries : Vec :: new_in ( & allocator) ,
1525+ view_queries : Vec :: new_in ( & allocator) ,
1526+ host : R3HostMetadata :: new ( & allocator) ,
1527+ uses_on_changes : false ,
1528+ inputs : Vec :: new_in ( & allocator) ,
1529+ outputs : Vec :: new_in ( & allocator) ,
1530+ uses_inheritance : false ,
1531+ export_as : Vec :: new_in ( & allocator) ,
1532+ providers : None ,
1533+ is_standalone : true ,
1534+ is_signal : false ,
1535+ host_directives,
1536+ } ;
1537+
1538+ let result = compile_directive ( & allocator, & metadata, 0 ) ;
1539+ let emitter = JsEmitter :: new ( ) ;
1540+ let output = emitter. emit_expression ( & result. expression ) ;
1541+ let normalized = output. replace ( [ ' ' , '\n' , '\t' ] , "" ) ;
1542+
1543+ // Must contain flat array format: outputs:["clicked","trackClick"]
1544+ assert ! (
1545+ normalized. contains( r#"outputs:["clicked","trackClick"]"# ) ,
1546+ "Host directive outputs should be flat array. Got:\n {}" ,
1547+ output
1548+ ) ;
1549+ }
14221550}
0 commit comments