diff --git a/src/Support/AttributeParser.php b/src/Support/AttributeParser.php
index af172a66..7fbd8a0a 100644
--- a/src/Support/AttributeParser.php
+++ b/src/Support/AttributeParser.php
@@ -15,23 +15,14 @@ public function parseAndReplaceDynamics(
return $attributesString;
}
- // :name="..." or :name=$var
- $attributesString = preg_replace_callback('/(\s*):([a-zA-Z0-9_-]+)\s*=\s*("[^"]*"|\$[a-zA-Z0-9_]+)/', function ($matches) use (&$attributePlaceholders, &$attributeNameToPlaceholder) {
+ // :name="..."
+ $attributesString = preg_replace_callback('/(?camel()->toString();
- $attributeValue = trim($match[2][0], '"');
- $original = trim($match[0][0]);
+ // Handle :name="..." syntax
+ preg_match_all('/(?:^|\s):([A-Za-z0-9_:-]+)\s*=\s*"([^"]*)"/', $attributesString, $matches, PREG_SET_ORDER);
+ foreach ($matches as $m) {
+ $attributeName = str($m[1])->camel()->toString();
+ if (isset($attributes[$attributeName])) continue;
$attributes[$attributeName] = [
'isDynamic' => true,
- 'value' => $attributeValue,
- 'original' => $original,
+ 'value' => $m[2],
+ 'original' => trim($m[0]),
];
}
// Handle short :$var syntax (expands to :var="$var")
- preg_match_all('/(?:^|\s):\$([a-zA-Z0-9_:-]+)(?=\s|$)/', $attributesString, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE);
- foreach ($matches as $match) {
- $start = $match[0][1];
- $end = $start + strlen($match[0][0]);
-
- // Skip if this position was already processed
- $skip = false;
- foreach ($processedPositions as [$procStart, $procEnd]) {
- if ($start >= $procStart && $end <= $procEnd) {
- $skip = true;
- break;
- }
- }
- if ($skip) continue;
-
- $processedPositions[] = [$start, $end];
-
- $attributeName = str($match[1][0])->camel()->toString();
- $attributeValue = '$' . $match[1][0];
- $original = trim($match[0][0]);
+ preg_match_all('/(?:^|\s):\$([A-Za-z0-9_:-]+)(?=\s|$)/', $attributesString, $matches, PREG_SET_ORDER);
+ foreach ($matches as $m) {
+ $raw = $m[1];
+ $attributeName = str($raw)->camel()->toString();
+ if (isset($attributes[$attributeName])) continue;
$attributes[$attributeName] = [
'isDynamic' => true,
- 'value' => $attributeValue,
- 'original' => $original,
+ 'value' => '$' . $raw,
+ 'original' => trim($m[0]),
];
}
// Handle regular name="value" syntax
- preg_match_all('/(\s*)([a-zA-Z0-9_:-]+)\s*=\s*("[^"]*")/', $attributesString, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE);
- foreach ($matches as $match) {
- $start = $match[0][1];
- $end = $start + strlen($match[0][0]);
-
- // Skip if this position was already processed
- $skip = false;
- foreach ($processedPositions as [$procStart, $procEnd]) {
- if ($start >= $procStart && $end <= $procEnd) {
- $skip = true;
- break;
- }
- }
- if ($skip) continue;
-
- $processedPositions[] = [$start, $end];
-
- $attributeName = str($match[2][0])->camel()->toString();
- $attributeValue = trim($match[3][0], '"');
- $original = trim($match[0][0]);
+ preg_match_all('/(?:^|\s)(?!:)([A-Za-z0-9_:-]+)\s*=\s*"([^"]*)"/', $attributesString, $matches, PREG_SET_ORDER);
+ foreach ($matches as $m) {
+ $attributeName = str($m[1])->camel()->toString();
+ if (isset($attributes[$attributeName])) continue;
$attributes[$attributeName] = [
'isDynamic' => false,
- 'value' => $attributeValue,
- 'original' => $original,
+ 'value' => $m[2],
+ 'original' => trim($m[0]),
];
}
// Handle boolean attributes (single words without values)
- preg_match_all('/(\s*)([a-zA-Z0-9_:-]+)(?=\s|$)/', $attributesString, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE);
- foreach ($matches as $match) {
- $start = $match[0][1];
- $end = $start + strlen($match[0][0]);
-
- // Skip if this position was already processed
- $skip = false;
- foreach ($processedPositions as [$procStart, $procEnd]) {
- if ($start >= $procStart && $end <= $procEnd) {
- $skip = true;
- break;
- }
- }
- if ($skip) continue;
-
- $attributeName = str($match[2][0])->camel()->toString();
- $original = trim($match[0][0]);
-
- // Only add if not already processed as a key-value pair
- if (!array_key_exists($attributeName, $attributes)) {
- $attributes[$attributeName] = [
- 'isDynamic' => false,
- 'value' => true,
- 'original' => $original,
- ];
- }
+ preg_match_all('/(?:^|\s)([A-Za-z0-9_:-]+)(?=\s|$)/', $attributesString, $matches, PREG_SET_ORDER);
+ foreach ($matches as $m) {
+ $attributeName = str($m[1])->camel()->toString();
+ if (isset($attributes[$attributeName])) continue;
+
+ $attributes[$attributeName] = [
+ 'isDynamic' => false,
+ 'value' => true,
+ 'original' => trim($m[0]),
+ ];
}
return $attributes;
diff --git a/tests/AttributeParserTest.php b/tests/AttributeParserTest.php
index 1942a42b..c4d430d7 100644
--- a/tests/AttributeParserTest.php
+++ b/tests/AttributeParserTest.php
@@ -3,6 +3,100 @@
use Livewire\Blaze\Support\AttributeParser;
describe('parse attributes', function () {
+ it('parses and replaces dynamic attributes with name and value syntax', function() {
+ $input = ':name="$foo"';
+ $output = 'name="ATTR_PLACEHOLDER_0"';
+
+ $attributePlaceholders = [];
+ $attributeNameToPlaceholder = [];
+
+ $result = (new AttributeParser)->parseAndReplaceDynamics($input, $attributePlaceholders, $attributeNameToPlaceholder);
+
+ expect($result)->toBe($output);
+
+ expect($attributePlaceholders)->toBe([
+ 'ATTR_PLACEHOLDER_0' => '{{ $foo }}',
+ ]);
+
+ expect($attributeNameToPlaceholder)->toBe([
+ 'name' => 'ATTR_PLACEHOLDER_0',
+ ]);
+ });
+
+ it('parses and replaces dynamic attributes with short syntax', function() {
+ $input = ':$name';
+ $output = 'name="ATTR_PLACEHOLDER_0"';
+
+ $attributePlaceholders = [];
+ $attributeNameToPlaceholder = [];
+
+ $result = (new AttributeParser)->parseAndReplaceDynamics($input, $attributePlaceholders, $attributeNameToPlaceholder);
+
+ expect($result)->toBe($output);
+
+ expect($attributePlaceholders)->toBe([
+ 'ATTR_PLACEHOLDER_0' => '{{ $name }}',
+ ]);
+
+ expect($attributeNameToPlaceholder)->toBe([
+ 'name' => 'ATTR_PLACEHOLDER_0',
+ ]);
+ });
+
+ it('parses and replaces dynamic attributes echoed within a value', function() {
+ $input = 'name="foo {{ $type }}"';
+ $output = 'name="foo ATTR_PLACEHOLDER_0"';
+
+ $attributePlaceholders = [];
+ $attributeNameToPlaceholder = [];
+
+ $result = (new AttributeParser)->parseAndReplaceDynamics($input, $attributePlaceholders, $attributeNameToPlaceholder);
+
+ expect($result)->toBe($output);
+
+ expect($attributePlaceholders)->toBe([
+ 'ATTR_PLACEHOLDER_0' => '{{ $type }}',
+ ]);
+
+ expect($attributeNameToPlaceholder)->toBe([]);
+ });
+
+ it('does not parse static attributes with colon in the name when used alone', function() {
+ $input = 'icon:trailing="chevrons-up-down"';
+ $output = 'icon:trailing="chevrons-up-down"';
+
+ $attributePlaceholders = [];
+ $attributeNameToPlaceholder = [];
+
+ $result = (new AttributeParser)->parseAndReplaceDynamics($input, $attributePlaceholders, $attributeNameToPlaceholder);
+
+ expect($result)->toBe($output);
+
+ expect($attributePlaceholders)->toBe([]);
+
+ expect($attributeNameToPlaceholder)->toBe([]);
+ });
+
+ it('does not parse static attributes with colon in the name when used with dynamic attributes', function() {
+ $input = ':name="$foo" icon:trailing="chevrons-up-down"';
+ $output = 'name="ATTR_PLACEHOLDER_0" icon:trailing="chevrons-up-down"';
+
+ $attributePlaceholders = [];
+ $attributeNameToPlaceholder = [];
+
+ $result = (new AttributeParser)->parseAndReplaceDynamics($input, $attributePlaceholders, $attributeNameToPlaceholder);
+
+ expect($result)->toBe($output);
+
+ expect($attributePlaceholders)->toBe([
+ 'ATTR_PLACEHOLDER_0' => '{{ $foo }}',
+ ]);
+
+ expect($attributeNameToPlaceholder)->toBe([
+ 'name' => 'ATTR_PLACEHOLDER_0',
+ ]);
+ });
+
it('parses static attributes', function () {
$input = 'name="Bob" searchable="true"';
$output = [
@@ -144,8 +238,13 @@
});
it('parses static attributes which contain colons', function () {
- $input = 'wire:sort:item="{{ $id }}"';
+ $input = 'icon:trailing="chevrons-up-down" wire:sort:item="{{ $id }}"';
$output = [
+ 'icon:trailing' => [
+ 'isDynamic' => false,
+ 'value' => 'chevrons-up-down',
+ 'original' => 'icon:trailing="chevrons-up-down"',
+ ],
'wire:sort:item' => [
'isDynamic' => false,
'value' => '{{ $id }}',
diff --git a/tests/FluxTest.php b/tests/FluxTest.php
index a205bc0c..b8e19a2b 100644
--- a/tests/FluxTest.php
+++ b/tests/FluxTest.php
@@ -1,5 +1,7 @@
toContain('data-flux-heading');
expect($output)->not->toContain('flux:heading');
});
+
+ it('folds link component with dynamic route helper link', function() {
+ Route::get('/dashboard', fn() => 'dashboard')->name('dashboard');
+
+ $input = 'Dashboard';
+ $output = app('blaze')->compile($input);
+
+ expect($output)
+ ->toContain('toContain(' href="{{ route(\'dashboard\') }}"');
+ });
});