diff --git a/src/Runtime/BlazeAttributeBag.php b/src/Runtime/BlazeAttributeBag.php index fb7c0fe..641650b 100644 --- a/src/Runtime/BlazeAttributeBag.php +++ b/src/Runtime/BlazeAttributeBag.php @@ -49,6 +49,11 @@ public static function make(array $attributes, array $boundKeys = [], array $ori /** {@inheritdoc} */ public function merge(array $attributeDefaults = [], $escape = true): static { + // Fast path: merge(['class' => 'string']) — the most common pattern. + if (count($attributeDefaults) === 1 && isset($attributeDefaults['class']) && is_string($attributeDefaults['class'])) { + return $this->withMergedClass($escape ? e($attributeDefaults['class']) : $attributeDefaults['class']); + } + if ($escape) { foreach ($attributeDefaults as $key => $value) { if ($this->shouldEscapeAttributeValue($escape, $value)) { @@ -103,6 +108,10 @@ public function merge(array $attributeDefaults = [], $escape = true): static /** {@inheritdoc} */ public function class($classList): static { + if (is_string($classList)) { + return $this->withMergedClass(e($classList)); + } + $classes = $this->toCssClasses(Arr::wrap($classList)); return $this->merge(['class' => $classes]); @@ -111,11 +120,45 @@ public function class($classList): static /** {@inheritdoc} */ public function style($styleList): static { + if (is_string($styleList)) { + $default = e(rtrim($styleList, ';').';'); + $current = $this->attributes['style'] ?? ''; + + if ($current) { + $current = rtrim((string) $current, ';').';'; + $style = $current === $default ? $default : $default.' '.$current; + } else { + $style = $default; + } + + return new static(['style' => $style] + $this->attributes); + } + $styles = $this->toCssStyles((array) $styleList); return $this->merge(['style' => $styles]); } + /** + * Return a new bag with the given class default merged into the current class attribute. + */ + protected function withMergedClass(string $default): static + { + $current = $this->attributes['class'] ?? ''; + + if (! $current || $current === $default) { + $class = $default; + } elseif (! $default) { + $class = $current ?: ''; + } else { + $class = $default.' '.$current; + } + + // Array union places class first, matching merge()'s + // array_merge($attributeDefaults, $attributes) key ordering. + return new static(['class' => $class] + $this->attributes); + } + /** * Convert class list to CSS classes string. */ diff --git a/tests/ComparisonTest.php b/tests/ComparisonTest.php index 165b09c..14f776a 100644 --- a/tests/ComparisonTest.php +++ b/tests/ComparisonTest.php @@ -95,6 +95,11 @@ BLADE )); +test('merge preserves attribute ordering', fn () => compare(<<<'BLADE' + + BLADE +)); + test('deeply nested same component with different components interleaved', fn () => compare(<<<'BLADE' diff --git a/tests/fixtures/views/components/merge-class.blade.php b/tests/fixtures/views/components/merge-class.blade.php new file mode 100644 index 0000000..dba7365 --- /dev/null +++ b/tests/fixtures/views/components/merge-class.blade.php @@ -0,0 +1,3 @@ +@blaze + +
merge(['class' => 'default-class']) }}>
\ No newline at end of file