diff --git a/src/TwigComponent/CHANGELOG.md b/src/TwigComponent/CHANGELOG.md index 02b46078539..6a306d36e87 100644 --- a/src/TwigComponent/CHANGELOG.md +++ b/src/TwigComponent/CHANGELOG.md @@ -1,5 +1,9 @@ # CHANGELOG +## 2.19.0 + +- Allow escape nesting separator with `::` #1959 + ## 2.17.0 - Add nested attribute support #1405 diff --git a/src/TwigComponent/doc/index.rst b/src/TwigComponent/doc/index.rst index 1c0bbde0d1d..47d305c69aa 100644 --- a/src/TwigComponent/doc/index.rst +++ b/src/TwigComponent/doc/index.rst @@ -486,12 +486,12 @@ component use a ``PreMount`` hook:: In its default configuration, the OptionsResolver treats all props. However, if more props are passed than the options defined in the OptionsResolver, an error will be prompted, indicating that one or more options do not exist. To avoid this, use the `ignoreUndefined()` method with `true`. See `ignore not defined options`_ for more info. - + $resolver->setIgnoreUndefined(true); - - The major drawback of this configuration is that the OptionsResolver will remove every non-defined option when resolving data. + + The major drawback of this configuration is that the OptionsResolver will remove every non-defined option when resolving data. To maintain props that have not been defined within the OptionsResolver, combine the data from the hook with the resolved data. - + return $resolver->resolve($data) + $data; @@ -1127,6 +1127,15 @@ The nesting is recursive so you could potentially do something like this: row:widget:class="ui-form-widget" /> +.. note:: + + If you require an attribute that actually includes a ``:`` (like ``x-on:click``), + you can escape the nested attribute separator by using a double colon (``x-on::click``). + +.. versionadded:: 2.19 + + The ability to escape the nested attribute separator was added in TwigComponents 2.19. + Component with Complex Variants (CVA) ------------------------------------- diff --git a/src/TwigComponent/src/ComponentAttributes.php b/src/TwigComponent/src/ComponentAttributes.php index 57c5e4676d6..e61e3eb064f 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-]+):(?!:)(.+)$#'; /** @var array */ private array $rendered = []; @@ -64,6 +64,8 @@ function (string $carry, string $key) { $value = 'true'; } + $key = str_replace('::', ':', $key); + return match ($value) { true => "{$carry} {$key}", false => $carry, diff --git a/src/TwigComponent/tests/Integration/ComponentExtensionTest.php b/src/TwigComponent/tests/Integration/ComponentExtensionTest.php index b6eaa07d651..15fa58f26c0 100644 --- a/src/TwigComponent/tests/Integration/ComponentExtensionTest.php +++ b/src/TwigComponent/tests/Integration/ComponentExtensionTest.php @@ -331,12 +331,12 @@ public function testRenderingHtmlSyntaxComponentWithNestedAttributes(): void $output = self::getContainer() ->get(Environment::class) - ->createTemplate('') + ->createTemplate('') ->render() ; $this->assertSame(<< +
diff --git a/src/TwigComponent/tests/Unit/ComponentAttributesTest.php b/src/TwigComponent/tests/Unit/ComponentAttributesTest.php index 824a148f7d3..95f2ae26cf6 100644 --- a/src/TwigComponent/tests/Unit/ComponentAttributesTest.php +++ b/src/TwigComponent/tests/Unit/ComponentAttributesTest.php @@ -249,14 +249,21 @@ public function testNestedAttributes(): void { $attributes = new ComponentAttributes([ 'class' => 'foo', + 'data-class' => 'qux', 'title:class' => 'bar', + 'title:x-on::click' => '!close', 'title:span:class' => 'baz', + 'data-title:class' => 'flo', + 'data-title:data-class' => 'shi', + 'x-on::click' => '!open', ]); - $this->assertSame(' class="foo"', (string) $attributes); - $this->assertSame(' class="bar"', (string) $attributes->nested('title')); + $this->assertSame(' class="foo" data-class="qux" x-on:click="!open"', (string) $attributes); + $this->assertSame(' class="bar" x-on:click="!close"', (string) $attributes->nested('title')); $this->assertSame(' class="baz"', (string) $attributes->nested('title')->nested('span')); $this->assertSame('', (string) $attributes->nested('invalid')); + $this->assertSame('', (string) $attributes->nested('x-on')); + $this->assertSame(' class="flo" data-class="shi"', (string) $attributes->nested('data-title')); } public function testConvertTrueAriaAttributeValue(): void