Skip to content

Commit

Permalink
[8.x] Blade component slot attributes (laravel#38372)
Browse files Browse the repository at this point in the history
* 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 <taylorotwell@gmail.com>
  • Loading branch information
2 people authored and victorvilella committed Oct 12, 2021
1 parent 84761da commit b9c06d9
Show file tree
Hide file tree
Showing 7 changed files with 163 additions and 18 deletions.
43 changes: 41 additions & 2 deletions src/Illuminate/View/Compilers/ComponentTagCompiler.php
Original file line number Diff line number Diff line change
Expand Up @@ -395,14 +395,53 @@ protected function compileClosingTags(string $value)
*/
public function compileSlots(string $value)
{
$value = preg_replace_callback('/<\s*x[\-\:]slot\s+(:?)name=(?<name>(\"[^\"]+\"|\\\'[^\\\']+\\\'|[^\s>]+))\s*>/', function ($matches) {
$pattern = "/
<
\s*
x[\-\:]slot
\s+
(:?)name=(?<name>(\"[^\"]+\"|\\\'[^\\\']+\\\'|[^\s>]+))
(?<attributes>
(?:
\s+
(?:
(?:
\{\{\s*\\\$attributes(?:[^}]+?)?\s*\}\}
)
|
(?:
[\w\-:.@]+
(
=
(?:
\\\"[^\\\"]*\\\"
|
\'[^\']*\'
|
[^\'\\\"=<>]+
)
)?
)
)
)*
\s*
)
(?<![\/=\-])
>
/x";

$value = preg_replace_callback($pattern, function ($matches) {
$name = $this->stripQuotes($matches['name']);

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

return " @slot({$name}) ";
$this->boundAttributes = [];

$attributes = $this->getAttributesFromAttributeString($matches['attributes']);

return " @slot({$name}, null, [".$this->attributesToString($attributes).']) ';
}, $value);

return preg_replace('/<\/\s*x[\-\:]slot[^>]*>/', ' @endslot', $value);
Expand Down
89 changes: 89 additions & 0 deletions src/Illuminate/View/ComponentSlot.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<?php

namespace Illuminate\View;

use Illuminate\Contracts\Support\Htmlable;

class ComponentSlot implements Htmlable
{
/**
* The slot attribute bag.
*
* @var \Illuminate\View\ComponentAttributeBag
*/
public $attributes;

/**
* The slot contents.
*
* @var string
*/
protected $contents;

/**
* Create a new slot instance.
*
* @param string $contents
* @param array $attributes
* @return void
*/
public function __construct($contents = '', $attributes = [])
{
$this->contents = $contents;

$this->withAttributes($attributes);
}

/**
* Set the extra attributes that the slot should make available.
*
* @param array $attributes
* @return $this
*/
public function withAttributes(array $attributes)
{
$this->attributes = new ComponentAttributeBag($attributes);

return $this;
}

/**
* Get the slot's HTML string.
*
* @return string
*/
public function toHtml()
{
return $this->contents;
}

/**
* Determine if the slot is empty.
*
* @return bool
*/
public function isEmpty()
{
return $this->contents === '';
}

/**
* Determine if the slot is not empty.
*
* @return bool
*/
public function isNotEmpty()
{
return ! $this->isEmpty();
}

/**
* Get the slot's HTML string.
*
* @return string
*/
public function __toString()
{
return $this->toHtml();
}
}
19 changes: 10 additions & 9 deletions src/Illuminate/View/Concerns/ManagesComponents.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
use Illuminate\Contracts\View\View;
use Illuminate\Support\Arr;
use Illuminate\Support\HtmlString;
use InvalidArgumentException;
use Illuminate\View\ComponentSlot;

trait ManagesComponents
{
Expand Down Expand Up @@ -120,20 +120,17 @@ protected function componentData()
*
* @param string $name
* @param string|null $content
* @param array $attributes
* @return void
*
* @throws \InvalidArgumentException
*/
public function slot($name, $content = null)
public function slot($name, $content = null, $attributes = [])
{
if (func_num_args() > 2) {
throw new InvalidArgumentException('You passed too many arguments to the ['.$name.'] slot.');
} elseif (func_num_args() === 2) {
if ($content) {
$this->slots[$this->currentComponent()][$name] = $content;
} elseif (ob_start()) {
$this->slots[$this->currentComponent()][$name] = '';

$this->slotStack[$this->currentComponent()][] = $name;
$this->slotStack[$this->currentComponent()][] = [$name, $attributes];
}
}

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

$this->slots[$this->currentComponent()][$currentSlot] = new HtmlString(trim(ob_get_clean()));
[$currentName, $currentAttributes] = $currentSlot;

$this->slots[$this->currentComponent()][$currentName] = new ComponentSlot(
trim(ob_get_clean()), $currentAttributes
);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/Illuminate/View/DynamicComponent.php
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ protected function compileBindings(array $bindings)
protected function compileSlots(array $slots)
{
return collect($slots)->map(function ($slot, $name) {
return $name === '__default' ? null : '<x-slot name="'.$name.'">{{ $'.$name.' }}</x-slot>';
return $name === '__default' ? null : '<x-slot name="'.$name.'" '.((string) $slot->attributes).'>{{ $'.$name.' }}</x-slot>';
})->filter()->implode(PHP_EOL);
}

Expand Down
20 changes: 18 additions & 2 deletions tests/View/Blade/BladeComponentTagCompilerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,31 @@ public function testSlotsCanBeCompiled()
$result = $this->compiler()->compileSlots('<x-slot name="foo">
</x-slot>');

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

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

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

public function testSlotsWithAttributesCanBeCompiled()
{
$result = $this->compiler()->compileSlots('<x-slot name="foo" class="font-bold">
</x-slot>');

$this->assertSame("@slot('foo', null, ['class' => 'font-bold']) \n".' @endslot', trim($result));
}

public function testSlotsWithDynamicAttributesCanBeCompiled()
{
$result = $this->compiler()->compileSlots('<x-slot name="foo" :class="$classes">
</x-slot>');

$this->assertSame("@slot('foo', null, ['class' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\$classes)]) \n".' @endslot', trim($result));
}

public function testBasicComponentParsing()
Expand Down
2 changes: 1 addition & 1 deletion tests/View/Blade/BladeComponentsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public function testEndComponentClassesAreCompiled()

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

Expand Down
6 changes: 3 additions & 3 deletions tests/View/ViewFactoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,7 @@ public function testComponentHandling()
$factory->getDispatcher()->shouldReceive('dispatch');
$factory->startComponent('component', ['name' => 'Taylor']);
$factory->slot('title');
$factory->slot('website', 'laravel.com');
$factory->slot('website', 'laravel.com', []);
echo 'title<hr>';
$factory->endSlot();
echo 'component';
Expand All @@ -371,7 +371,7 @@ public function testComponentHandlingUsingViewObject()
$factory->getDispatcher()->shouldReceive('dispatch');
$factory->startComponent($factory->make('component'), ['name' => 'Taylor']);
$factory->slot('title');
$factory->slot('website', 'laravel.com');
$factory->slot('website', 'laravel.com', []);
echo 'title<hr>';
$factory->endSlot();
echo 'component';
Expand All @@ -392,7 +392,7 @@ public function testComponentHandlingUsingClosure()
return $factory->make('component');
}, ['name' => 'Taylor']);
$factory->slot('title');
$factory->slot('website', 'laravel.com');
$factory->slot('website', 'laravel.com', []);
echo 'title<hr>';
$factory->endSlot();
echo 'component';
Expand Down

0 comments on commit b9c06d9

Please sign in to comment.