diff --git a/src/Illuminate/Collections/Arr.php b/src/Illuminate/Collections/Arr.php
index 222478cefcc0..1b38fc52e971 100644
--- a/src/Illuminate/Collections/Arr.php
+++ b/src/Illuminate/Collections/Arr.php
@@ -795,6 +795,29 @@ public static function toCssClasses($array)
return implode(' ', $classes);
}
+ /**
+ * Conditionally compile styles from an array into a style list.
+ *
+ * @param array $array
+ * @return string
+ */
+ public static function toCssStyles($array)
+ {
+ $styleList = static::wrap($array);
+
+ $styles = [];
+
+ foreach ($styleList as $class => $constraint) {
+ if (is_numeric($class)) {
+ $styles[] = Str::finish($constraint, ';');
+ } elseif ($constraint) {
+ $styles[] = Str::finish($class, ';');
+ }
+ }
+
+ return implode(' ', $styles);
+ }
+
/**
* Filter the array using the given callback.
*
diff --git a/src/Illuminate/View/Compilers/BladeCompiler.php b/src/Illuminate/View/Compilers/BladeCompiler.php
index d061732d1ffc..b20e7c4924b8 100644
--- a/src/Illuminate/View/Compilers/BladeCompiler.php
+++ b/src/Illuminate/View/Compilers/BladeCompiler.php
@@ -31,6 +31,7 @@ class BladeCompiler extends Compiler implements CompilerInterface
Concerns\CompilesLoops,
Concerns\CompilesRawPhp,
Concerns\CompilesStacks,
+ Concerns\CompilesStyles,
Concerns\CompilesTranslations,
ReflectsClosures;
diff --git a/src/Illuminate/View/Compilers/ComponentTagCompiler.php b/src/Illuminate/View/Compilers/ComponentTagCompiler.php
index 8fee31dd29a4..cf42383629ff 100644
--- a/src/Illuminate/View/Compilers/ComponentTagCompiler.php
+++ b/src/Illuminate/View/Compilers/ComponentTagCompiler.php
@@ -115,6 +115,10 @@ protected function compileOpeningTags(string $value)
@(?:class)(\( (?: (?>[^()]+) | (?-1) )* \))
)
|
+ (?:
+ @(?:style)(\( (?: (?>[^()]+) | (?-1) )* \))
+ )
+ |
(?:
\{\{\s*\\\$attributes(?:[^}]+?)?\s*\}\}
)
@@ -176,6 +180,10 @@ protected function compileSelfClosingTags(string $value)
@(?:class)(\( (?: (?>[^()]+) | (?-1) )* \))
)
|
+ (?:
+ @(?:style)(\( (?: (?>[^()]+) | (?-1) )* \))
+ )
+ |
(?:
\{\{\s*\\\$attributes(?:[^}]+?)?\s*\}\}
)
@@ -506,6 +514,10 @@ public function compileSlots(string $value)
@(?:class)(\( (?: (?>[^()]+) | (?-1) )* \))
)
|
+ (?:
+ @(?:style)(\( (?: (?>[^()]+) | (?-1) )* \))
+ )
+ |
(?:
\{\{\s*\\\$attributes(?:[^}]+?)?\s*\}\}
)
@@ -563,6 +575,7 @@ protected function getAttributesFromAttributeString(string $attributeString)
$attributeString = $this->parseShortAttributeSyntax($attributeString);
$attributeString = $this->parseAttributeBag($attributeString);
$attributeString = $this->parseComponentTagClassStatements($attributeString);
+ $attributeString = $this->parseComponentTagStyleStatements($attributeString);
$attributeString = $this->parseBindAttributes($attributeString);
$pattern = '/
@@ -665,6 +678,27 @@ protected function parseComponentTagClassStatements(string $attributeString)
);
}
+ /**
+ * Parse @style statements in a given attribute string into their fully-qualified syntax.
+ *
+ * @param string $attributeString
+ * @return string
+ */
+ protected function parseComponentTagStyleStatements(string $attributeString)
+ {
+ return preg_replace_callback(
+ '/@(style)(\( ( (?>[^()]+) | (?2) )* \))/x', function ($match) {
+ if ($match[1] === 'style') {
+ $match[2] = str_replace('"', "'", $match[2]);
+
+ return ":style=\"\Illuminate\Support\Arr::toCssStyles{$match[2]}\"";
+ }
+
+ return $match[0];
+ }, $attributeString
+ );
+ }
+
/**
* Parse the "bind" attributes in a given attribute string into their fully-qualified syntax.
*
diff --git a/src/Illuminate/View/Compilers/Concerns/CompilesStyles.php b/src/Illuminate/View/Compilers/Concerns/CompilesStyles.php
new file mode 100644
index 000000000000..6c715061ca4a
--- /dev/null
+++ b/src/Illuminate/View/Compilers/Concerns/CompilesStyles.php
@@ -0,0 +1,19 @@
+\"";
+ }
+}
diff --git a/src/Illuminate/View/ComponentAttributeBag.php b/src/Illuminate/View/ComponentAttributeBag.php
index 0c6c615f36c2..acf5c3fbe9f5 100644
--- a/src/Illuminate/View/ComponentAttributeBag.php
+++ b/src/Illuminate/View/ComponentAttributeBag.php
@@ -221,6 +221,19 @@ public function class($classList)
return $this->merge(['class' => Arr::toCssClasses($classList)]);
}
+ /**
+ * Conditionally merge styles into the attribute bag.
+ *
+ * @param mixed|array $styleList
+ * @return static
+ */
+ public function style($styleList)
+ {
+ $styleList = Arr::wrap($styleList);
+
+ return $this->merge(['style' => Arr::toCssStyles($styleList)]);
+ }
+
/**
* Merge additional attributes / values into the attribute bag.
*
@@ -238,9 +251,10 @@ public function merge(array $attributeDefaults = [], $escape = true)
[$appendableAttributes, $nonAppendableAttributes] = collect($this->attributes)
->partition(function ($value, $key) use ($attributeDefaults) {
- return $key === 'class' ||
- (isset($attributeDefaults[$key]) &&
- $attributeDefaults[$key] instanceof AppendableAttributeValue);
+ return $key === 'class' || $key === 'style' || (
+ isset($attributeDefaults[$key]) &&
+ $attributeDefaults[$key] instanceof AppendableAttributeValue
+ );
});
$attributes = $appendableAttributes->mapWithKeys(function ($value, $key) use ($attributeDefaults, $escape) {
@@ -248,6 +262,10 @@ public function merge(array $attributeDefaults = [], $escape = true)
? $this->resolveAppendableAttributeDefault($attributeDefaults, $key, $escape)
: ($attributeDefaults[$key] ?? '');
+ if ($key === 'style') {
+ $value = Str::finish($value, ';');
+ }
+
return [$key => implode(' ', array_unique(array_filter([$defaultsValue, $value])))];
})->merge($nonAppendableAttributes)->all();
diff --git a/tests/Support/SupportArrTest.php b/tests/Support/SupportArrTest.php
index e5ce328e0cae..47aae98576b6 100644
--- a/tests/Support/SupportArrTest.php
+++ b/tests/Support/SupportArrTest.php
@@ -946,6 +946,25 @@ public function testToCssClasses()
$this->assertSame('font-bold mt-4 ml-2', $classes);
}
+ public function testToCssStyles()
+ {
+ $styles = Arr::toCssStyles([
+ 'font-weight: bold',
+ 'margin-top: 4px;',
+ ]);
+
+ $this->assertSame('font-weight: bold; margin-top: 4px;', $styles);
+
+ $styles = Arr::toCssStyles([
+ 'font-weight: bold;',
+ 'margin-top: 4px',
+ 'margin-left: 2px;' => true,
+ 'margin-right: 2px' => false,
+ ]);
+
+ $this->assertSame('font-weight: bold; margin-top: 4px; margin-left: 2px;', $styles);
+ }
+
public function testWhere()
{
$array = [100, '200', 300, '400', 500];
diff --git a/tests/View/Blade/BladeComponentTagCompilerTest.php b/tests/View/Blade/BladeComponentTagCompilerTest.php
index 74226a488efb..01e6161af3fd 100644
--- a/tests/View/Blade/BladeComponentTagCompilerTest.php
+++ b/tests/View/Blade/BladeComponentTagCompilerTest.php
@@ -87,6 +87,15 @@ public function testSlotsWithClassDirectiveCanBeCompiled()
$this->assertSame("@slot('foo', null, ['class' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\Illuminate\Support\Arr::toCssClasses(\$classes))]) \n".' @endslot', trim($result));
}
+ public function testSlotsWithStyleDirectiveCanBeCompiled()
+ {
+ $this->mockViewFactory();
+ $result = $this->compiler()->compileSlots('
+');
+
+ $this->assertSame("@slot('foo', null, ['style' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\Illuminate\Support\Arr::toCssStyles(\$styles))]) \n".' @endslot', trim($result));
+ }
+
public function testBasicComponentParsing()
{
$this->mockViewFactory();
@@ -271,6 +280,18 @@ public function testClassDirective()
withAttributes(['class' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\Illuminate\Support\Arr::toCssClasses(['bar'=>true]))]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result));
}
+ public function testStyleDirective()
+ {
+ $this->mockViewFactory();
+ $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags('true])>');
+
+ $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', [])
+getConstructor()): ?>
+except(collect(\$constructor->getParameters())->map->getName()->all()); ?>
+
+withAttributes(['style' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\Illuminate\Support\Arr::toCssStyles(['bar'=>true]))]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result));
+ }
+
public function testColonNestedComponentParsing()
{
$this->mockViewFactory();
diff --git a/tests/View/Blade/BladeStyleTest.php b/tests/View/Blade/BladeStyleTest.php
new file mode 100644
index 000000000000..01e8c2eb14df
--- /dev/null
+++ b/tests/View/Blade/BladeStyleTest.php
@@ -0,0 +1,14 @@
+ true, 'margin-top: 10px' => false])>";
+ $expected = " true, 'margin-top: 10px' => false]) ?>\">";
+
+ $this->assertEquals($expected, $this->compiler->compileString($string));
+ }
+}
diff --git a/tests/View/ViewComponentAttributeBagTest.php b/tests/View/ViewComponentAttributeBagTest.php
index 453014e09c84..66ce23e75866 100644
--- a/tests/View/ViewComponentAttributeBagTest.php
+++ b/tests/View/ViewComponentAttributeBagTest.php
@@ -31,6 +31,14 @@ public function testAttributeRetrieval()
$this->assertSame('class="mt-4 font-bold" name="test"', (string) $bag->class(['mt-4']));
$this->assertSame('class="mt-4 ml-2 font-bold" name="test"', (string) $bag->class(['mt-4', 'ml-2' => true, 'mr-2' => false]));
+ $bag = new ComponentAttributeBag(['class' => 'font-bold', 'name' => 'test', 'style' => 'margin-top: 10px']);
+ $this->assertSame('class="mt-4 ml-2 font-bold" style="margin-top: 10px;" name="test"', (string) $bag->class(['mt-4', 'ml-2' => true, 'mr-2' => false]));
+ $this->assertSame('style="margin-top: 4px; margin-left: 10px; margin-top: 10px;" class="font-bold" name="test"', (string) $bag->style(['margin-top: 4px', 'margin-left: 10px;']));
+
+ $bag = new ComponentAttributeBag(['class' => 'font-bold', 'name' => 'test', 'style' => 'margin-top: 10px; font-weight: bold']);
+ $this->assertSame('class="mt-4 ml-2 font-bold" style="margin-top: 10px; font-weight: bold;" name="test"', (string) $bag->class(['mt-4', 'ml-2' => true, 'mr-2' => false]));
+ $this->assertSame('style="margin-top: 4px; margin-left: 10px; margin-top: 10px; font-weight: bold;" class="font-bold" name="test"', (string) $bag->style(['margin-top: 4px', 'margin-left: 10px;']));
+
$bag = new ComponentAttributeBag([]);
$this->assertSame('class="mt-4"', (string) $bag->merge(['class' => 'mt-4']));