Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions src/Tempest/View/src/Components/DynamicViewComponent.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ final class DynamicViewComponent implements ViewComponent
public function __construct(
private AppConfig $appConfig,
private TempestViewCompiler $compiler,
private ElementFactory $elementFactory,
private ViewConfig $viewConfig,
) {}

Expand Down Expand Up @@ -54,7 +53,6 @@ public function render(string $name, array $attributes): string
$element = new ViewComponentElement(
environment: $this->appConfig->environment,
compiler: $this->compiler,
elementFactory: $this->elementFactory,
viewComponent: $viewComponent,
attributes: $attributes,
);
Expand Down
1 change: 0 additions & 1 deletion src/Tempest/View/src/Elements/ElementFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,6 @@ private function makeElement(Token $token, ?Element $parent): ?Element
$element = new ViewComponentElement(
environment: $this->appConfig->environment,
compiler: $this->compiler,
elementFactory: $this,
viewComponent: $viewComponentClass,
attributes: $attributes,
);
Expand Down
49 changes: 28 additions & 21 deletions src/Tempest/View/src/Elements/ViewComponentElement.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
namespace Tempest\View\Elements;

use Tempest\Core\Environment;
use Tempest\Support\Str\ImmutableString;
use Tempest\Support\Str\MutableString;
use Tempest\View\Element;
use Tempest\View\Parser\TempestViewCompiler;
use Tempest\View\Parser\TempestViewParser;
Expand All @@ -23,7 +25,6 @@ final class ViewComponentElement implements Element
public function __construct(
private readonly Environment $environment,
private readonly TempestViewCompiler $compiler,
private readonly ElementFactory $elementFactory,
private readonly ViewComponent $viewComponent,
array $attributes,
) {
Expand Down Expand Up @@ -92,43 +93,49 @@ public function compile(): string
->mapWithKeys(fn (SlotElement $element) => yield $element->name => Slot::fromElement($element))
->toArray();

$compiled = str($this->viewComponent->compile($this))
$compiled = str($this->viewComponent->compile($this));

$compiled = $compiled
// Fallthrough attributes
->replaceRegex(
regex: '/^<(?<tag>[\w-]+)(.*?["\s])?>/', // Match the very first opening tag, this will never fail.
replace: function ($matches) {
$closingTag = '</' . $matches['tag'] . '>';

$ast = TempestViewParser::ast($matches[0] . $closingTag);
/** @var \Tempest\View\Parser\Token $token */
$token = TempestViewParser::ast($matches[0])[0];

$element = $this->elementFactory->make($ast[0]);
$attributes = arr($token->htmlAttributes)->map(fn (string $value) => new MutableString($value));

foreach (['class', 'style', 'id'] as $attributeName) {
if (! isset($this->dataAttributes[$attributeName])) {
continue;
}

$attributes[$attributeName] ??= new MutableString();

if ($attributeName === 'id') {
$value = $this->dataAttributes[$attributeName];
$attributes[$attributeName] = new MutableString(' ' . $this->dataAttributes[$attributeName]);
} else {
$value = arr([
$element->getAttribute($attributeName),
$this->dataAttributes[$attributeName],
])
->filter()
->implode(' ')
->toString();
$attributes[$attributeName]->append(' ' . $this->dataAttributes[$attributeName]);
}

$element->setAttribute(
name: $attributeName,
value: $value,
);
}

return str($element->compile())->replaceLast($closingTag, '');
return sprintf(
'<%s%s>',
$matches['tag'],
$attributes
->map(function (MutableString $value, string $key) {
return sprintf('%s="%s"', $key, $value->trim());
})
->implode(' ')
->when(
fn (ImmutableString $string) => $string->isNotEmpty(),
fn (ImmutableString $string) => $string->prepend(' '),
),
);
},
)
);

$compiled = $compiled
->prepend(
// Add attributes to the current scope
'<?php $_previousAttributes = $attributes ?? null; ?>',
Expand Down
29 changes: 29 additions & 0 deletions tests/Integration/View/ViewComponentTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,21 @@ public function test_merged_fallthrough_attributes(): void
HTML, $html);
}

public function test_fallthrough_attributes_with_other_attributes(): void
{
$this->registerViewComponent('x-test', <<<'HTML'
<div class="foo" style="font-weight: bold;" id="other"></div>
HTML);

$html = $this->render(<<<'HTML'
<x-test class="test" style="text-decoration: underline;" id="test"></x-test>
HTML);

$this->assertStringEqualsStringIgnoringLineEndings(<<<'HTML'
<div class="foo test" style="font-weight: bold; text-decoration: underline;" id="test"></div>
HTML, $html);
}

public function test_file_name_component(): void
{
$html = $this->render('<x-file-component></x-file-component>');
Expand Down Expand Up @@ -810,6 +825,20 @@ public function test_dynamic_view_component_with_variable_attribute(): void
$this->assertSame('<div>test</div>', $html);
}

public function test_nested_slots(): void
{
$this->registerViewComponent('x-a', '<a><x-slot /></a>');
$this->registerViewComponent('x-b', '<x-a><b><x-slot /></b></x-a>');

$html = $this->render(<<<'HTML'
<x-b>
hi
</x-b>
HTML);

$this->assertSnippetsMatch('<a><b>hi</b></a>', $html);
}

private function assertSnippetsMatch(string $expected, string $actual): void
{
$expected = str_replace([PHP_EOL, ' '], '', $expected);
Expand Down
Loading