diff --git a/composer.json b/composer.json index 82c11a8a5..8c5d87dfa 100644 --- a/composer.json +++ b/composer.json @@ -34,7 +34,7 @@ "symfony/uid": "^7.1", "symfony/var-dumper": "^7.1", "symfony/var-exporter": "^7.1", - "tempest/highlight": "^2.11.2", + "tempest/highlight": "^2.11.4", "vlucas/phpdotenv": "^5.6", "voku/portable-ascii": "^2.0.3" }, diff --git a/src/Tempest/View/src/Components/DynamicViewComponent.php b/src/Tempest/View/src/Components/DynamicViewComponent.php index 78b0f22c8..1c4c583df 100644 --- a/src/Tempest/View/src/Components/DynamicViewComponent.php +++ b/src/Tempest/View/src/Components/DynamicViewComponent.php @@ -2,26 +2,23 @@ namespace Tempest\View\Components; -use Tempest\Container\Container; +use Stringable; use Tempest\Core\AppConfig; -use Tempest\View\Elements\ElementFactory; +use Tempest\Support\Str\ImmutableString; +use Tempest\View\Elements\CollectionElement; use Tempest\View\Elements\ViewComponentElement; -use Tempest\View\GenericView; use Tempest\View\Parser\TempestViewCompiler; use Tempest\View\Parser\Token; use Tempest\View\ViewComponent; use Tempest\View\ViewConfig; -use Tempest\View\ViewRenderer; + +use function Tempest\Support\arr; final class DynamicViewComponent implements ViewComponent { private Token $token; - public function __construct( - private AppConfig $appConfig, - private TempestViewCompiler $compiler, - private ViewConfig $viewConfig, - ) {} + public function __construct() {} public function setToken(Token $token): void { @@ -36,27 +33,46 @@ public static function getName(): string public function compile(ViewComponentElement $element): string { $name = $this->token->getAttribute('is') ?? $this->token->getAttribute(':is'); + $isExpression = $this->token->getAttribute(':is') !== null; - return sprintf( - '\' . \Tempest\get(%s::class)->render(%s, %s)); ?>', - self::class, - $isExpression ? $name : "'{$name}'", - var_export($element->getAttributes(), true), // @mago-expect best-practices/no-debug-symbols - ); - } + $collectionElement = new CollectionElement($element->getChildren()); - public function render(string $name, array $attributes): string - { - $viewComponent = $this->viewConfig->viewComponents[$name] ?? null; + $attributes = arr($element->getAttributes()) + ->filter(fn (string $_value, string $key) => $key !== 'is' && $key !== ':is') + ->map(function (string $value, string $key) { + return sprintf('%s="%s"', $key, trim($value)); + }) + ->implode(' ') + ->when( + fn (Stringable $string) => ((string) $string) !== '', + fn (Stringable $string) => new ImmutableString(" {$string}"), + ); - $element = new ViewComponentElement( - environment: $this->appConfig->environment, - compiler: $this->compiler, - viewComponent: $viewComponent, - attributes: $attributes, + $compiledChildren = sprintf( + <<<'HTML' + <%s%s> + %s + + HTML, + '%s', + $attributes, + $collectionElement->compile(), + '%s', ); - return $element->compile(); + return sprintf( + 'render(\Tempest\view(sprintf(<<<\'HTML\' +%s +HTML, %s, %s), ...$vars)); ?> +', + $compiledChildren, + $isExpression ? $name : "'{$name}'", + $isExpression ? $name : "'{$name}'", + ); } } diff --git a/src/Tempest/View/src/Parser/TempestViewCompiler.php b/src/Tempest/View/src/Parser/TempestViewCompiler.php index ed2fd5b33..4c4ea5ae8 100644 --- a/src/Tempest/View/src/Parser/TempestViewCompiler.php +++ b/src/Tempest/View/src/Parser/TempestViewCompiler.php @@ -9,8 +9,10 @@ use Tempest\Mapper\Exceptions\ViewNotFound; use Tempest\View\Attribute; use Tempest\View\Attributes\AttributeFactory; +use Tempest\View\Components\DynamicViewComponent; use Tempest\View\Element; use Tempest\View\Elements\ElementFactory; +use Tempest\View\Elements\ViewComponentElement; use Tempest\View\View; use function Tempest\Support\arr; @@ -130,13 +132,20 @@ private function applyAttributes(array $elements): array $previous = null; foreach ($elements as $element) { - $children = $this->applyAttributes($element->getChildren()); + $isDynamicViewComponent = $element instanceof ViewComponentElement && $element->getViewComponent() instanceof DynamicViewComponent; - $element - ->setPrevious($previous) - ->setChildren($children); + if (! $isDynamicViewComponent) { + $children = $this->applyAttributes($element->getChildren()); + $element->setChildren($children); + } + + $element->setPrevious($previous); foreach ($element->getAttributes() as $name => $value) { + if ($isDynamicViewComponent && $name !== ':is' && $name !== 'is') { + continue; + } + // TODO: possibly refactor attribute construction to ElementFactory? if ($value instanceof Attribute) { $attribute = $value; diff --git a/src/Tempest/View/src/Renderers/TempestViewRenderer.php b/src/Tempest/View/src/Renderers/TempestViewRenderer.php index f3aec1002..349bd8b1c 100644 --- a/src/Tempest/View/src/Renderers/TempestViewRenderer.php +++ b/src/Tempest/View/src/Renderers/TempestViewRenderer.php @@ -17,9 +17,6 @@ use Tempest\View\ViewRenderer; use Throwable; -use function Tempest\Support\arr; -use function Tempest\Support\str; - final class TempestViewRenderer implements ViewRenderer { private ?View $currentView = null; diff --git a/src/Tempest/Vite/src/TagsResolver/ManifestTagsResolver.php b/src/Tempest/Vite/src/TagsResolver/ManifestTagsResolver.php index 563ac1f86..5a9ea872c 100644 --- a/src/Tempest/Vite/src/TagsResolver/ManifestTagsResolver.php +++ b/src/Tempest/Vite/src/TagsResolver/ManifestTagsResolver.php @@ -225,6 +225,7 @@ function loadNext(assets, count) { setTimeout(() => loadNext({$assets}, {$this->viteConfig->prefetching->concurrent})) }) JS, + PrefetchStrategy::NONE => '', }; return $this->tagCompiler->compilePrefetchTag($script, $chunk); diff --git a/tests/Integration/View/ViewComponentTest.php b/tests/Integration/View/ViewComponentTest.php index d770395fc..518eed1a2 100644 --- a/tests/Integration/View/ViewComponentTest.php +++ b/tests/Integration/View/ViewComponentTest.php @@ -825,6 +825,17 @@ public function test_dynamic_view_component_with_variable_attribute(): void $this->assertSame('
test
', $html); } + public function test_dynamic_view_component_with_slot(): void + { + $this->registerViewComponent('x-test', '
'); + + $html = $this->render(<<<'HTML' + test + HTML, name: 'x-test'); + + $this->assertSnippetsMatch('
test
', $html); + } + public function test_nested_slots(): void { $this->registerViewComponent('x-a', '');