22
33namespace Html \TemplateGenerator ;
44
5+ use BackedEnum ;
56use Html \Interface \HTMLElementDelegatorInterface ;
67use Html \Interface \TemplateGeneratorInterface ;
78use Html \Mapping \TemplateGenerator ;
89use ReflectionClass ;
10+ use ReflectionMethod ;
911use ReflectionNamedType ;
12+ use ReflectionProperty ;
1013use ReflectionUnionType ;
14+ use Throwable ;
15+ use UnitEnum ;
1116
1217/**
1318 * TypeScriptGenerator - Generates Pure TypeScript Classes
@@ -116,7 +121,7 @@ public function renderComposedElement(HTMLElementDelegatorInterface $element): ?
116121 $ lines = explode ("\n" , $ docComment );
117122 foreach ($ lines as $ line ) {
118123 $ line = trim ($ line , " \t/* " );
119- if (str_starts_with ($ line , '* ' ) && !str_starts_with ($ line , '*/ ' ) && !str_starts_with ($ line , '* @ ' )) {
124+ if (str_starts_with ($ line , '* ' ) && ! str_starts_with ($ line , '*/ ' ) && ! str_starts_with ($ line , '* @ ' )) {
120125 $ desc = trim (substr ($ line , 1 ));
121126 break ;
122127 }
@@ -143,7 +148,7 @@ public function renderElement(HTMLElementDelegatorInterface $element): string
143148 $ lines = explode ("\n" , $ docComment );
144149 foreach ($ lines as $ line ) {
145150 $ line = trim ($ line , " \t/* " );
146- if (str_starts_with ($ line , '* ' ) && !str_starts_with ($ line , '*/ ' ) && !str_starts_with ($ line , '* @ ' )) {
151+ if (str_starts_with ($ line , '* ' ) && ! str_starts_with ($ line , '*/ ' ) && ! str_starts_with ($ line , '* @ ' )) {
147152 $ desc = trim (substr ($ line , 1 ));
148153 break ;
149154 }
@@ -157,7 +162,7 @@ public function renderElement(HTMLElementDelegatorInterface $element): string
157162 $ enumImports = [];
158163
159164 // Add children for non-self-closing elements
160- if (!$ isSelfClosing ) {
165+ if (! $ isSelfClosing ) {
161166 $ props ['children ' ] = [
162167 'type ' => 'string | HTMLElement | (string | HTMLElement)[] ' ,
163168 'description ' => 'Child content or elements ' ,
@@ -170,7 +175,7 @@ public function renderElement(HTMLElementDelegatorInterface $element): string
170175 $ example = null ;
171176 try {
172177 $ example = $ ref ->newInstance ();
173- } catch (\ Throwable $ e ) {
178+ } catch (Throwable $ e ) {
174179 // Ignore if we can't create an instance
175180 }
176181
@@ -215,7 +220,9 @@ public function renderElement(HTMLElementDelegatorInterface $element): string
215220 foreach ($ propType ->getTypes () as $ unionType ) {
216221 if (str_ends_with ($ unionType ->getName (), 'Enum ' )) {
217222 $ isEnum = true ;
218- $ enumClass = basename (str_replace ('\\' , '/ ' , $ unionType ->getName ())); // Extract just the enum name
223+ $ enumClass = basename (
224+ str_replace ('\\' , '/ ' , $ unionType ->getName ())
225+ ); // Extract just the enum name
219226 break ;
220227 }
221228 }
@@ -242,8 +249,12 @@ public function renderElement(HTMLElementDelegatorInterface $element): string
242249 $ reserved = ['children ' ];
243250 $ sortedKeys = array_keys ($ props );
244251 usort ($ sortedKeys , function ($ a , $ b ) use ($ reserved ) {
245- if (in_array ($ a , $ reserved )) return -1 ;
246- if (in_array ($ b , $ reserved )) return 1 ;
252+ if (in_array ($ a , $ reserved )) {
253+ return -1 ;
254+ }
255+ if (in_array ($ b , $ reserved )) {
256+ return 1 ;
257+ }
247258 return strcasecmp ($ a , $ b );
248259 });
249260
@@ -252,14 +263,7 @@ public function renderElement(HTMLElementDelegatorInterface $element): string
252263 $ sortedProps [$ key ] = $ props [$ key ];
253264 }
254265
255- $ ts = $ this ->buildTypeScriptClass (
256- $ elementName ,
257- $ name ,
258- $ desc ,
259- $ sortedProps ,
260- $ enumImports ,
261- $ isSelfClosing
262- );
266+ $ ts = $ this ->buildTypeScriptClass ($ elementName , $ name , $ desc , $ sortedProps , $ enumImports , $ isSelfClosing );
263267
264268 return $ ts ;
265269 }
@@ -269,13 +273,13 @@ private function camelToKebab(string $string): string
269273 return strtolower (preg_replace ('/([a-z])([A-Z])/ ' , '$1-$2 ' , $ string ));
270274 }
271275
272- private function getPropertyType (\ ReflectionProperty $ prop , \ ReflectionMethod $ getter ): string
276+ private function getPropertyType (ReflectionProperty $ prop , ReflectionMethod $ getter ): string
273277 {
274278 $ type = $ prop ->getType ();
275279 if ($ type instanceof ReflectionNamedType) {
276280 $ typeName = $ type ->getName ();
277281 $ allowsNull = $ type ->allowsNull ();
278-
282+
279283 if ($ typeName === 'string ' ) {
280284 return $ allowsNull ? 'string | null | undefined ' : 'string | undefined ' ;
281285 } elseif ($ typeName === 'int ' ) {
@@ -290,11 +294,14 @@ private function getPropertyType(\ReflectionProperty $prop, \ReflectionMethod $g
290294 if (enum_exists ($ fullClassName )) {
291295 try {
292296 $ cases = $ fullClassName ::cases ();
293- $ values = array_map (fn (\UnitEnum $ case ) => $ case instanceof \BackedEnum ? $ case ->value : $ case ->name , $ cases );
297+ $ values = array_map (
298+ fn (UnitEnum $ case ) => $ case instanceof BackedEnum ? $ case ->value : $ case ->name ,
299+ $ cases
300+ );
294301 if (in_array ('true ' , $ values ) && in_array ('false ' , $ values )) {
295302 $ enumType .= ' | boolean ' ;
296303 }
297- } catch (\ Throwable $ e ) {
304+ } catch (Throwable $ e ) {
298305 // Ignore
299306 }
300307 }
@@ -317,11 +324,14 @@ private function getPropertyType(\ReflectionProperty $prop, \ReflectionMethod $g
317324 if (enum_exists ($ fullClassName )) {
318325 try {
319326 $ cases = $ fullClassName ::cases ();
320- $ values = array_map (fn (\UnitEnum $ case ) => $ case instanceof \BackedEnum ? $ case ->value : $ case ->name , $ cases );
327+ $ values = array_map (
328+ fn (UnitEnum $ case ) => $ case instanceof BackedEnum ? $ case ->value : $ case ->name ,
329+ $ cases
330+ );
321331 if (in_array ('true ' , $ values ) && in_array ('false ' , $ values )) {
322332 $ enumType .= ' | boolean ' ;
323333 }
324- } catch (\ Throwable $ e ) {
334+ } catch (Throwable $ e ) {
325335 // Ignore
326336 }
327337 }
@@ -342,7 +352,9 @@ private function getTypeName(string $phpType): string
342352 'string ' => 'string ' ,
343353 'int ' => 'number ' ,
344354 'bool ' => 'boolean ' ,
345- default => str_ends_with ($ phpType , 'Enum ' ) ? $ this ->getEnumType (basename (str_replace ('\\' , '/ ' , $ phpType ))) : 'any ' ,
355+ default => str_ends_with ($ phpType , 'Enum ' ) ? $ this ->getEnumType (
356+ basename (str_replace ('\\' , '/ ' , $ phpType ))
357+ ) : 'any ' ,
346358 };
347359 }
348360
@@ -353,16 +365,19 @@ private function getEnumType(string $enumClass): string
353365 if (enum_exists ($ fullClassName )) {
354366 try {
355367 $ cases = $ fullClassName ::cases ();
356- $ values = array_map (fn (\UnitEnum $ case ) => $ case instanceof \BackedEnum ? "' {$ case ->value }' " : "' {$ case ->name }' " , $ cases );
368+ $ values = array_map (
369+ fn (UnitEnum $ case ) => $ case instanceof BackedEnum ? "' {$ case ->value }' " : "' {$ case ->name }' " ,
370+ $ cases
371+ );
357372 return implode (' | ' , $ values );
358- } catch (\ Throwable $ e ) {
373+ } catch (Throwable $ e ) {
359374 // Fallback
360375 }
361376 }
362377 return 'string ' ; // Fallback to string if enum not found
363378 }
364379
365- private function getPropertyDescription (\ ReflectionProperty $ prop ): string
380+ private function getPropertyDescription (ReflectionProperty $ prop ): string
366381 {
367382 $ docComment = $ prop ->getDocComment ();
368383 if ($ docComment !== false ) {
@@ -375,7 +390,7 @@ private function getPropertyDescription(\ReflectionProperty $prop): string
375390 $ line = trim ($ line );
376391 if (str_starts_with ($ line , '* ' )) {
377392 $ line = trim (substr ($ line , 1 ));
378- if (!str_starts_with ($ line , '@ ' ) && !empty ($ line )) {
393+ if (! str_starts_with ($ line , '@ ' ) && ! empty ($ line )) {
379394 $ description .= $ line . ' ' ;
380395 }
381396 }
@@ -392,25 +407,82 @@ private function getGlobalAttributeTraits(object $element): array
392407
393408 // Check which global attribute traits are used
394409 $ globalTraitClasses = [
395- 'AccesskeyTrait ' => ['type ' => 'string ' , 'description ' => 'Keyboard shortcut to activate or focus the element ' ],
396- 'AutocapitalizeTrait ' => ['type ' => 'string ' , 'description ' => 'Controls automatic capitalization ' ],
397- 'AutofocusTrait ' => ['type ' => 'boolean ' , 'description ' => 'Automatically focus the element when the page loads ' ],
398- 'ClassTrait ' => ['type ' => 'string ' , 'description ' => 'CSS class names ' ],
399- 'ContenteditableTrait ' => ['type ' => 'boolean | "true" | "false" | "inherit" ' , 'description ' => 'Whether the element is editable ' ],
400- 'DataTrait ' => ['type ' => 'Record<string, string> ' , 'description ' => 'Custom data attributes ' ],
401- 'DirTrait ' => ['type ' => '"ltr" | "rtl" | "auto" ' , 'description ' => 'Text direction ' ],
402- 'DraggableTrait ' => ['type ' => 'boolean ' , 'description ' => 'Whether the element can be dragged ' ],
403- 'HiddenTrait ' => ['type ' => 'boolean ' , 'description ' => 'Whether the element is hidden ' ],
404- 'IdTrait ' => ['type ' => 'string ' , 'description ' => 'Unique identifier ' ],
405- 'InputmodeTrait ' => ['type ' => 'string ' , 'description ' => 'Type of virtual keyboard to show ' ],
406- 'LangTrait ' => ['type ' => 'string ' , 'description ' => 'Language of the element ' ],
407- 'PopoverTrait ' => ['type ' => 'string ' , 'description ' => 'Controls popover behavior ' ],
408- 'SlotTrait ' => ['type ' => 'string ' , 'description ' => 'Shadow DOM slot name ' ],
409- 'SpellcheckTrait ' => ['type ' => 'boolean ' , 'description ' => 'Whether to check spelling ' ],
410- 'StyleTrait ' => ['type ' => 'string | Partial<CSSStyleDeclaration> ' , 'description ' => 'Inline CSS styles ' ],
411- 'TabindexTrait ' => ['type ' => 'number ' , 'description ' => 'Tab order position ' ],
412- 'TitleTrait ' => ['type ' => 'string ' , 'description ' => 'Tooltip text ' ],
413- 'TranslateTrait ' => ['type ' => 'boolean ' , 'description ' => 'Whether the element should be translated ' ],
410+ 'AccesskeyTrait ' => [
411+ 'type ' => 'string ' ,
412+ 'description ' => 'Keyboard shortcut to activate or focus the element ' ,
413+ ],
414+ 'AutocapitalizeTrait ' => [
415+ 'type ' => 'string ' ,
416+ 'description ' => 'Controls automatic capitalization ' ,
417+ ],
418+ 'AutofocusTrait ' => [
419+ 'type ' => 'boolean ' ,
420+ 'description ' => 'Automatically focus the element when the page loads ' ,
421+ ],
422+ 'ClassTrait ' => [
423+ 'type ' => 'string ' ,
424+ 'description ' => 'CSS class names ' ,
425+ ],
426+ 'ContenteditableTrait ' => [
427+ 'type ' => 'boolean | "true" | "false" | "inherit" ' ,
428+ 'description ' => 'Whether the element is editable ' ,
429+ ],
430+ 'DataTrait ' => [
431+ 'type ' => 'Record<string, string> ' ,
432+ 'description ' => 'Custom data attributes ' ,
433+ ],
434+ 'DirTrait ' => [
435+ 'type ' => '"ltr" | "rtl" | "auto" ' ,
436+ 'description ' => 'Text direction ' ,
437+ ],
438+ 'DraggableTrait ' => [
439+ 'type ' => 'boolean ' ,
440+ 'description ' => 'Whether the element can be dragged ' ,
441+ ],
442+ 'HiddenTrait ' => [
443+ 'type ' => 'boolean ' ,
444+ 'description ' => 'Whether the element is hidden ' ,
445+ ],
446+ 'IdTrait ' => [
447+ 'type ' => 'string ' ,
448+ 'description ' => 'Unique identifier ' ,
449+ ],
450+ 'InputmodeTrait ' => [
451+ 'type ' => 'string ' ,
452+ 'description ' => 'Type of virtual keyboard to show ' ,
453+ ],
454+ 'LangTrait ' => [
455+ 'type ' => 'string ' ,
456+ 'description ' => 'Language of the element ' ,
457+ ],
458+ 'PopoverTrait ' => [
459+ 'type ' => 'string ' ,
460+ 'description ' => 'Controls popover behavior ' ,
461+ ],
462+ 'SlotTrait ' => [
463+ 'type ' => 'string ' ,
464+ 'description ' => 'Shadow DOM slot name ' ,
465+ ],
466+ 'SpellcheckTrait ' => [
467+ 'type ' => 'boolean ' ,
468+ 'description ' => 'Whether to check spelling ' ,
469+ ],
470+ 'StyleTrait ' => [
471+ 'type ' => 'string | Partial<CSSStyleDeclaration> ' ,
472+ 'description ' => 'Inline CSS styles ' ,
473+ ],
474+ 'TabindexTrait ' => [
475+ 'type ' => 'number ' ,
476+ 'description ' => 'Tab order position ' ,
477+ ],
478+ 'TitleTrait ' => [
479+ 'type ' => 'string ' ,
480+ 'description ' => 'Tooltip text ' ,
481+ ],
482+ 'TranslateTrait ' => [
483+ 'type ' => 'boolean ' ,
484+ 'description ' => 'Whether the element should be translated ' ,
485+ ],
414486 ];
415487
416488 foreach ($ globalTraitClasses as $ traitName => $ traitInfo ) {
@@ -475,12 +547,7 @@ private function buildTypeScriptClass(
475547 $ ts .= " * @description {$ description }\n" ;
476548 $ ts .= " */ \n\n" ;
477549
478- // Import statements - none needed for now since we use inline union types
479- // if (!empty($enumImports)) {
480- // $ts .= "import { " . implode(', ', array_unique($enumImports)) . " } from './enums';\n\n";
481- // }
482550
483- // Interface definition
484551 $ ts .= "export interface {$ className }Props { \n" ;
485552 foreach ($ props as $ propName => $ propData ) {
486553 $ optional = $ propData ['required ' ] ? '' : '? ' ;
@@ -492,7 +559,7 @@ private function buildTypeScriptClass(
492559 }
493560 $ ts .= "} \n\n" ;
494561
495- // Class definition
562+
496563 $ ts .= "/** \n" ;
497564 $ ts .= " * {$ className } - {$ description }\n" ;
498565 $ ts .= " */ \n" ;
@@ -511,7 +578,7 @@ private function buildTypeScriptClass(
511578 $ quotedPropName = $ this ->quotePropertyName ($ propName );
512579 $ isQuoted = $ quotedPropName !== $ propName ;
513580 $ propAccess = $ isQuoted ? "[' {$ propName }'] " : ". {$ propName }" ;
514-
581+
515582 if ($ propName === 'children ' ) {
516583 $ ts .= " if (props {$ propAccess } !== undefined) { \n" ;
517584 $ ts .= " this.setChildren(props {$ propAccess }); \n" ;
@@ -564,7 +631,7 @@ private function buildTypeScriptClass(
564631 }
565632
566633 // setChildren method
567- if (!$ isSelfClosing ) {
634+ if (! $ isSelfClosing ) {
568635 $ ts .= " setChildren(children: string | HTMLElement | (string | HTMLElement)[]): this { \n" ;
569636 $ ts .= " // Clear existing children \n" ;
570637 $ ts .= " while (this.element.firstChild) { \n" ;
@@ -623,7 +690,7 @@ private function buildComposedTypeScript(
623690 $ ts = "/** \n" ;
624691 $ ts .= " * THIS FILE IS AUTOGENERATED. DO NOT EDIT IT. \n" ;
625692 $ ts .= " * \n" ;
626- $ ts .= " * @generated " . date ('F j, Y H:i:s ' ) . "\n" ;
693+ $ ts .= ' * @generated ' . date ('F j, Y H:i:s ' ) . "\n" ;
627694 $ ts .= " * @component {$ name }Example \n" ;
628695 $ ts .= " * @description {$ description } - Composed Example \n" ;
629696 $ ts .= " */ \n\n" ;
@@ -634,11 +701,17 @@ private function buildComposedTypeScript(
634701 $ ts .= " * {$ name }Example - Demonstrates valid parent-child relationships \n" ;
635702 $ ts .= " * \n" ;
636703 $ ts .= " * CONTENT MODEL: \n" ;
637- if (!empty ($ childOf )) {
638- $ ts .= " * - Can be child of: " . implode (', ' , array_map (function ($ c ) { return basename (str_replace ('\\' , '/ ' , $ c )); }, $ childOf )) . "\n" ;
704+ if (! empty ($ childOf )) {
705+ $ ts .= ' * - Can be child of: ' . implode (
706+ ', ' ,
707+ array_map (function ($ c ) { return basename (str_replace ('\\' , '/ ' , $ c )); }, $ childOf )
708+ ) . "\n" ;
639709 }
640- if (!empty ($ parentOf )) {
641- $ ts .= " * - Can contain: " . implode (', ' , array_map (function ($ c ) { return basename (str_replace ('\\' , '/ ' , $ c )); }, $ parentOf )) . "\n" ;
710+ if (! empty ($ parentOf )) {
711+ $ ts .= ' * - Can contain: ' . implode (
712+ ', ' ,
713+ array_map (function ($ c ) { return basename (str_replace ('\\' , '/ ' , $ c )); }, $ parentOf )
714+ ) . "\n" ;
642715 }
643716 $ ts .= " */ \n" ;
644717 $ ts .= "export class {$ name }Example { \n" ;
@@ -665,4 +738,4 @@ private function determineLevel(string $className): string
665738 }
666739 return 'block ' ;
667740 }
668- }
741+ }
0 commit comments