diff --git a/README.md b/README.md
index b084b46c..3300cf2f 100644
--- a/README.md
+++ b/README.md
@@ -204,7 +204,8 @@ class ScriptNonceSubscriber implements EventSubscriberInterface
### stimulus_controller
This bundle also ships with a special `stimulus_controller()` Twig function
-that can be used to render [Stimulus Controllers & Values](https://stimulus.hotwired.dev/reference/values).
+that can be used to render [Stimulus Controllers & Values](https://stimulus.hotwired.dev/reference/values)
+and [CSS Classes](https://stimulus.hotwired.dev/reference/css-classes).
See [stimulus-bridge](https://github.com/symfony/stimulus-bridge) for more details.
For example:
@@ -224,6 +225,29 @@ For example:
```
+If you want to set CSS classes:
+
+```twig
+
+ Hello
+
+
+
+
+ Hello
+
+
+
+
+ Hello
+
+```
+
Any non-scalar values (like `data: [1, 2, 3, 4]`) are JSON-encoded. And all
values are properly escaped (the string `[` is an escaped
`[` character, so the attribute is really `[1,2,3,4]`).
diff --git a/src/Dto/StimulusControllersDto.php b/src/Dto/StimulusControllersDto.php
index 096e5cc7..5cdde862 100644
--- a/src/Dto/StimulusControllersDto.php
+++ b/src/Dto/StimulusControllersDto.php
@@ -15,8 +15,9 @@ final class StimulusControllersDto extends AbstractStimulusDto
{
private $controllers = [];
private $values = [];
+ private $classes = [];
- public function addController(string $controllerName, array $controllerValues = []): void
+ public function addController(string $controllerName, array $controllerValues = [], array $controllerClasses = []): void
{
$controllerName = $this->getFormattedControllerName($controllerName);
$this->controllers[] = $controllerName;
@@ -31,6 +32,12 @@ public function addController(string $controllerName, array $controllerValues =
$this->values['data-'.$controllerName.'-'.$key.'-value'] = $value;
}
+
+ foreach ($controllerClasses as $key => $class) {
+ $key = $this->escapeAsHtmlAttr($this->normalizeKeyName($key));
+
+ $this->values['data-'.$controllerName.'-'.$key.'-class'] = $class;
+ }
}
public function __toString(): string
@@ -39,9 +46,15 @@ public function __toString(): string
return '';
}
- return rtrim('data-controller="'.implode(' ', $this->controllers).'" '.implode(' ', array_map(static function (string $attribute, string $value): string {
- return $attribute.'="'.$value.'"';
- }, array_keys($this->values), $this->values)));
+ return rtrim(
+ 'data-controller="'.implode(' ', $this->controllers).'" '.
+ implode(' ', array_map(static function (string $attribute, string $value): string {
+ return $attribute.'="'.$value.'"';
+ }, array_keys($this->values), $this->values)).' '.
+ implode(' ', array_map(static function (string $attribute, string $value): string {
+ return $attribute.'="'.$value.'"';
+ }, array_keys($this->classes), $this->classes))
+ );
}
public function toArray(): array
@@ -52,7 +65,7 @@ public function toArray(): array
return [
'data-controller' => implode(' ', $this->controllers),
- ] + $this->values;
+ ] + $this->values + $this->classes;
}
/**
diff --git a/src/Twig/StimulusTwigExtension.php b/src/Twig/StimulusTwigExtension.php
index 4545dac7..68509867 100644
--- a/src/Twig/StimulusTwigExtension.php
+++ b/src/Twig/StimulusTwigExtension.php
@@ -38,18 +38,19 @@ public function getFilters(): array
}
/**
- * @param string $controllerName the Stimulus controller name
- * @param array $controllerValues array of controller values
+ * @param string $controllerName the Stimulus controller name
+ * @param array $controllerValues array of controller values
+ * @param array $controllerClasses array of controller CSS classes
*/
- public function renderStimulusController(Environment $env, $controllerName, array $controllerValues = []): StimulusControllersDto
+ public function renderStimulusController(Environment $env, $controllerName, array $controllerValues = [], array $controllerClasses = []): StimulusControllersDto
{
$dto = new StimulusControllersDto($env);
if (\is_array($controllerName)) {
trigger_deprecation('symfony/webpack-encore-bundle', 'v1.15.0', 'Passing an array as first argument of stimulus_controller() is deprecated.');
- if ($controllerValues) {
- throw new \InvalidArgumentException('You cannot pass an array to the first and second argument of stimulus_controller(): check the documentation.');
+ if ($controllerValues || $controllerClasses) {
+ throw new \InvalidArgumentException('You cannot pass an array to the first and second/third argument of stimulus_controller(): check the documentation.');
}
$data = $controllerName;
@@ -61,7 +62,7 @@ public function renderStimulusController(Environment $env, $controllerName, arra
return $dto;
}
- $dto->addController($controllerName, $controllerValues);
+ $dto->addController($controllerName, $controllerValues, $controllerClasses);
return $dto;
}
@@ -107,9 +108,9 @@ public function renderStimulusAction(Environment $env, $controllerName, string $
return $dto;
}
- public function appendStimulusController(StimulusControllersDto $dto, string $controllerName, array $controllerValues = []): StimulusControllersDto
+ public function appendStimulusController(StimulusControllersDto $dto, string $controllerName, array $controllerValues = [], array $controllerClasses = []): StimulusControllersDto
{
- $dto->addController($controllerName, $controllerValues);
+ $dto->addController($controllerName, $controllerValues, $controllerClasses);
return $dto;
}
diff --git a/tests/IntegrationTest.php b/tests/IntegrationTest.php
index 95548ce0..eb1c9fcb 100644
--- a/tests/IntegrationTest.php
+++ b/tests/IntegrationTest.php
@@ -209,13 +209,17 @@ public function provideRenderStimulusController()
'controllerValues' => [
'my"Key"' => true,
],
- 'expectedString' => 'data-controller="symfony--ux-dropzone--dropzone" data-symfony--ux-dropzone--dropzone-my-key-value="true"',
- 'expectedArray' => ['data-controller' => 'symfony--ux-dropzone--dropzone', 'data-symfony--ux-dropzone--dropzone-my-key-value' => 'true'],
+ 'controllerClasses' => [
+ 'second"Key"' => 'loading',
+ ],
+ 'expectedString' => 'data-controller="symfony--ux-dropzone--dropzone" data-symfony--ux-dropzone--dropzone-my-key-value="true" data-symfony--ux-dropzone--dropzone-second-key-class="loading"',
+ 'expectedArray' => ['data-controller' => 'symfony--ux-dropzone--dropzone', 'data-symfony--ux-dropzone--dropzone-my-key-value' => 'true', 'data-symfony--ux-dropzone--dropzone-second-key-class' => 'loading'],
];
yield 'short-single-controller-no-data' => [
'dataOrControllerName' => 'my-controller',
'controllerValues' => [],
+ 'controllerClasses' => [],
'expectedString' => 'data-controller="my-controller"',
'expectedArray' => ['data-controller' => 'my-controller'],
];
@@ -223,6 +227,7 @@ public function provideRenderStimulusController()
yield 'short-single-controller-with-data' => [
'dataOrControllerName' => 'my-controller',
'controllerValues' => ['myValue' => 'scalar-value'],
+ 'controllerClasses' => [],
'expectedString' => 'data-controller="my-controller" data-my-controller-my-value-value="scalar-value"',
'expectedArray' => ['data-controller' => 'my-controller', 'data-my-controller-my-value-value' => 'scalar-value'],
];
@@ -230,6 +235,7 @@ public function provideRenderStimulusController()
yield 'false-attribute-value-renders-false' => [
'dataOrControllerName' => 'false-controller',
'controllerValues' => ['isEnabled' => false],
+ 'controllerClasses' => [],
'expectedString' => 'data-controller="false-controller" data-false-controller-is-enabled-value="false"',
'expectedArray' => ['data-controller' => 'false-controller', 'data-false-controller-is-enabled-value' => 'false'],
];
@@ -237,6 +243,7 @@ public function provideRenderStimulusController()
yield 'true-attribute-value-renders-true' => [
'dataOrControllerName' => 'true-controller',
'controllerValues' => ['isEnabled' => true],
+ 'controllerClasses' => [],
'expectedString' => 'data-controller="true-controller" data-true-controller-is-enabled-value="true"',
'expectedArray' => ['data-controller' => 'true-controller', 'data-true-controller-is-enabled-value' => 'true'],
];
@@ -244,22 +251,31 @@ public function provideRenderStimulusController()
yield 'null-attribute-value-does-not-render' => [
'dataOrControllerName' => 'null-controller',
'controllerValues' => ['firstName' => null],
+ 'controllerClasses' => [],
'expectedString' => 'data-controller="null-controller"',
'expectedArray' => ['data-controller' => 'null-controller'],
];
+
+ yield 'short-single-controller-no-data-with-class' => [
+ 'dataOrControllerName' => 'my-controller',
+ 'controllerValues' => [],
+ 'controllerClasses' => ['loading' => 'spinner'],
+ 'expectedString' => 'data-controller="my-controller" data-my-controller-loading-class="spinner"',
+ 'expectedArray' => ['data-controller' => 'my-controller', 'data-my-controller-loading-class' => 'spinner'],
+ ];
}
/**
* @dataProvider provideRenderStimulusController
*/
- public function testRenderStimulusController($dataOrControllerName, array $controllerValues, string $expectedString, array $expectedArray)
+ public function testRenderStimulusController($dataOrControllerName, array $controllerValues, array $controllerClasses, string $expectedString, array $expectedArray)
{
$kernel = new WebpackEncoreIntegrationTestKernel(true);
$kernel->boot();
$twig = $this->getTwigEnvironmentFromBootedKernel($kernel);
$extension = new StimulusTwigExtension();
- $dto = $extension->renderStimulusController($twig, $dataOrControllerName, $controllerValues);
+ $dto = $extension->renderStimulusController($twig, $dataOrControllerName, $controllerValues, $controllerClasses);
$this->assertSame($expectedString, (string) $dto);
$this->assertSame($expectedArray, $dto->toArray());
}