@@ -198,16 +198,40 @@ static void transpileStmt(Stmt *stmt, FILE *html, FILE *js, int indent) {
198198
199199 // Emit props/attributes
200200 DictPairList * props = stmt -> as .ui_component .props ;
201+ Expr * innerTextValue = NULL ;
202+
201203 for (int i = 0 ; i < props -> count ; i ++ ) {
202204 const char * rawKey = (props -> items [i ].key && props -> items [i ].key -> type == EXPR_VARIABLE )
203205 ? props -> items [i ].key -> as .variable .name : NULL ;
204206 const char * mappedKey = rawKey ? mapPropName (rawKey ) : NULL ;
205207 bool isVariableValue = (props -> items [i ].value && props -> items [i ].value -> type == EXPR_VARIABLE );
206208
209+ // Special handling for 'value' prop on non-form elements
210+ if (rawKey && strcmp (rawKey , "value" ) == 0 ) {
211+ if (strcmp (htmlTag , "input" ) != 0 && strcmp (htmlTag , "option" ) != 0 &&
212+ strcmp (htmlTag , "textarea" ) != 0 && strcmp (htmlTag , "select" ) != 0 &&
213+ strcmp (htmlTag , "progress" ) != 0 && strcmp (htmlTag , "meter" ) != 0 ) {
214+
215+ // If literal string, inject as inner text
216+ if (props -> items [i ].value && props -> items [i ].value -> type == EXPR_LITERAL && IS_STRING (props -> items [i ].value -> as .literal .value )) {
217+ innerTextValue = props -> items [i ].value ;
218+ continue ; // Skip emitting this attribute
219+ } else {
220+ // Any other expression becomes a reactive x-text attribute
221+ mappedKey = "x-text" ;
222+ isVariableValue = false; // x-text is natively reactive, do not prefix with ':'
223+ }
224+ }
225+ }
226+
207227 fprintf (html , " " );
208228 if (mappedKey ) {
209- // Reactive prefix for Alpine
210- if (isVariableValue ) {
229+ // Only add ':' reactive prefix for plain HTML attribute bindings.
230+ // Event handlers (@click etc.) and Alpine directives (x-model, x-show etc.)
231+ // must NOT be prefixed with ':'.
232+ bool isEventHandler = (mappedKey [0 ] == '@' );
233+ bool isAlpineDirective = (mappedKey [0 ] == 'x' && mappedKey [1 ] == '-' );
234+ if (isVariableValue && !isEventHandler && !isAlpineDirective ) {
211235 fprintf (html , ":%s" , mappedKey );
212236 } else {
213237 fprintf (html , "%s" , mappedKey );
@@ -227,9 +251,14 @@ static void transpileStmt(Stmt *stmt, FILE *html, FILE *js, int indent) {
227251
228252 fprintf (html , ">" );
229253
254+ if (innerTextValue ) {
255+ transpileExpr (innerTextValue , html );
256+ }
257+
230258 // Children
231259 if (stmt -> as .ui_component .children && stmt -> as .ui_component .children -> count > 0 ) {
232- fprintf (html , "\n" );
260+ if (!innerTextValue ) fprintf (html , "\n" );
261+ else fprintf (html , "\n" );
233262 for (int i = 0 ; i < stmt -> as .ui_component .children -> count ; i ++ ) {
234263 transpileStmt (stmt -> as .ui_component .children -> items [i ], html , js , indent + 1 );
235264 }
@@ -409,10 +438,20 @@ void transpileUIApp(Stmt *appStmt, const char *outputDir) {
409438 fprintf (js , "function app() {\n return {\n" );
410439
411440 StmtList * body = appStmt -> as .ui_app .body ;
441+
442+ // Pass 1: emit Window/Component HTML structure (no JS)
412443 for (int i = 0 ; i < body -> count ; i ++ ) {
413444 transpileStmt (body -> items [i ], html , NULL , 1 );
414445 }
415446
447+ // Pass 2: emit State + Action into app.js
448+ for (int i = 0 ; i < body -> count ; i ++ ) {
449+ Stmt * s = body -> items [i ];
450+ if (s -> type == STMT_UI_STATE || s -> type == STMT_UI_ACTION ) {
451+ transpileStmt (s , NULL , js , 2 );
452+ }
453+ }
454+
416455 fprintf (js , " };\n}\n" );
417456 fprintf (html , " <script src=\"app.js\"></script>\n" );
418457 fprintf (html , "</body>\n</html>\n" );
0 commit comments