Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[8.x] Blade component slot attributes #38372

Merged
merged 12 commits into from
Aug 18, 2021
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, $escapeBound = false).']) ';
danharrin marked this conversation as resolved.
Show resolved Hide resolved
}, $value);

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

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

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

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

/**
* 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 HTML string.
*
* @return string
*/
public function __toString()
{
return $this->toHtml();
}
}
17 changes: 8 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 @@ -119,21 +119,18 @@ protected function componentData()
* Start the slot rendering process.
*
* @param string $name
* @param array $attributes
* @param string|null $content
* @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,9 @@ public function endSlot()
$this->slotStack[$this->currentComponent()]
);

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

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

/**
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' => \$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