Skip to content

Commit 2b699a9

Browse files
committed
feat(twig): Allow nested attributes
1 parent c9dbea1 commit 2b699a9

File tree

6 files changed

+124
-1
lines changed

6 files changed

+124
-1
lines changed

src/TwigComponent/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# CHANGELOG
22

3+
## 2.17.0
4+
5+
- Added nested attribute support.
6+
37
## 2.16.0
48

59
- Introduce CVA to style TwigComponent #1416

src/TwigComponent/src/ComponentAttributes.php

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@
1919
*
2020
* @immutable
2121
*/
22-
final class ComponentAttributes implements \IteratorAggregate, \Countable
22+
final class ComponentAttributes implements \Stringable, \IteratorAggregate, \Countable
2323
{
24+
private const NESTED_REGEX = '#^([\w-]+):(.+)$#';
25+
2426
/** @var array<string,true> */
2527
private array $rendered = [];
2628

@@ -39,6 +41,10 @@ public function __toString(): string
3941
fn (string $key) => !isset($this->rendered[$key])
4042
),
4143
function (string $carry, string $key) {
44+
if (preg_match(self::NESTED_REGEX, $key)) {
45+
return $carry;
46+
}
47+
4248
$value = $this->attributes[$key];
4349

4450
if ($value instanceof \Stringable) {
@@ -196,6 +202,19 @@ public function remove($key): self
196202
return new self($attributes);
197203
}
198204

205+
public function nested(string $namespace): self
206+
{
207+
$attributes = [];
208+
209+
foreach ($this->attributes as $key => $value) {
210+
if (preg_match(self::NESTED_REGEX, $key, $matches) && $namespace === $matches[1]) {
211+
$attributes[$matches[2]] = $value;
212+
}
213+
}
214+
215+
return new self($attributes);
216+
}
217+
199218
public function getIterator(): \Traversable
200219
{
201220
return new \ArrayIterator($this->attributes);
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<div{{ attributes }}/>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<main{{ attributes }}>
2+
<div{{ attributes.nested('title') }}>
3+
<span{{ attributes.nested('title').nested('span') }}>
4+
{{ component('JustAttributes', [...attributes.nested('inner')]) }}
5+
</span>
6+
</div>
7+
</main>

src/TwigComponent/tests/Integration/ComponentExtensionTest.php

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,84 @@ public function testComponentWithClassMerge(): void
263263
$this->assertStringContainsString('class="alert alert-red alert-lg font-semibold rounded-md dark:bg-gray-600 flex p-4"', $output);
264264
}
265265

266+
public function testRenderingComponentWithNestedAttributes(): void
267+
{
268+
$output = $this->renderComponent('NestedAttributes');
269+
270+
$this->assertSame(<<<HTML
271+
<main>
272+
<div>
273+
<span>
274+
<div/>
275+
276+
</span>
277+
</div>
278+
</main>
279+
HTML,
280+
trim($output)
281+
);
282+
283+
$output = $this->renderComponent('NestedAttributes', [
284+
'class' => 'foo',
285+
'title:class' => 'bar',
286+
'title:span:class' => 'baz',
287+
]);
288+
289+
$this->assertSame(<<<HTML
290+
<main class="foo">
291+
<div class="bar">
292+
<span class="baz">
293+
<div/>
294+
295+
</span>
296+
</div>
297+
</main>
298+
HTML,
299+
trim($output)
300+
);
301+
}
302+
303+
public function testRenderingHtmlSyntaxComponentWithNestedAttributes(): void
304+
{
305+
$output = self::getContainer()
306+
->get(Environment::class)
307+
->createTemplate('<twig:NestedAttributes />')
308+
->render()
309+
;
310+
311+
$this->assertSame(<<<HTML
312+
<main>
313+
<div>
314+
<span>
315+
<div/>
316+
317+
</span>
318+
</div>
319+
</main>
320+
HTML,
321+
trim($output)
322+
);
323+
324+
$output = self::getContainer()
325+
->get(Environment::class)
326+
->createTemplate('<twig:NestedAttributes class="foo" title:class="bar" title:span:class="baz" inner:class="foo" />')
327+
->render()
328+
;
329+
330+
$this->assertSame(<<<HTML
331+
<main class="foo">
332+
<div class="bar">
333+
<span class="baz">
334+
<div class="foo"/>
335+
336+
</span>
337+
</div>
338+
</main>
339+
HTML,
340+
trim($output)
341+
);
342+
}
343+
266344
private function renderComponent(string $name, array $data = []): string
267345
{
268346
return self::getContainer()->get(Environment::class)->render('render_component.html.twig', [

src/TwigComponent/tests/Unit/ComponentAttributesTest.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,4 +244,18 @@ public function testCanCheckIfAttributeExists(): void
244244

245245
$this->assertTrue($attributes->has('foo'));
246246
}
247+
248+
public function testNestedAttributes(): void
249+
{
250+
$attributes = new ComponentAttributes([
251+
'class' => 'foo',
252+
'title:class' => 'bar',
253+
'title:span:class' => 'baz',
254+
]);
255+
256+
$this->assertSame(' class="foo"', (string) $attributes);
257+
$this->assertSame(' class="bar"', (string) $attributes->nested('title'));
258+
$this->assertSame(' class="baz"', (string) $attributes->nested('title')->nested('span'));
259+
$this->assertSame('', (string) $attributes->nested('invalid'));
260+
}
247261
}

0 commit comments

Comments
 (0)