diff --git a/extra/html-extra/HtmlExtension.php b/extra/html-extra/HtmlExtension.php
index d8a6c0036d1..634df7f07d2 100644
--- a/extra/html-extra/HtmlExtension.php
+++ b/extra/html-extra/HtmlExtension.php
@@ -12,6 +12,7 @@
namespace Twig\Extra\Html;
use Symfony\Component\Mime\MimeTypes;
+use Twig\Environment;
use Twig\Error\RuntimeError;
use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
@@ -30,6 +31,7 @@ public function getFilters(): array
{
return [
new TwigFilter('data_uri', [$this, 'dataUri']),
+ new TwigFilter('html_attr_merge', 'twig_html_attr_merge'),
];
}
@@ -38,6 +40,7 @@ public function getFunctions(): array
return [
new TwigFunction('html_classes', [self::class, 'htmlClasses']),
new TwigFunction('html_cva', [self::class, 'htmlCva']),
+ new TwigFunction('html_attr', 'twig_html_attr', ['needs_environment' => true, 'is_safe' => ['html']]),
];
}
@@ -124,4 +127,75 @@ public static function htmlCva(array|string $base = [], array $variants = [], ar
{
return new Cva($base, $variants, $compoundVariants, $defaultVariant);
}
+
+static function twig_html_attr_merge(...$arrays): array
+{
+ $result = [];
+
+ foreach ($arrays as $argNumber => $array) {
+ if (!$array) {
+ continue;
+ }
+
+ if (!twig_test_iterable($array)) {
+ throw new RuntimeError(sprintf('The "attr_merge" filter only works with arrays or "Traversable", got "%s" for argument %d.', \gettype($array), $argNumber + 1));
+ }
+
+ $array = twig_to_array($array);
+
+ foreach (['class', 'style', 'data'] as $deepMergeKey) {
+ if (isset($array[$deepMergeKey])) {
+ $value = $array[$deepMergeKey];
+ unset($array[$deepMergeKey]);
+
+ if (!twig_test_iterable($value)) {
+ $value = (array) $value;
+ }
+
+ $value = twig_to_array($value);
+
+ $result[$deepMergeKey] = array_merge($result[$deepMergeKey] ?? [], $value);
+ }
+ }
+
+ $result = array_merge($result, $array);
+ }
+
+ return $result;
+}
+
+static function twig_html_attr(Environment $env, ...$args): string
+{
+ $attr = twig_html_attr_merge(...$args);
+
+ if (isset($attr['class'])) {
+ $attr['class'] = trim(implode(' ', array_values($attr['class'])));
+ }
+
+ if (isset($attr['style'])) {
+ $style = '';
+ foreach ($attr['style'] as $name => $value) {
+ if (is_numeric($name)) {
+ $style .= $value.'; ';
+ } else {
+ $style .= $name.': '.$value.'; ';
+ }
+ }
+ $attr['style'] = trim($style);
+ }
+
+ if (isset($attr['data'])) {
+ foreach ($attr['data'] as $name => $value) {
+ $attr['data-'.$name] = $value;
+ }
+ unset($attr['data']);
+ }
+
+ $result = '';
+ foreach ($attr as $name => $value) {
+ $result .= twig_escape_filter($env, $name, 'html_attr').'="'.htmlspecialchars($value).'" ';
+ }
+
+ return trim($result);
+}
}