1919use Symfony \Component \Console \Command \Command ;
2020use Symfony \Component \Console \Input \InputArgument ;
2121use Symfony \Component \Console \Input \InputInterface ;
22+ use Symfony \Component \Console \Input \InputOption ;
2223use Symfony \Component \Console \Question \Question ;
2324use Symfony \UX \StimulusBundle \StimulusBundle ;
2425use Symfony \WebpackEncoreBundle \WebpackEncoreBundle ;
@@ -44,25 +45,34 @@ public function configureCommand(Command $command, InputConfiguration $inputConf
4445 {
4546 $ command
4647 ->addArgument ('name ' , InputArgument::REQUIRED , 'The name of the Stimulus controller (e.g. <fg=yellow>hello</>) ' )
48+ ->addOption ('typescript ' , 'ts ' , InputOption::VALUE_NONE , 'Create a TypeScript controller (default is JavaScript) ' )
4749 ->setHelp ($ this ->getHelpFileContents ('MakeStimulusController.txt ' ))
4850 ;
51+
52+ $ inputConfig ->setArgumentAsNonInteractive ('typescript ' );
4953 }
5054
5155 public function interact (InputInterface $ input , ConsoleStyle $ io , Command $ command ): void
5256 {
5357 $ command ->addArgument ('extension ' , InputArgument::OPTIONAL );
5458 $ command ->addArgument ('targets ' , InputArgument::OPTIONAL );
5559 $ command ->addArgument ('values ' , InputArgument::OPTIONAL );
60+ $ command ->addArgument ('classes ' , InputArgument::OPTIONAL );
61+
62+ if ($ input ->getOption ('typescript ' )) {
63+ $ input ->setArgument ('extension ' , 'ts ' );
64+ } else {
65+ $ chosenExtension = $ io ->choice (
66+ 'Language (<fg=yellow>JavaScript</> or <fg=yellow>TypeScript</>) ' ,
67+ [
68+ 'js ' => 'JavaScript ' ,
69+ 'ts ' => 'TypeScript ' ,
70+ ],
71+ 'js ' ,
72+ );
5673
57- $ chosenExtension = $ io ->choice (
58- 'Language (<fg=yellow>JavaScript</> or <fg=yellow>TypeScript</>) ' ,
59- [
60- 'js ' => 'JavaScript ' ,
61- 'ts ' => 'TypeScript ' ,
62- ]
63- );
64-
65- $ input ->setArgument ('extension ' , $ chosenExtension );
74+ $ input ->setArgument ('extension ' , $ chosenExtension );
75+ }
6676
6777 if ($ io ->confirm ('Do you want to include targets? ' )) {
6878 $ targets = [];
@@ -98,16 +108,35 @@ public function interact(InputInterface $input, ConsoleStyle $io, Command $comma
98108
99109 $ input ->setArgument ('values ' , $ values );
100110 }
111+
112+ if ($ io ->confirm ('Do you want to add classes? ' , false )) {
113+ $ classes = [];
114+ $ isFirstClass = true ;
115+
116+ while (true ) {
117+ $ newClass = $ this ->askForNextClass ($ io , $ classes , $ isFirstClass );
118+ if (null === $ newClass ) {
119+ break ;
120+ }
121+
122+ $ isFirstClass = false ;
123+ $ classes [] = $ newClass ;
124+ }
125+
126+ $ input ->setArgument ('classes ' , $ classes );
127+ }
101128 }
102129
103130 public function generate (InputInterface $ input , ConsoleStyle $ io , Generator $ generator ): void
104131 {
105132 $ controllerName = Str::asSnakeCase ($ input ->getArgument ('name ' ));
106133 $ chosenExtension = $ input ->getArgument ('extension ' );
107- $ targets = $ input ->getArgument ('targets ' );
108- $ values = $ input ->getArgument ('values ' );
134+ $ targets = $ targetArgs = $ input ->getArgument ('targets ' ) ?? [];
135+ $ values = $ valuesArg = $ input ->getArgument ('values ' ) ?? [];
136+ $ classes = $ classesArgs = $ input ->getArgument ('classes ' ) ?? [];
109137
110138 $ targets = empty ($ targets ) ? $ targets : \sprintf ("['%s'] " , implode ("', ' " , $ targets ));
139+ $ classes = $ classes ? \sprintf ("['%s'] " , implode ("', ' " , $ classes )) : null ;
111140
112141 $ fileName = \sprintf ('%s_controller.%s ' , $ controllerName , $ chosenExtension );
113142 $ filePath = \sprintf ('assets/controllers/%s ' , $ fileName );
@@ -118,6 +147,7 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen
118147 [
119148 'targets ' => $ targets ,
120149 'values ' => $ values ,
150+ 'classes ' => $ classes ,
121151 ]
122152 );
123153
@@ -128,7 +158,12 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen
128158 $ io ->text ([
129159 'Next: ' ,
130160 \sprintf ('- Open <info>%s</info> and add the code you need ' , $ filePath ),
131- 'Find the documentation at <fg=yellow>https://github.com/symfony/stimulus-bridge</> ' ,
161+ '- Use the controller in your templates: ' ,
162+ ...array_map (
163+ fn (string $ line ): string => " $ line " ,
164+ explode ("\n" , $ this ->generateUsageExample ($ controllerName , $ targetArgs , $ valuesArg , $ classesArgs )),
165+ ),
166+ 'Find the documentation at <fg=yellow>https://symfony.com/bundles/StimulusBundle</> ' ,
132167 ]);
133168 }
134169
@@ -215,6 +250,29 @@ private function askForNextValue(ConsoleStyle $io, array $values, bool $isFirstV
215250 return ['name ' => $ valueName , 'type ' => $ type ];
216251 }
217252
253+ /** @param string[] $classes */
254+ private function askForNextClass (ConsoleStyle $ io , array $ classes , bool $ isFirstClass ): ?string
255+ {
256+ $ questionText = 'New class name (press <return> to stop adding classes) ' ;
257+
258+ if (!$ isFirstClass ) {
259+ $ questionText = 'Add another class? Enter the class name (or press <return> to stop adding classes) ' ;
260+ }
261+
262+ $ className = $ io ->ask ($ questionText , validator: function (?string $ name ) use ($ classes ) {
263+ if (str_contains ($ name , ' ' )) {
264+ throw new \InvalidArgumentException ('Class name cannot contain spaces. ' );
265+ }
266+ if (\in_array ($ name , $ classes , true )) {
267+ throw new \InvalidArgumentException (\sprintf ('The "%s" class already exists. ' , $ name ));
268+ }
269+
270+ return $ name ;
271+ });
272+
273+ return $ className ?: null ;
274+ }
275+
218276 private function printAvailableTypes (ConsoleStyle $ io ): void
219277 {
220278 foreach ($ this ->getValuesTypes () as $ type ) {
@@ -234,6 +292,51 @@ private function getValuesTypes(): array
234292 ];
235293 }
236294
295+ /**
296+ * @param array<int, string> $targets
297+ * @param array<array{name: string, type: string}> $values
298+ * @param array<int, string> $classes
299+ */
300+ private function generateUsageExample (string $ name , array $ targets , array $ values , array $ classes ): string
301+ {
302+ $ slugify = fn (string $ name ) => str_replace ('_ ' , '- ' , Str::asSnakeCase ($ name ));
303+ $ controller = $ slugify ($ name );
304+
305+ $ htmlTargets = [];
306+ foreach ($ targets as $ target ) {
307+ $ htmlTargets [] = \sprintf ('<div data-%s-target="%s"></div> ' , $ controller , $ target );
308+ }
309+
310+ $ htmlValues = [];
311+ foreach ($ values as ['name ' => $ name , 'type ' => $ type ]) {
312+ $ value = match ($ type ) {
313+ 'Array ' => '[] ' ,
314+ 'Boolean ' => 'false ' ,
315+ 'Number ' => '123 ' ,
316+ 'Object ' => '{} ' ,
317+ 'String ' => 'abc ' ,
318+ default => '' ,
319+ };
320+ $ htmlValues [] = \sprintf ('data-%s-%s-value="%s" ' , $ controller , $ slugify ($ name ), $ value );
321+ }
322+
323+ $ htmlClasses = [];
324+ foreach ($ classes as $ class ) {
325+ $ value = Str::asLowerCamelCase ($ class );
326+ $ htmlClasses [] = \sprintf ('data-%s-%s-class="%s" ' , $ controller , $ slugify ($ class ), $ value );
327+ }
328+
329+ return \sprintf (
330+ '<div data-controller="%s"%s%s%s>%s%s</div> ' ,
331+ $ controller ,
332+ $ htmlValues ? ("\n " .implode ("\n " , $ htmlValues )) : '' ,
333+ $ htmlClasses ? ("\n " .implode ("\n " , $ htmlClasses )) : '' ,
334+ ($ htmlValues || $ htmlClasses ) ? "\n" : '' ,
335+ $ htmlTargets ? ("\n " .implode ("\n " , $ htmlTargets )) : '' ,
336+ "\n <!-- ... --> \n" ,
337+ );
338+ }
339+
237340 public function configureDependencies (DependencyBuilder $ dependencies ): void
238341 {
239342 // lower than 8.1, allow WebpackEncoreBundle
0 commit comments