From 89c0f396ca39e79869ac9960c80a37b2caca904b Mon Sep 17 00:00:00 2001 From: Marco Rieser Date: Sun, 28 Jun 2026 13:52:30 +0200 Subject: [PATCH] Fix kebab-cased props not binding on Blade components in Antlers --- src/Tags/ComponentProxy.php | 17 ++++-- .../Components/BladeComponentsTest.php | 53 +++++++++++++++++++ .../Antlers/Fixtures/Components/KebabProp.php | 19 +++++++ .../views/components/kebab_prop.blade.php | 2 + .../components/kebab_prop_class.antlers.html | 1 + 5 files changed, 89 insertions(+), 3 deletions(-) create mode 100644 tests/Antlers/Fixtures/Components/KebabProp.php create mode 100644 tests/__fixtures__/views/components/kebab_prop.blade.php create mode 100644 tests/__fixtures__/views/components/kebab_prop_class.antlers.html diff --git a/src/Tags/ComponentProxy.php b/src/Tags/ComponentProxy.php index 4152a4b735b..5a18593cc87 100644 --- a/src/Tags/ComponentProxy.php +++ b/src/Tags/ComponentProxy.php @@ -2,6 +2,7 @@ namespace Statamic\Tags; +use Illuminate\Support\Str; use Illuminate\View\AnonymousComponent; use Illuminate\View\Compilers\BladeCompiler; use Illuminate\View\Compilers\ComponentTagCompiler; @@ -51,12 +52,22 @@ public function index() } if ($constructor = (new ReflectionClass($className))->getConstructor()) { - $constructorParameters = collect($constructor->getParameters())->map->getName()->all(); - $attributes = $attributes->except($constructorParameters); - $constructorParameters = collect($scopeData)->only($constructorParameters)->all(); + $parameterNames = collect($constructor->getParameters())->map->getName()->all(); + + // Kebab-cased attributes (e.g. :some-prop) should bind to camelCase + // constructor parameters ($someProp), mirroring Laravel's native behavior. + $attributes = $attributes->filter(fn ($value, $key) => ! in_array(Str::camel($key), $parameterNames)); + + $constructorParameters = collect($scopeData) + ->mapWithKeys(fn ($value, $key) => [Str::camel($key) => $value]) + ->only($parameterNames) + ->all(); } if ($isAnonymous) { + // Camel-case data keys so kebab-cased attributes resolve to the + // component's @props (e.g. :some-prop -> $someProp), as Laravel does. + $data = collect($data)->mapWithKeys(fn ($value, $key) => [Str::camel($key) => $value])->all(); $constructorParameters = array_merge($constructorParameters, $data, ['view' => $anonymousViewName, 'data' => $data]); } diff --git a/tests/Antlers/Components/BladeComponentsTest.php b/tests/Antlers/Components/BladeComponentsTest.php index 1cf233752f0..d376dcb40d8 100644 --- a/tests/Antlers/Components/BladeComponentsTest.php +++ b/tests/Antlers/Components/BladeComponentsTest.php @@ -5,6 +5,7 @@ use Illuminate\Support\Facades\Blade; use Statamic\View\Antlers\Language\Utilities\StringUtilities; use Tests\Antlers\Fixtures\Components\Card; +use Tests\Antlers\Fixtures\Components\KebabProp; use Tests\Antlers\ParserTestCase; class BladeComponentsTest extends ParserTestCase @@ -126,6 +127,58 @@ public function test_components_blade_compatibility() ); } + public function test_kebab_cased_attributes_bind_props_on_anonymous_blade_components() + { + $template = <<<'EOT' +{{ foo = 'Test' }} +EOT; + + $this->assertSame( + '
', + trim($this->renderString($template)) + ); + } + + public function test_kebab_cased_attributes_bind_props_on_class_blade_components() + { + Blade::component(KebabProp::class); + + $template = <<<'EOT' +{{ foo = 'Test' }} +EOT; + + $this->assertSame( + 'Test', + trim($this->renderString($template)) + ); + } + + public function test_camel_cased_attributes_still_bind_props_on_anonymous_blade_components() + { + $template = <<<'EOT' +{{ foo = 'Test' }} +EOT; + + $this->assertSame( + '
', + trim($this->renderString($template)) + ); + } + + public function test_camel_cased_attributes_still_bind_props_on_class_blade_components() + { + Blade::component(KebabProp::class); + + $template = <<<'EOT' +{{ foo = 'Test' }} +EOT; + + $this->assertSame( + 'Test', + trim($this->renderString($template)) + ); + } + public function test_method_calls_across_lines_with_attributes() { $template = <<<'EOT' diff --git a/tests/Antlers/Fixtures/Components/KebabProp.php b/tests/Antlers/Fixtures/Components/KebabProp.php new file mode 100644 index 00000000000..e86e1ebaa1a --- /dev/null +++ b/tests/Antlers/Fixtures/Components/KebabProp.php @@ -0,0 +1,19 @@ + null]) +
diff --git a/tests/__fixtures__/views/components/kebab_prop_class.antlers.html b/tests/__fixtures__/views/components/kebab_prop_class.antlers.html new file mode 100644 index 00000000000..c3bee206a20 --- /dev/null +++ b/tests/__fixtures__/views/components/kebab_prop_class.antlers.html @@ -0,0 +1 @@ +{{ someProp }} \ No newline at end of file