diff --git a/src/Tempest/View/src/Components/DynamicViewComponent.php b/src/Tempest/View/src/Components/DynamicViewComponent.php index 9580be615..78b0f22c8 100644 --- a/src/Tempest/View/src/Components/DynamicViewComponent.php +++ b/src/Tempest/View/src/Components/DynamicViewComponent.php @@ -20,7 +20,6 @@ final class DynamicViewComponent implements ViewComponent public function __construct( private AppConfig $appConfig, private TempestViewCompiler $compiler, - private ElementFactory $elementFactory, private ViewConfig $viewConfig, ) {} @@ -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, ); diff --git a/src/Tempest/View/src/Elements/ElementFactory.php b/src/Tempest/View/src/Elements/ElementFactory.php index 26eaef507..88068fbb5 100644 --- a/src/Tempest/View/src/Elements/ElementFactory.php +++ b/src/Tempest/View/src/Elements/ElementFactory.php @@ -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, ); diff --git a/src/Tempest/View/src/Elements/ViewComponentElement.php b/src/Tempest/View/src/Elements/ViewComponentElement.php index 78bc9800b..83b0b4406 100644 --- a/src/Tempest/View/src/Elements/ViewComponentElement.php +++ b/src/Tempest/View/src/Elements/ViewComponentElement.php @@ -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; @@ -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, ) { @@ -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: '/^<(?[\w-]+)(.*?["\s])?>/', // Match the very first opening tag, this will never fail. replace: function ($matches) { - $closingTag = ''; - - $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 '', diff --git a/tests/Integration/View/ViewComponentTest.php b/tests/Integration/View/ViewComponentTest.php index e29401189..ddb75b267 100644 --- a/tests/Integration/View/ViewComponentTest.php +++ b/tests/Integration/View/ViewComponentTest.php @@ -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' +
+ HTML); + + $html = $this->render(<<<'HTML' + + HTML); + + $this->assertStringEqualsStringIgnoringLineEndings(<<<'HTML' +
+ HTML, $html); + } + public function test_file_name_component(): void { $html = $this->render(''); @@ -810,6 +825,20 @@ public function test_dynamic_view_component_with_variable_attribute(): void $this->assertSame('
test
', $html); } + public function test_nested_slots(): void + { + $this->registerViewComponent('x-a', ''); + $this->registerViewComponent('x-b', ''); + + $html = $this->render(<<<'HTML' + + hi + + HTML); + + $this->assertSnippetsMatch('hi', $html); + } + private function assertSnippetsMatch(string $expected, string $actual): void { $expected = str_replace([PHP_EOL, ' '], '', $expected);