From 06227e7aba73a38fb3675148ecca3af503ab8cd9 Mon Sep 17 00:00:00 2001 From: Dimitri Gritsajuk Date: Sat, 2 Nov 2024 17:57:22 +0100 Subject: [PATCH] Allow to disable nested attributes with ! --- src/TwigComponent/CHANGELOG.md | 4 ++ src/TwigComponent/src/ComponentAttributes.php | 6 ++- src/TwigComponent/src/Twig/TwigPreLexer.php | 6 ++- .../Integration/ComponentExtensionTest.php | 38 +++++++++++++++++++ .../tests/Unit/ComponentAttributesTest.php | 14 +++++++ 5 files changed, 66 insertions(+), 2 deletions(-) diff --git a/src/TwigComponent/CHANGELOG.md b/src/TwigComponent/CHANGELOG.md index 34d110e3b36..e5aea74b5b1 100644 --- a/src/TwigComponent/CHANGELOG.md +++ b/src/TwigComponent/CHANGELOG.md @@ -1,5 +1,9 @@ # CHANGELOG +## 2.21.0 + +- Allow to disable nested attributes with ! + ## 2.20.0 - Add Anonymous Component support for 3rd-party bundles #2019 diff --git a/src/TwigComponent/src/ComponentAttributes.php b/src/TwigComponent/src/ComponentAttributes.php index 57c5e4676d6..425feda2da6 100644 --- a/src/TwigComponent/src/ComponentAttributes.php +++ b/src/TwigComponent/src/ComponentAttributes.php @@ -21,7 +21,7 @@ */ final class ComponentAttributes implements \Stringable, \IteratorAggregate, \Countable { - private const NESTED_REGEX = '#^([\w-]+):(.+)$#'; + private const NESTED_REGEX = '#^([\w-]+):(.+)(? */ private array $rendered = []; @@ -64,6 +64,10 @@ function (string $carry, string $key) { $value = 'true'; } + if (str_ends_with($key, '!')) { + $key = substr($key, 0, -1); + } + return match ($value) { true => "{$carry} {$key}", false => $carry, diff --git a/src/TwigComponent/src/Twig/TwigPreLexer.php b/src/TwigComponent/src/Twig/TwigPreLexer.php index 2e0fdbe7199..93dc9fc86fd 100644 --- a/src/TwigComponent/src/Twig/TwigPreLexer.php +++ b/src/TwigComponent/src/Twig/TwigPreLexer.php @@ -200,7 +200,7 @@ public function preLexComponents(string $input): string private function consumeComponentName(?string $customExceptionMessage = null): string { $start = $this->position; - while ($this->position < $this->length && preg_match('/[A-Za-z0-9_:@\-.]/', $this->input[$this->position])) { + while ($this->position < $this->length && preg_match('/[A-Za-z0-9_:@!\-.]/', $this->input[$this->position])) { ++$this->position; } @@ -253,6 +253,10 @@ private function consumeAttributes(string $componentName): string $key = $this->consumeAttributeName($componentName); + if (str_ends_with($key, '!')) { + $isAttributeDynamic = false; + } + // -> someProp: true if (!$this->check('=')) { // don't allow "" diff --git a/src/TwigComponent/tests/Integration/ComponentExtensionTest.php b/src/TwigComponent/tests/Integration/ComponentExtensionTest.php index 8b2b0e0987a..129501833b0 100644 --- a/src/TwigComponent/tests/Integration/ComponentExtensionTest.php +++ b/src/TwigComponent/tests/Integration/ComponentExtensionTest.php @@ -316,6 +316,44 @@ public function testRenderingComponentWithNestedAttributes(): void ); } + public function testRenderingComponentWithNestedAndNotNestedAttributes(): void + { + $output = $this->renderComponent('NestedAttributes'); + + $this->assertSame(<< +
+ +
+ + +
+ + HTML, + trim($output) + ); + + $output = $this->renderComponent('NestedAttributes', [ + 'class' => 'foo', + 'x-bind:class!' => 'alpine', + ':class!' => 'alpine', + 'title:span:class' => 'baz', + ]); + + $this->assertSame(<< +
+ +
+ + +
+ + HTML, + trim($output) + ); + } + public function testRenderingHtmlSyntaxComponentWithNestedAttributes(): void { $output = self::getContainer() diff --git a/src/TwigComponent/tests/Unit/ComponentAttributesTest.php b/src/TwigComponent/tests/Unit/ComponentAttributesTest.php index 8ed7aa4f004..351253cb3d1 100644 --- a/src/TwigComponent/tests/Unit/ComponentAttributesTest.php +++ b/src/TwigComponent/tests/Unit/ComponentAttributesTest.php @@ -259,6 +259,20 @@ public function testNestedAttributes(): void $this->assertSame('', (string) $attributes->nested('invalid')); } + public function testNotNestedAttributes(): void + { + $attributes = new ComponentAttributes([ + 'class' => 'foo', + 'x-bind:class!' => 'alpine', + ':class!' => 'alpine', + 'title:span:class' => 'baz', + ]); + + $this->assertSame(' class="foo" x-bind:class="alpine" :class="alpine"', (string) $attributes); + $this->assertSame(' class="baz"', (string) $attributes->nested('title')->nested('span')); + $this->assertSame('', (string) $attributes->nested('invalid')); + } + public function testConvertTrueAriaAttributeValue(): void { $attributes = new ComponentAttributes([