Skip to content

Commit b75a01f

Browse files
[8.x] Blade component slot attributes (#38372)
* Update slot pattern * Add attributes params to existing tests * Create ComponentSlot class * Pass attributes from slot tag to Blade directive * Compile slot attributes into slot object * Add compilation tests for attribute support * Remove unused exception * Reorder arguments * Fix dynamic components with slot attributes * Escape bound attributes for slots * Update BladeComponentTagCompilerTest.php * formattinG Co-authored-by: Taylor Otwell <[email protected]>
1 parent ed4f354 commit b75a01f

File tree

7 files changed

+163
-18
lines changed

7 files changed

+163
-18
lines changed

src/Illuminate/View/Compilers/ComponentTagCompiler.php

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -395,14 +395,53 @@ protected function compileClosingTags(string $value)
395395
*/
396396
public function compileSlots(string $value)
397397
{
398-
$value = preg_replace_callback('/<\s*x[\-\:]slot\s+(:?)name=(?<name>(\"[^\"]+\"|\\\'[^\\\']+\\\'|[^\s>]+))\s*>/', function ($matches) {
398+
$pattern = "/
399+
<
400+
\s*
401+
x[\-\:]slot
402+
\s+
403+
(:?)name=(?<name>(\"[^\"]+\"|\\\'[^\\\']+\\\'|[^\s>]+))
404+
(?<attributes>
405+
(?:
406+
\s+
407+
(?:
408+
(?:
409+
\{\{\s*\\\$attributes(?:[^}]+?)?\s*\}\}
410+
)
411+
|
412+
(?:
413+
[\w\-:.@]+
414+
(
415+
=
416+
(?:
417+
\\\"[^\\\"]*\\\"
418+
|
419+
\'[^\']*\'
420+
|
421+
[^\'\\\"=<>]+
422+
)
423+
)?
424+
)
425+
)
426+
)*
427+
\s*
428+
)
429+
(?<![\/=\-])
430+
>
431+
/x";
432+
433+
$value = preg_replace_callback($pattern, function ($matches) {
399434
$name = $this->stripQuotes($matches['name']);
400435

401436
if ($matches[1] !== ':') {
402437
$name = "'{$name}'";
403438
}
404439

405-
return " @slot({$name}) ";
440+
$this->boundAttributes = [];
441+
442+
$attributes = $this->getAttributesFromAttributeString($matches['attributes']);
443+
444+
return " @slot({$name}, null, [".$this->attributesToString($attributes).']) ';
406445
}, $value);
407446

408447
return preg_replace('/<\/\s*x[\-\:]slot[^>]*>/', ' @endslot', $value);

src/Illuminate/View/ComponentSlot.php

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<?php
2+
3+
namespace Illuminate\View;
4+
5+
use Illuminate\Contracts\Support\Htmlable;
6+
7+
class ComponentSlot implements Htmlable
8+
{
9+
/**
10+
* The slot attribute bag.
11+
*
12+
* @var \Illuminate\View\ComponentAttributeBag
13+
*/
14+
public $attributes;
15+
16+
/**
17+
* The slot contents.
18+
*
19+
* @var string
20+
*/
21+
protected $contents;
22+
23+
/**
24+
* Create a new slot instance.
25+
*
26+
* @param string $contents
27+
* @param array $attributes
28+
* @return void
29+
*/
30+
public function __construct($contents = '', $attributes = [])
31+
{
32+
$this->contents = $contents;
33+
34+
$this->withAttributes($attributes);
35+
}
36+
37+
/**
38+
* Set the extra attributes that the slot should make available.
39+
*
40+
* @param array $attributes
41+
* @return $this
42+
*/
43+
public function withAttributes(array $attributes)
44+
{
45+
$this->attributes = new ComponentAttributeBag($attributes);
46+
47+
return $this;
48+
}
49+
50+
/**
51+
* Get the slot's HTML string.
52+
*
53+
* @return string
54+
*/
55+
public function toHtml()
56+
{
57+
return $this->contents;
58+
}
59+
60+
/**
61+
* Determine if the slot is empty.
62+
*
63+
* @return bool
64+
*/
65+
public function isEmpty()
66+
{
67+
return $this->contents === '';
68+
}
69+
70+
/**
71+
* Determine if the slot is not empty.
72+
*
73+
* @return bool
74+
*/
75+
public function isNotEmpty()
76+
{
77+
return ! $this->isEmpty();
78+
}
79+
80+
/**
81+
* Get the slot's HTML string.
82+
*
83+
* @return string
84+
*/
85+
public function __toString()
86+
{
87+
return $this->toHtml();
88+
}
89+
}

src/Illuminate/View/Concerns/ManagesComponents.php

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
use Illuminate\Contracts\View\View;
77
use Illuminate\Support\Arr;
88
use Illuminate\Support\HtmlString;
9-
use InvalidArgumentException;
9+
use Illuminate\View\ComponentSlot;
1010

1111
trait ManagesComponents
1212
{
@@ -120,20 +120,17 @@ protected function componentData()
120120
*
121121
* @param string $name
122122
* @param string|null $content
123+
* @param array $attributes
123124
* @return void
124-
*
125-
* @throws \InvalidArgumentException
126125
*/
127-
public function slot($name, $content = null)
126+
public function slot($name, $content = null, $attributes = [])
128127
{
129-
if (func_num_args() > 2) {
130-
throw new InvalidArgumentException('You passed too many arguments to the ['.$name.'] slot.');
131-
} elseif (func_num_args() === 2) {
128+
if ($content) {
132129
$this->slots[$this->currentComponent()][$name] = $content;
133130
} elseif (ob_start()) {
134131
$this->slots[$this->currentComponent()][$name] = '';
135132

136-
$this->slotStack[$this->currentComponent()][] = $name;
133+
$this->slotStack[$this->currentComponent()][] = [$name, $attributes];
137134
}
138135
}
139136

@@ -150,7 +147,11 @@ public function endSlot()
150147
$this->slotStack[$this->currentComponent()]
151148
);
152149

153-
$this->slots[$this->currentComponent()][$currentSlot] = new HtmlString(trim(ob_get_clean()));
150+
[$currentName, $currentAttributes] = $currentSlot;
151+
152+
$this->slots[$this->currentComponent()][$currentName] = new ComponentSlot(
153+
trim(ob_get_clean()), $currentAttributes
154+
);
154155
}
155156

156157
/**

src/Illuminate/View/DynamicComponent.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ protected function compileBindings(array $bindings)
120120
protected function compileSlots(array $slots)
121121
{
122122
return collect($slots)->map(function ($slot, $name) {
123-
return $name === '__default' ? null : '<x-slot name="'.$name.'">{{ $'.$name.' }}</x-slot>';
123+
return $name === '__default' ? null : '<x-slot name="'.$name.'" '.((string) $slot->attributes).'>{{ $'.$name.' }}</x-slot>';
124124
})->filter()->implode(PHP_EOL);
125125
}
126126

tests/View/Blade/BladeComponentTagCompilerTest.php

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,31 @@ public function testSlotsCanBeCompiled()
2323
$result = $this->compiler()->compileSlots('<x-slot name="foo">
2424
</x-slot>');
2525

26-
$this->assertSame("@slot('foo') \n".' @endslot', trim($result));
26+
$this->assertSame("@slot('foo', null, []) \n".' @endslot', trim($result));
2727
}
2828

2929
public function testDynamicSlotsCanBeCompiled()
3030
{
3131
$result = $this->compiler()->compileSlots('<x-slot :name="$foo">
3232
</x-slot>');
3333

34-
$this->assertSame("@slot(\$foo) \n".' @endslot', trim($result));
34+
$this->assertSame("@slot(\$foo, null, []) \n".' @endslot', trim($result));
35+
}
36+
37+
public function testSlotsWithAttributesCanBeCompiled()
38+
{
39+
$result = $this->compiler()->compileSlots('<x-slot name="foo" class="font-bold">
40+
</x-slot>');
41+
42+
$this->assertSame("@slot('foo', null, ['class' => 'font-bold']) \n".' @endslot', trim($result));
43+
}
44+
45+
public function testSlotsWithDynamicAttributesCanBeCompiled()
46+
{
47+
$result = $this->compiler()->compileSlots('<x-slot name="foo" :class="$classes">
48+
</x-slot>');
49+
50+
$this->assertSame("@slot('foo', null, ['class' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\$classes)]) \n".' @endslot', trim($result));
3551
}
3652

3753
public function testBasicComponentParsing()

tests/View/Blade/BladeComponentsTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public function testEndComponentClassesAreCompiled()
4444

4545
public function testSlotsAreCompiled()
4646
{
47-
$this->assertSame('<?php $__env->slot(\'foo\', ["foo" => "bar"]); ?>', $this->compiler->compileString('@slot(\'foo\', ["foo" => "bar"])'));
47+
$this->assertSame('<?php $__env->slot(\'foo\', null, ["foo" => "bar"]); ?>', $this->compiler->compileString('@slot(\'foo\', null, ["foo" => "bar"])'));
4848
$this->assertSame('<?php $__env->slot(\'foo\'); ?>', $this->compiler->compileString('@slot(\'foo\')'));
4949
}
5050

tests/View/ViewFactoryTest.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,7 @@ public function testComponentHandling()
355355
$factory->getDispatcher()->shouldReceive('dispatch');
356356
$factory->startComponent('component', ['name' => 'Taylor']);
357357
$factory->slot('title');
358-
$factory->slot('website', 'laravel.com');
358+
$factory->slot('website', 'laravel.com', []);
359359
echo 'title<hr>';
360360
$factory->endSlot();
361361
echo 'component';
@@ -371,7 +371,7 @@ public function testComponentHandlingUsingViewObject()
371371
$factory->getDispatcher()->shouldReceive('dispatch');
372372
$factory->startComponent($factory->make('component'), ['name' => 'Taylor']);
373373
$factory->slot('title');
374-
$factory->slot('website', 'laravel.com');
374+
$factory->slot('website', 'laravel.com', []);
375375
echo 'title<hr>';
376376
$factory->endSlot();
377377
echo 'component';
@@ -392,7 +392,7 @@ public function testComponentHandlingUsingClosure()
392392
return $factory->make('component');
393393
}, ['name' => 'Taylor']);
394394
$factory->slot('title');
395-
$factory->slot('website', 'laravel.com');
395+
$factory->slot('website', 'laravel.com', []);
396396
echo 'title<hr>';
397397
$factory->endSlot();
398398
echo 'component';

0 commit comments

Comments
 (0)