@@ -40,7 +40,9 @@ public function __construct(
4040 array $ attributes ,
4141 ) {
4242 $ this ->attributes = $ attributes ;
43- $ this ->viewComponentAttributes = arr ($ attributes );
43+
44+ $ this ->viewComponentAttributes = arr ($ attributes )
45+ ->mapWithKeys (fn (string $ value , string $ key ) => yield str ($ key )->ltrim (': ' )->toString () => $ value );
4446
4547 $ this ->dataAttributes = arr ($ attributes )
4648 ->filter (fn (string $ _ , string $ key ) => ! str_starts_with ($ key , ': ' ))
@@ -96,50 +98,10 @@ public function compile(): string
9698
9799 $ compiled = str ($ this ->viewComponent ->contents );
98100
99- // Fallthrough attributes
100- $ compiled = $ compiled
101- ->replaceRegex (
102- regex: '/^<(?<tag>[\w-]+)(.*?["\s])?>/ ' , // Match the very first opening tag, this will never fail.
103- replace: function ($ matches ) {
104- /** @var \Tempest\View\Parser\Token $token */
105- $ token = TempestViewParser::ast ($ matches [0 ])[0 ];
106-
107- $ attributes = arr ($ token ->htmlAttributes )->map (fn (string $ value ) => new MutableString ($ value ));
108-
109- foreach (['class ' , 'style ' , 'id ' ] as $ attributeName ) {
110- if (! isset ($ this ->dataAttributes [$ attributeName ])) {
111- continue ;
112- }
113-
114- $ attributes [$ attributeName ] ??= new MutableString ();
115-
116- if ($ attributeName === 'id ' ) {
117- $ attributes [$ attributeName ] = new MutableString (' ' . $ this ->dataAttributes [$ attributeName ]);
118- } else {
119- $ attributes [$ attributeName ]->append (' ' . $ this ->dataAttributes [$ attributeName ]);
120- }
121- }
101+ $ compiled = $ this ->applyFallthroughAttributes ($ compiled );
122102
123- return sprintf (
124- '<%s%s> ' ,
125- $ matches ['tag ' ],
126- $ attributes
127- ->map (function (MutableString $ value , string $ key ) {
128- return sprintf ('%s="%s" ' , $ key , $ value ->trim ());
129- })
130- ->implode (' ' )
131- ->when (
132- fn (ImmutableString $ string ) => $ string ->isNotEmpty (),
133- fn (ImmutableString $ string ) => $ string ->prepend (' ' ),
134- ),
135- );
136- },
137- );
138-
139- // Add scoped variables
140103 $ compiled = $ compiled
141104 ->prepend (
142- // Open the current scope
143105 sprintf (
144106 '<?php (function ($attributes, $slots, $scopedVariables %s %s) { extract($scopedVariables, EXTR_SKIP); ?> ' ,
145107 $ this ->dataAttributes ->isNotEmpty () ? ', ' . $ this ->dataAttributes ->map (fn (string $ _value , string $ key ) => "\${$ key }" )->implode (', ' ) : '' ,
@@ -148,10 +110,9 @@ public function compile(): string
148110 ),
149111 )
150112 ->append (
151- // Close and call the current scope
152113 sprintf (
153114 '<?php })(attributes: %s, slots: %s, scopedVariables: [%s] + ($scopedVariables ?? $this->currentView?->data ?? []) %s %s) ?> ' ,
154- ViewObjectExporter:: export ( $ this ->viewComponentAttributes ),
115+ $ this ->exportAttributesArray ( ),
155116 ViewObjectExporter::export ($ slots ),
156117 $ this ->scopedVariables ->isNotEmpty ()
157118 ? $ this ->scopedVariables ->map (fn (string $ name ) => "' {$ name }' => \${$ name }" )->implode (', ' )
@@ -165,7 +126,6 @@ public function compile(): string
165126 ),
166127 );
167128
168- // Compile slots
169129 $ compiled = $ compiled ->replaceRegex (
170130 regex: '/<x-slot\s*(name="(?<name>[\w-]+)")?((\s*\/>)|>(?<default>(.|\n)*?)<\/x-slot>)/ ' ,
171131 replace: function ($ matches ) use ($ slots ) {
@@ -222,4 +182,77 @@ private function getSlotElement(string $name): SlotElement|CollectionElement|nul
222182
223183 return null ;
224184 }
185+
186+ private function applyFallthroughAttributes (ImmutableString $ compiled ): ImmutableString
187+ {
188+ return $ compiled ->replaceRegex (
189+ regex: '/^<(?<tag>[\w-]+)(.*?["\s])?>/ ' ,
190+ replace: function (array $ matches ): string {
191+ /** @var Token $token */
192+ $ token = TempestViewParser::ast ($ matches [0 ])[0 ];
193+
194+ $ attributes = arr ($ token ->htmlAttributes )
195+ ->map (fn (string $ value ) => new MutableString ($ value ));
196+
197+ foreach (['class ' , 'style ' , 'id ' ] as $ name ) {
198+ $ attributes = $ this ->applyFallthroughAttribute ($ attributes , $ name );
199+ }
200+
201+ $ attributeString = $ attributes
202+ ->map (fn (MutableString $ value , string $ key ) => sprintf ('%s="%s" ' , $ key , $ value ->trim ()))
203+ ->implode (' ' )
204+ ->when (
205+ fn (ImmutableString $ s ) => $ s ->isNotEmpty (),
206+ fn (ImmutableString $ s ) => $ s ->prepend (' ' ),
207+ );
208+
209+ return sprintf ('<%s%s> ' , $ matches ['tag ' ], $ attributeString );
210+ },
211+ );
212+ }
213+
214+ private function applyFallthroughAttribute (ImmutableArray $ attributes , string $ name ): ImmutableArray
215+ {
216+ $ hasDataAttribute = isset ($ this ->dataAttributes [$ name ]);
217+ $ hasExpressionAttribute = isset ($ this ->expressionAttributes [$ name ]);
218+
219+ if (! $ hasDataAttribute && ! $ hasExpressionAttribute ) {
220+ return $ attributes ;
221+ }
222+
223+ $ attributes [$ name ] ??= new MutableString ();
224+
225+ if ($ name === 'id ' ) {
226+ if ($ hasDataAttribute ) {
227+ $ attributes [$ name ] = new MutableString ($ this ->dataAttributes [$ name ]);
228+ } elseif ($ hasExpressionAttribute ) {
229+ $ attributes [$ name ] = new MutableString (sprintf ('<?= $%s ?> ' , $ name ));
230+ }
231+ } else {
232+ if ($ hasDataAttribute ) {
233+ $ attributes [$ name ]->append (' ' . $ this ->dataAttributes [$ name ]);
234+ }
235+ if ($ hasExpressionAttribute ) {
236+ $ attributes [$ name ]->append (sprintf (' <?= $%s ?> ' , $ name ));
237+ }
238+ }
239+
240+ return $ attributes ;
241+ }
242+
243+ private function exportAttributesArray (): string
244+ {
245+ $ entries = [];
246+
247+ foreach ($ this ->viewComponentAttributes as $ key => $ value ) {
248+ $ camelKey = str ($ key )->camel ()->toString ();
249+ $ isExpression = isset ($ this ->expressionAttributes [$ camelKey ]);
250+
251+ $ entries [] = $ isExpression
252+ ? sprintf ("'%s' => %s " , $ key , $ value )
253+ : sprintf ("'%s' => %s " , $ key , ViewObjectExporter::exportValue ($ value ));
254+ }
255+
256+ return sprintf ('new \%s([%s]) ' , ImmutableArray::class, implode (', ' , $ entries ));
257+ }
225258}
0 commit comments