diff --git a/src/print/directive.ts b/src/print/directive.ts
index 3c382be..eef9b2d 100644
--- a/src/print/directive.ts
+++ b/src/print/directive.ts
@@ -1,7 +1,7 @@
import type { AstPath, Doc, Options } from "prettier";
import { doc } from "prettier";
import type { WrappedNode } from "../types.js";
-import { NodeKind } from "../tree/types.js";
+import { NodeKind, StructureRole } from "../tree/types.js";
import { TokenType } from "../lexer/types.js";
import {
formatDirectiveNameToken,
@@ -43,15 +43,6 @@ function isBranchNode(node: WrappedNode): node is WrappedNode & { kind: BranchNo
return node.kind === NodeKind.Directive || node.kind === NodeKind.PhpTag;
}
-const BODY_BLANK_LINE_LAYOUT_DIRECTIVES = new Set([
- "include",
- "includeif",
- "includewhen",
- "includeunless",
- "includefirst",
- "each",
-]);
-
function isIgnoreRangeNode(node: WrappedNode): boolean {
return node.kind === NodeKind.IgnoreRange;
}
@@ -757,24 +748,21 @@ function printBetweenLine(prev: WrappedNode, next: WrappedNode): Doc {
}
function shouldPreserveBodyBlankLine(prev: WrappedNode, next: WrappedNode): boolean {
- // Nested branch markers such as @default/@else inside directive bodies already
- // have dedicated separator handling. Preserving source blank lines here causes
- // pass-2 duplication when an outer mode like `always` inserts its own branch gap.
- if (isBranchNode(next) && next.children.length > 0) {
+ // Branch markers such as @else/@elseif/@case/@default already have dedicated
+ // separator handling. Preserving source blank lines here causes pass-2
+ // duplication when an outer mode like `always` inserts its own branch gap.
+ if (isBranchBoundaryDirective(prev) || isBranchBoundaryDirective(next)) {
return false;
}
- // Keep preserve-mode scope narrow: retain authored gaps only for layout-like
- // boundaries such as `@include` before a container component. This matches
- // the expected section-body use case without changing general directive-body
- // spacing across the validation corpus.
- return (
- (isBodyLayoutDirective(prev) && isContainerLikeBodySibling(next)) ||
- (isBodyLayoutDirective(next) && isContainerLikeBodySibling(prev))
- );
+ if (isMeaningfulBodySibling(prev) && isMeaningfulBodySibling(next)) {
+ return true;
+ }
+
+ return false;
}
-function isBodyLayoutDirective(node: WrappedNode): boolean {
+function isBranchBoundaryDirective(node: WrappedNode): boolean {
if (node.kind !== NodeKind.Directive) {
return false;
}
@@ -784,21 +772,24 @@ function isBodyLayoutDirective(node: WrappedNode): boolean {
return false;
}
- return BODY_BLANK_LINE_LAYOUT_DIRECTIVES.has(name);
-}
-
-function isContainerLikeBodySibling(node: WrappedNode): boolean {
- if (isTextLikeNode(node)) {
+ const directive = node.buildResult.directives?.getDirective(name);
+ if (!directive) {
return false;
}
- if (node.kind === NodeKind.Element) {
- return node.children.length > 0 && isComponentLikeElement(node);
- }
-
- return node.children.length > 0;
+ return (
+ directive.role === StructureRole.Mixed ||
+ directive.role === StructureRole.Closing ||
+ directive.isSwitchBranch ||
+ directive.isSwitchTerminator ||
+ directive.isConditionalClose
+ );
}
-function isComponentLikeElement(node: WrappedNode): boolean {
- return node.tagName.includes("-") || node.fullName.includes(":");
+function isMeaningfulBodySibling(node: WrappedNode): boolean {
+ if (node.kind === NodeKind.NonOutput) {
+ return false;
+ }
+
+ return !(node.kind === NodeKind.Text && fullText(node).trim().length === 0);
}
diff --git a/tests/directives/body-blank-lines.test.ts b/tests/directives/body-blank-lines.test.ts
new file mode 100644
index 0000000..0ba45a5
--- /dev/null
+++ b/tests/directives/body-blank-lines.test.ts
@@ -0,0 +1,385 @@
+import { describe, it } from "vitest";
+import bladePlugin from "../../src/index.js";
+import * as phpPlugin from "@prettier/plugin-php";
+import { formatEqual } from "../helpers.js";
+
+const WITH_PHP = {
+ plugins: [bladePlugin, phpPlugin],
+ bladePhpFormatting: "safe" as const,
+ singleQuote: true,
+};
+
+describe("directives/body-blank-lines", () => {
+ it.each([
+ {
+ name: "php block then directive",
+ input: [
+ "@foreach ($items as $item)",
+ "@php",
+ "$x = 1;",
+ "@endphp",
+ "",
+ "@livewire($x)",
+ "@endforeach",
+ "",
+ ].join("\n"),
+ expected: [
+ "@foreach ($items as $item)",
+ " @php",
+ " $x = 1;",
+ " @endphp",
+ "",
+ " @livewire ($x)",
+ "@endforeach",
+ "",
+ ].join("\n"),
+ },
+ {
+ name: "verbatim block then directive",
+ input: [
+ "@foreach ($items as $item)",
+ "@verbatim",
+ "{{ raw }}",
+ "@endverbatim",
+ "",
+ "@livewire($item)",
+ "@endforeach",
+ "",
+ ].join("\n"),
+ expected: [
+ "@foreach ($items as $item)",
+ " @verbatim",
+ "{{ raw }}",
+ "@endverbatim",
+ "",
+ " @livewire ($item)",
+ "@endforeach",
+ "",
+ ].join("\n"),
+ },
+ {
+ name: "nested directive block then directive",
+ input: [
+ "@foreach ($items as $item)",
+ "@if ($item->visible)",
+ "{{ $item->label }} ",
+ "@endif",
+ "",
+ "@livewire($item)",
+ "@endforeach",
+ "",
+ ].join("\n"),
+ expected: [
+ "@foreach ($items as $item)",
+ " @if ($item->visible)",
+ " {{ $item->label }} ",
+ " @endif",
+ "",
+ " @livewire ($item)",
+ "@endforeach",
+ "",
+ ].join("\n"),
+ },
+ {
+ name: "directive then nested directive block",
+ input: [
+ "@foreach ($items as $item)",
+ "@livewire($item)",
+ "",
+ "@if ($item->visible)",
+ "{{ $item->label }} ",
+ "@endif",
+ "@endforeach",
+ "",
+ ].join("\n"),
+ expected: [
+ "@foreach ($items as $item)",
+ " @livewire ($item)",
+ "",
+ " @if ($item->visible)",
+ " {{ $item->label }} ",
+ " @endif",
+ "@endforeach",
+ "",
+ ].join("\n"),
+ },
+ {
+ name: "element then directive",
+ input: [
+ "@foreach ($items as $item)",
+ "
{{ $item->label }}
",
+ "",
+ "@livewire($item)",
+ "@endforeach",
+ "",
+ ].join("\n"),
+ expected: [
+ "@foreach ($items as $item)",
+ " {{ $item->label }}
",
+ "",
+ " @livewire ($item)",
+ "@endforeach",
+ "",
+ ].join("\n"),
+ },
+ {
+ name: "echo then directive",
+ input: [
+ "@foreach ($items as $item)",
+ "{{ $item->label }}",
+ "",
+ "@livewire($item)",
+ "@endforeach",
+ "",
+ ].join("\n"),
+ expected: [
+ "@foreach ($items as $item)",
+ " {{ $item->label }}",
+ "",
+ " @livewire ($item)",
+ "@endforeach",
+ "",
+ ].join("\n"),
+ },
+ {
+ name: "directive then php block",
+ input: [
+ "@foreach ($items as $item)",
+ "@livewire($item)",
+ "",
+ "@php",
+ "$x = 1;",
+ "@endphp",
+ "@endforeach",
+ "",
+ ].join("\n"),
+ expected: [
+ "@foreach ($items as $item)",
+ " @livewire ($item)",
+ "",
+ " @php",
+ " $x = 1;",
+ " @endphp",
+ "@endforeach",
+ "",
+ ].join("\n"),
+ },
+ ])(
+ "preserves authored blank lines between mixed body siblings: $name",
+ async ({ input, expected }) => {
+ await formatEqual(input, expected, WITH_PHP, 3);
+ },
+ );
+
+ it.each([
+ {
+ name: "section",
+ input: [
+ "@section('content')",
+ " ",
+ "",
+ "@php",
+ "$title = $post->title;",
+ "@endphp",
+ "@endsection",
+ "",
+ ].join("\n"),
+ expected: [
+ "@section ('content')",
+ " ",
+ "",
+ " @php",
+ " $title = $post->title;",
+ " @endphp",
+ "@endsection",
+ "",
+ ].join("\n"),
+ },
+ {
+ name: "component",
+ input: [
+ "@component('mail::message')",
+ "@php",
+ "$total = $order->total();",
+ "@endphp",
+ "",
+ ' ',
+ "@endcomponent",
+ "",
+ ].join("\n"),
+ expected: [
+ "@component ('mail::message')",
+ " @php",
+ " $total = $order->total();",
+ " @endphp",
+ "",
+ ' ',
+ "@endcomponent",
+ "",
+ ].join("\n"),
+ },
+ {
+ name: "for",
+ input: [
+ "@for ($i = 0; $i < 2; $i++)",
+ "@php",
+ "$value = $items[$i];",
+ "@endphp",
+ "",
+ ' ',
+ "@endfor",
+ "",
+ ].join("\n"),
+ expected: [
+ "@for ($i = 0; $i < 2; $i++)",
+ " @php",
+ " $value = $items[$i];",
+ " @endphp",
+ "",
+ ' ',
+ "@endfor",
+ "",
+ ].join("\n"),
+ },
+ {
+ name: "forelse primary branch",
+ input: [
+ "@forelse ($items as $item)",
+ ' ',
+ "",
+ "@include('partials.row-actions')",
+ "@empty",
+ "No items.
",
+ "@endforelse",
+ "",
+ ].join("\n"),
+ expected: [
+ "@forelse ($items as $item)",
+ ' ',
+ "",
+ " @include ('partials.row-actions')",
+ "@empty",
+ " No items.
",
+ "@endforelse",
+ "",
+ ].join("\n"),
+ },
+ {
+ name: "push",
+ input: [
+ "@push('scripts')",
+ "@vite('resources/js/app.js')",
+ "",
+ "",
+ "@endpush",
+ "",
+ ].join("\n"),
+ expected: [
+ "@push ('scripts')",
+ " @vite ('resources/js/app.js')",
+ "",
+ " ",
+ "@endpush",
+ "",
+ ].join("\n"),
+ },
+ {
+ name: "fragment",
+ input: [
+ "@fragment('modal')",
+ " ",
+ "",
+ "@livewire('settings.modal')",
+ "@endfragment",
+ "",
+ ].join("\n"),
+ expected: [
+ "@fragment ('modal')",
+ " ",
+ "",
+ " @livewire ('settings.modal')",
+ "@endfragment",
+ "",
+ ].join("\n"),
+ },
+ {
+ name: "switch case",
+ input: [
+ "@switch($type)",
+ "@case('a')",
+ "@php",
+ "$label = $type;",
+ "@endphp",
+ "",
+ "{{ $label }}
",
+ "@break",
+ "@default",
+ "Unknown
",
+ "@endswitch",
+ "",
+ ].join("\n"),
+ expected: [
+ "@switch ($type)",
+ " @case ('a')",
+ " @php",
+ " $label = $type;",
+ " @endphp",
+ "",
+ " {{ $label }}
",
+ " @break",
+ " @default",
+ " Unknown
",
+ "@endswitch",
+ "",
+ ].join("\n"),
+ },
+ ])(
+ "preserves authored blank lines inside representative block directive bodies: $name",
+ async ({ input, expected }) => {
+ await formatEqual(input, expected, WITH_PHP, 3);
+ },
+ );
+
+ it("does not duplicate branch separator blank lines while preserving body sibling gaps", async () => {
+ const input = [
+ "@if($ready)",
+ "@php",
+ "$state = 'ready';",
+ "@endphp",
+ "",
+ "@else",
+ "Waiting
",
+ "",
+ "@endif",
+ "",
+ ].join("\n");
+
+ const expected = [
+ "@if ($ready)",
+ " @php",
+ " $state = 'ready';",
+ " @endphp",
+ "",
+ "@else",
+ " Waiting
",
+ "",
+ "@endif",
+ "",
+ ].join("\n");
+
+ await formatEqual(
+ input,
+ expected,
+ {
+ ...WITH_PHP,
+ bladeBlankLinesAroundDirectives: "always",
+ bladeDirectiveBlockStyle: "multiline",
+ },
+ 3,
+ );
+ });
+});
diff --git a/tests/directives/issue-cases.test.ts b/tests/directives/issue-cases.test.ts
index d526d80..72cef97 100644
--- a/tests/directives/issue-cases.test.ts
+++ b/tests/directives/issue-cases.test.ts
@@ -235,6 +235,38 @@ describe("directives/issue-cases", () => {
});
});
+ // Issue #180: preserve authored body gap after @php before another directive.
+ it("#180: @php block preserves following blank line inside control directive", async () => {
+ const input = [
+ "@foreach ($widgets as $widget)",
+ " @php",
+ " $widgetClass = $normalizeWidgetClass($widget);",
+ " @endphp",
+ "",
+ " @livewire($widgetClass, ['widget' => $widget])",
+ "@endforeach",
+ "",
+ ].join("\n");
+
+ const expected = [
+ "@foreach ($widgets as $widget)",
+ " @php",
+ " $widgetClass = $normalizeWidgetClass($widget);",
+ " @endphp",
+ "",
+ " @livewire($widgetClass, ['widget' => $widget])",
+ "@endforeach",
+ "",
+ ].join("\n");
+
+ await formatEqual(input, expected, {
+ plugins: [bladePlugin, phpPlugin],
+ bladePhpFormatting: "safe",
+ bladeDirectiveArgSpacing: "preserve",
+ singleQuote: true,
+ });
+ });
+
// Issue #119: custom component with @class does not error
it("#119: x-component with @class in attributes does not error", async () => {
const input = [
@@ -330,6 +362,7 @@ describe("directives/issue-cases", () => {
" @if ($form->picture !== '')",
' ',
" @endif",
+ "",
" @if ($form->picture !== '')",
' ',
" @endif",
@@ -464,6 +497,7 @@ describe("directives/issue-cases", () => {
" @endempty",
" ",
" ",
+ "",
' ',
'
',
" @empty ($key)",
diff --git a/tests/validation/conformance/__snapshots__/formatter-cases.test.ts.snap b/tests/validation/conformance/__snapshots__/formatter-cases.test.ts.snap
index 37a9eae..7267655 100644
--- a/tests/validation/conformance/__snapshots__/formatter-cases.test.ts.snap
+++ b/tests/validation/conformance/__snapshots__/formatter-cases.test.ts.snap
@@ -608,7 +608,9 @@ Placeholder text.
href="coffee.htm"
/>
+
+
Something.
@@ -878,11 +880,13 @@ exports[`validation/conformance/formatter-cases > formats directives__009 1`] =
@pair
Test one.
+
@pair
@pair
Test two.
+
@pair
@@ -2101,6 +2105,7 @@ exports[`validation/conformance/formatter-cases > formats ignore__001 1`] = `
@unless ($true)
World
+
test
@@ -2280,6 +2285,7 @@ exports[`validation/conformance/formatter-cases > formats ignore__002 1`] = `
@unless ($true)
World
+
test
@@ -3103,6 +3109,7 @@ exports[`validation/conformance/formatter-cases > formats switch__001 1`] = `
exports[`validation/conformance/formatter-cases > formats switch__002 1`] = `
"@switch ($i)
{{-- Leading node test. --}}
+
Test {{ $title }}
@case (1)
First case...
@@ -3120,6 +3127,7 @@ exports[`validation/conformance/formatter-cases > formats switch__002 1`] = `
exports[`validation/conformance/formatter-cases > formats switch__003 1`] = `
"@switch ($i)
{{-- Leading node test. --}}
+
Test {{ $title }}
@case (1)
First case...
@@ -3177,6 +3185,7 @@ exports[`validation/conformance/formatter-cases > formats switch__007 1`] = `
exports[`validation/conformance/formatter-cases > formats switch__008 1`] = `
"@switch ($i)
{{-- Leading node test. --}}
+
Test {{ $title }}
@case (1)
First case...
@@ -3278,10 +3287,12 @@ exports[`validation/conformance/formatter-cases > formats templates__002 1`] = `
@section ('content')
About Us
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis convallis
mauris mauris, id volutpat diam feugiat nec.
+
@include ('partials.team')
@endsection
"
@@ -3294,6 +3305,7 @@ exports[`validation/conformance/formatter-cases > formats templates__003 1`] = `
@section ('content')
User List
+
@if ($users->isEmpty())
No users found.
@else
@@ -3314,6 +3326,7 @@ exports[`validation/conformance/formatter-cases > formats templates__004 1`] = `
@section ('content')
Contact Us
+
+
@php
$this->hasActionsModalRendered = true;
@endphp
@@ -275,6 +278,7 @@ exports[`validation/filament-fixtures > formats forms_resources_views_components
$reorderActionIsVisible = $isReorderableWithDragAndDrop && $reorderAction->isVisible();
$hasItemHeader = $hasBlockHeaders && ($reorderActionIsVisible || $moveUpActionIsVisible || $moveDownActionIsVisible || $hasBlockIcons || $hasBlockLabels || $editActionIsVisible || $cloneActionIsVisible || $deleteActionIsVisible || $isCollapsible || $visibleExtraItemActions);
@endphp
+
formats forms_resources_views_components
@if ($moveUpActionIsVisible || $moveDownActionIsVisible)
{{ $moveUpAction }}
+
{{ $moveDownAction }}
@endif
@@ -394,6 +399,7 @@ exports[`validation/filament-fixtures > formats forms_resources_views_components
>
{{ $item->getParentComponent()->renderPreview($item->getRawState()) }}
+
@if ($editActionIsVisible && (! $hasInteractiveBlockPreviews))
formats forms_resources_views_components
@endif
+
@if (! $loop->last)
@if ($isAddable && $addBetweenAction(['afterItem' => $itemKey])->isVisible())
@@ -524,6 +531,7 @@ exports[`validation/filament-fixtures > formats forms_resources_views_components
$wireClickAction = "mountAction('{$action->getName()}', {$wireClickActionArguments}, { schemaComponent: '{$key}' })";
@endphp
+
formats forms_resources_views_components
/>
@endif
+
@if ($isBulkToggleable && count($options))
formats forms_resources_views_components
class="fi-fo-date-time-picker-year-input"
/>
+
+
@@ -2155,6 +2169,7 @@ exports[`validation/filament-fixtures > formats forms_resources_views_components
$wireModelAttribute => $statePath,
], escape: false);
@endphp
+
formats forms_resources_views_components
$reorderActionIsVisible = $isReorderableWithDragAndDrop && $reorderAction->isVisible();
$hasItemHeader = $hasItemHeaders && ($reorderActionIsVisible || $moveUpActionIsVisible || $moveDownActionIsVisible || filled($itemLabel) || $cloneActionIsVisible || $deleteActionIsVisible || $isCollapsible || $visibleExtraItemActions);
@endphp
+
formats forms_resources_views_components
@if ($moveUpActionIsVisible || $moveDownActionIsVisible)
{{ $moveUpAction }}
+
{{ $moveDownAction }}
@endif
@@ -2381,6 +2398,7 @@ exports[`validation/filament-fixtures > formats forms_resources_views_components
{{ $item }}
+
@if (! $loop->last)
@if ($isAddable && $addBetweenAction(['afterItem' => $itemKey])->isVisible())
@@ -2489,6 +2507,7 @@ exports[`validation/filament-fixtures > formats forms_resources_views_components
$moveUpActionIsVisible = $isReorderableWithButtons && $moveUpAction->isVisible();
$reorderActionIsVisible = $isReorderableWithDragAndDrop && $reorderAction->isVisible();
@endphp
+
formats forms_resources_views_components
@if ($moveUpActionIsVisible || $moveDownActionIsVisible)
{{ $moveUpAction }}
+
{{ $moveDownAction }}
@endif
@@ -2652,6 +2672,7 @@ exports[`validation/filament-fixtures > formats forms_resources_views_components
$reorderActionIsVisible = $isReorderableWithDragAndDrop && $reorderAction->isVisible();
$itemStatePath = $item->getStatePath();
@endphp
+
formats forms_resources_views_components
@if ($moveUpActionIsVisible || $moveDownActionIsVisible)
{{ $moveUpAction }}
+
{{ $moveDownAction }}
@endif
@@ -2686,6 +2708,7 @@ exports[`validation/filament-fixtures > formats forms_resources_views_components
new \\Exception('Table repeaters must only contain schema components, but [' . $schemaComponent::class . '] was used.'),
);
@endphp
+
@if (count($tableColumns) > $counter)
@if ($schemaComponent instanceof \\Filament\\Forms\\Components\\Hidden)
{{ $schemaComponent }}
@@ -2693,12 +2716,14 @@ exports[`validation/filament-fixtures > formats forms_resources_views_components
@php
$counter++
@endphp
+
@if ($schemaComponent->isVisible())
@php
$schemaComponentStatePath = $schemaComponent->getStatePath();
$currentColumn = $tableColumns[$counter - 1] ?? null;
$columnVerticalAlignment = $currentColumn?->getVerticalAlignment();
@endphp
+
value) : (is_string($columnVerticalAlignment) ? $columnVerticalAlignment : ''),
@@ -2914,6 +2939,7 @@ exports[`validation/filament-fixtures > formats forms_resources_views_components
@php
$blockId = $block::getId();
@endphp
+
formats forms_resources_views_components
$color = $getColor($value);
$icon = $getIcon($value);
@endphp
+
formats forms_resources_views_components
{{ $wireModelAttribute }}="{{ $statePath }}"
{{ $extraInputAttributeBag }}
/>
+
formats forms_resources_views_components
$color = $getColor($value);
$icon = $getIcon($value);
@endphp
+
formats infolists_resources_views_compon
@php
$wireClickAction = $action->getLivewireClickHandler();
@endphp
+
formats notifications_resources_views_da
+
@foreach ($notifications as $notification)
formats notifications_resources_views_da
{{ $this->getNotification($notification)->inline() }}
@endforeach
+
@if ($broadcastChannel = $this->getBroadcastChannel())
@script
@endscript
@endif
+
@if ($isPaginated)
@@ -4514,7 +4547,9 @@ exports[`validation/filament-fixtures > formats panels_resources_views_component
>
@if ($hasTopbar)
{{ \\Filament\\Support\\Facades\\FilamentView::renderHook(\\Filament\\View\\PanelsRenderHook::TOPBAR_BEFORE, scopes: $renderHookScopes) }}
+
@livewire (filament()->getTopbarLivewireComponent())
+
{{ \\Filament\\Support\\Facades\\FilamentView::renderHook(\\Filament\\View\\PanelsRenderHook::TOPBAR_AFTER, scopes: $renderHookScopes) }}
@elseif ($hasNavigation)
formats panels_resources_views_component
x-transition.opacity.300ms
class="fi-sidebar-close-overlay"
>
+
@livewire (filament()->getSidebarLivewireComponent())
@endif
@@ -4752,9 +4788,11 @@ exports[`validation/filament-fixtures > formats panels_resources_views_component
{{ \\Filament\\Support\\Facades\\FilamentView::renderHook(\\Filament\\View\\PanelsRenderHook::PAGE_SUB_NAVIGATION_MOBILE_MENU_BEFORE, scopes: $this->getRenderHookScopes()) }}
+
+
{{ \\Filament\\Support\\Facades\\FilamentView::renderHook(\\Filament\\View\\PanelsRenderHook::PAGE_SUB_NAVIGATION_MOBILE_MENU_AFTER, scopes: $this->getRenderHookScopes()) }}
@@ -4770,6 +4808,7 @@ exports[`validation/filament-fixtures > formats panels_resources_views_component
$breadcrumbs = filament()->hasBreadcrumbs() ? $this->getBreadcrumbs() : [];
$subheading = $this->getSubheading();
@endphp
+
@if (filled($headerActions) || $breadcrumbs || filled($heading) || filled($subheading))
formats panels_resources_views_component
@if ($subNavigation)
@if ($subNavigationPosition === SubNavigationPosition::Start)
{{ \\Filament\\Support\\Facades\\FilamentView::renderHook(\\Filament\\View\\PanelsRenderHook::PAGE_SUB_NAVIGATION_START_BEFORE, scopes: $this->getRenderHookScopes()) }}
+
+
{{ \\Filament\\Support\\Facades\\FilamentView::renderHook(\\Filament\\View\\PanelsRenderHook::PAGE_SUB_NAVIGATION_START_AFTER, scopes: $this->getRenderHookScopes()) }}
@endif
+
@if ($subNavigationPosition === SubNavigationPosition::Top)
{{ \\Filament\\Support\\Facades\\FilamentView::renderHook(\\Filament\\View\\PanelsRenderHook::PAGE_SUB_NAVIGATION_TOP_BEFORE, scopes: $this->getRenderHookScopes()) }}
+
+
{{ \\Filament\\Support\\Facades\\FilamentView::renderHook(\\Filament\\View\\PanelsRenderHook::PAGE_SUB_NAVIGATION_TOP_AFTER, scopes: $this->getRenderHookScopes()) }}
@endif
@endif
@@ -4829,9 +4873,11 @@ exports[`validation/filament-fixtures > formats panels_resources_views_component
@if ($subNavigation && $subNavigationPosition === SubNavigationPosition::End)
{{ \\Filament\\Support\\Facades\\FilamentView::renderHook(\\Filament\\View\\PanelsRenderHook::PAGE_SUB_NAVIGATION_END_BEFORE, scopes: $this->getRenderHookScopes()) }}
+
+
{{ \\Filament\\Support\\Facades\\FilamentView::renderHook(\\Filament\\View\\PanelsRenderHook::PAGE_SUB_NAVIGATION_END_AFTER, scopes: $this->getRenderHookScopes()) }}
@endif
@@ -4997,6 +5043,7 @@ exports[`validation/filament-fixtures > formats panels_resources_views_component
{{ $navigationGroupLabel }}
@endif
+
@foreach ($navigationGroup->getItems() as $navigationItem)
@foreach ([$navigationItem, ...$navigationItem->getChildItems()] as $navigationItemChild)
@@ -5007,6 +5054,7 @@ exports[`validation/filament-fixtures > formats panels_resources_views_component
$navigationItemUrl = $navigationItem->getUrl();
$shouldNavigationItemOpenUrlInNewTab = $navigationItem->shouldOpenUrlInNewTab();
@endphp
+
formats panels_resources_views_component
$navigationGroupLabel = $navigationGroup->getLabel();
$navigationGroupExtraSidebarAttributeBag = $navigationGroup->getExtraSidebarAttributeBag();
@endphp
+
formats panels_resources_views_component
$isNavigationGroupActive = $navigationGroup->isActive();
$navigationGroupIcon = $navigationGroup->getIcon();
@endphp
+
@if ($navigationGroupLabel)
@@ -5095,6 +5145,7 @@ exports[`validation/filament-fixtures > formats panels_resources_views_component
$navigationItemUrl = $navigationItem->getUrl();
$shouldNavigationItemOpenUrlInNewTab = $navigationItem->shouldOpenUrlInNewTab();
@endphp
+
formats panels_resources_views_component
$navigationItemUrl = $navigationItem->getUrl();
$shouldNavigationItemOpenUrlInNewTab = $navigationItem->shouldOpenUrlInNewTab();
@endphp
+
formats panels_resources_views_component
$itemIcon = $itemIsActive ? ($item->getActiveIcon() ?? $item->getIcon()) : $item->getIcon();
$shouldItemOpenUrlInNewTab = $item->shouldOpenUrlInNewTab();
@endphp
+
formats panels_resources_views_component
}
}
@endphp
+
formats panels_resources_views_component
$shouldChildItemOpenUrlInNewTab = $childItem->shouldOpenUrlInNewTab();
$childItemUrl = $childItem->getUrl();
@endphp
+
formats panels_resources_views_component
$tenantName = filament()->getTenantName($tenant);
$tenantUrl = filament()->getUrl($tenant);
@endphp
+
{{ $item->getLabel() }}
+
{{ \\Filament\\Support\\Facades\\FilamentView::renderHook(\\Filament\\View\\PanelsRenderHook::USER_MENU_PROFILE_AFTER) }}
@endif
@@ -5953,7 +6012,9 @@ exports[`validation/filament-fixtures > formats panels_resources_views_component
@foreach ($itemsBeforeThemeSwitcher as $key => $item)
@if ($key === 'profile')
{{ \\Filament\\Support\\Facades\\FilamentView::renderHook(\\Filament\\View\\PanelsRenderHook::USER_MENU_PROFILE_BEFORE) }}
+
{{ $item }}
+
{{ \\Filament\\Support\\Facades\\FilamentView::renderHook(\\Filament\\View\\PanelsRenderHook::USER_MENU_PROFILE_AFTER) }}
@else
{{ $item }}
@@ -5973,7 +6034,9 @@ exports[`validation/filament-fixtures > formats panels_resources_views_component
@foreach ($itemsAfterThemeSwitcher as $key => $item)
@if ($key === 'profile')
{{ \\Filament\\Support\\Facades\\FilamentView::renderHook(\\Filament\\View\\PanelsRenderHook::USER_MENU_PROFILE_BEFORE) }}
+
{{ $item }}
+
{{ \\Filament\\Support\\Facades\\FilamentView::renderHook(\\Filament\\View\\PanelsRenderHook::USER_MENU_PROFILE_AFTER) }}
@else
{{ $item }}
@@ -6074,6 +6137,7 @@ exports[`validation/filament-fixtures > formats panels_resources_views_livewire_
@php
$resultVisibleActions = $result->getVisibleActions();
@endphp
+
formats panels_resources_views_livewire_
x-show="!$store.sidebar.isOpen"
class="fi-topbar-open-sidebar-btn"
/>
+
formats panels_resources_views_livewire_
@if ($hasTenancy && filament()->hasTenantMenu())
@endif
+
@if ($hasNavigation)
@php
$navigation = filament()->getNavigation();
@endphp
+
@foreach ($navigation as $group)
@php
@@ -6474,6 +6541,7 @@ exports[`validation/filament-fixtures > formats panels_resources_views_livewire_
$isGroupActive = $group->isActive();
$groupIcon = $group->getIcon();
@endphp
+
@if ($groupLabel)
formats panels_resources_views_livewire_
$itemIcon = $isItemActive ? ($item->getActiveIcon() ?? $item->getIcon()) : $item->getIcon();
$shouldItemOpenUrlInNewTab = $item->shouldOpenUrlInNewTab();
@endphp
+
formats panels_resources_views_livewire_
$shouldItemOpenUrlInNewTab = $item->shouldOpenUrlInNewTab();
$itemUrl = $item->getUrl();
@endphp
+
formats panels_resources_views_livewire_
'lazy' => filament()->hasLazyLoadedDatabaseNotifications(),
])
@endif
+
@if (filament()->hasUserMenu() && filament()->getUserMenuPosition() === \\Filament\\Enums\\UserMenuPosition::Topbar)
@endif
@@ -6906,6 +6977,7 @@ exports[`validation/filament-fixtures > formats schemas_resources_views_componen
$componentStatePath = $component->getStatePath();
@endphp
+
formats schemas_resources_views_componen
$tabLabel = $tab->getLabel();
$tabVisibilityJs = $getTabVisibilityJs($tab, $index, 'trigger');
@endphp
+
formats schemas_resources_views_componen
$tabKey = $tab->getKey(isAbsolute: false);
$tabLabel = $tab->getLabel();
@endphp
+
formats schemas_resources_views_componen
@php
$tabVisibilityJs = $getTabVisibilityJs($tab);
@endphp
+
@if ($tabVisibilityJs)
{{ $tab }}
@else
@@ -7406,6 +7482,7 @@ exports[`validation/filament-fixtures > formats schemas_resources_views_componen
@php
$activeTab = strval($this->{$livewireProperty});
@endphp
+
formats schemas_resources_views_componen
$tabKey = strval($tabKey);
$tabLabel = $tab->getLabel() ?? $this->generateTabLabel($tabKey);
@endphp
+
formats support_resources_views_componen
])), size: $iconSize ?? \\Filament\\Support\\Enums\\IconSize::Small)
}}
@endif
+
@if ($hasLoadingIndicator)
{{
\\Filament\\Support\\generate_loading_indicator_html((new \\Illuminate\\View\\ComponentAttributeBag([
@@ -8102,6 +8181,7 @@ exports[`validation/filament-fixtures > formats support_resources_views_componen
$deleteButtonLoadingIndicatorTarget = html_entity_decode($deleteButtonWireTarget, ENT_QUOTES);
}
@endphp
+
formats support_resources_views_componen
])), size: $iconSize ?? \\Filament\\Support\\Enums\\IconSize::Small)
}}
@endif
+
@if ($hasLoadingIndicator)
{{
\\Filament\\Support\\generate_loading_indicator_html((new \\Illuminate\\View\\ComponentAttributeBag([
@@ -8175,6 +8256,7 @@ exports[`validation/filament-fixtures > formats support_resources_views_componen
'fi-breadcrumbs-item-separator fi-ltr',
]))
}}
+
{{
generate_icon_html(\\Filament\\Support\\Icons\\Heroicon::ChevronLeft, alias: \\Filament\\Support\\View\\SupportIconAlias::BREADCRUMBS_SEPARATOR_RTL, attributes: (new ComponentAttributeBag)->class([
'fi-breadcrumbs-item-separator fi-rtl',
@@ -8357,6 +8439,7 @@ exports[`validation/filament-fixtures > formats support_resources_views_componen
])), size: $iconSize)
}}
@endif
+
@if ($hasLoadingIndicator)
{{
\\Filament\\Support\\generate_loading_indicator_html((new \\Illuminate\\View\\ComponentAttributeBag([
@@ -8365,6 +8448,7 @@ exports[`validation/filament-fixtures > formats support_resources_views_componen
])), size: $iconSize)
}}
@endif
+
@if ($hasFormProcessingLoadingIndicator)
{{
\\Filament\\Support\\generate_loading_indicator_html((new \\Illuminate\\View\\ComponentAttributeBag([
@@ -8396,6 +8480,7 @@ exports[`validation/filament-fixtures > formats support_resources_views_componen
])), size: $iconSize)
}}
@endif
+
@if ($hasLoadingIndicator)
{{
\\Filament\\Support\\generate_loading_indicator_html((new \\Illuminate\\View\\ComponentAttributeBag([
@@ -8404,6 +8489,7 @@ exports[`validation/filament-fixtures > formats support_resources_views_componen
])), size: $iconSize)
}}
@endif
+
@if ($hasFormProcessingLoadingIndicator)
{{
\\Filament\\Support\\generate_loading_indicator_html((new \\Illuminate\\View\\ComponentAttributeBag([
@@ -9207,6 +9293,7 @@ exports[`validation/filament-fixtures > formats support_resources_views_componen
@if ($inlinePrefix)
wire:loading.delay.{{ config('filament.livewire_loading_delay', 'default') }}.class.remove="ps-3"
@endif
+
wire:target="{{ $loadingIndicatorTarget }}"
@endif
@class ([
@@ -9373,6 +9460,7 @@ exports[`validation/filament-fixtures > formats support_resources_views_componen
])), size: $iconSize)
}}
@endif
+
@if ($hasLoadingIndicator)
{{
\\Filament\\Support\\generate_loading_indicator_html((new \\Illuminate\\View\\ComponentAttributeBag([
@@ -9396,6 +9484,7 @@ exports[`validation/filament-fixtures > formats support_resources_views_componen
])), size: $iconSize)
}}
@endif
+
@if ($hasLoadingIndicator)
{{
\\Filament\\Support\\generate_loading_indicator_html((new \\Illuminate\\View\\ComponentAttributeBag([
@@ -9528,6 +9617,7 @@ exports[`validation/filament-fixtures > formats support_resources_views_componen
@if ($trigger)
{!! '' !!}
{{-- Avoid formatting issues with unclosed elements --}}
+
attributes->get('disabled'))
@if ($id)
@@ -9669,6 +9759,7 @@ exports[`validation/filament-fixtures > formats support_resources_views_componen
@endif
+
{{ $heading }}
@@ -9766,6 +9857,7 @@ exports[`validation/filament-fixtures > formats support_resources_views_componen
$wireClickAction = "previousPage('{$paginator->getPageName()}')";
}
@endphp
+
formats support_resources_views_componen
$wireClickAction = "nextPage('{$paginator->getPageName()}')";
}
@endphp
+
formats support_resources_views_componen
:wire:key="$this->getId() . '.pagination.first'"
/>
@endif
+
formats support_resources_views_componen
@if (is_string($element))
@endif
+
@if (is_array($element))
@foreach ($element as $page => $url)
formats support_resources_views_componen
:wire:click="'nextPage(\\'' . $paginator->getPageName() . '\\')'"
:wire:key="$this->getId() . '.pagination.next'"
/>
+
@if ($extremeLinks)
formats tables_resources_views_component
@php
$columnHasSummary = ($pageTableSummaryQuery && $column->hasSummary($pageTableSummaryQuery)) || $column->hasSummary($allTableSummaryQuery);
@endphp
+
@if ($placeholderColumns || $columnHasSummary)
@php
$alignment = $column->getAlignment() ?? Alignment::Start;
@@ -10783,6 +10880,7 @@ exports[`validation/filament-fixtures > formats tables_resources_views_component
$hasColumnHeaderLabel = (! $placeholderColumns) || $columnHasSummary;
@endphp
+
getExtraHeaderAttributeBag()->class([
@@ -10809,9 +10907,11 @@ exports[`validation/filament-fixtures > formats tables_resources_views_component
@endif
+
@php
$selectedState = $this->getTableSummarySelectedState($pageTableSummaryQuery)[0] ?? [];
@endphp
+
formats tables_resources_views_component
$alignment = filled($alignment) ? (Alignment::tryFrom($alignment) ?? $alignment) : null;
}
@endphp
+
formats tables_resources_views_index.bla
@php
$searchPlaceholder = $getSearchPlaceholder();
@endphp
+
formats tables_resources_views_index.bla
$filtersTriggerActionIsModalFooterSticky = $filtersTriggerAction->isModalFooterSticky();
$filtersTriggerActionIsModalHeaderSticky = $filtersTriggerAction->isModalHeaderSticky();
@endphp
+
formats tables_resources_views_index.bla
{{ $filtersTriggerAction->badge($activeFiltersCount) }}
@endif
+
{{ FilamentView::renderHook(TablesRenderHook::TOOLBAR_COLUMN_MANAGER_TRIGGER_BEFORE, scopes: static::class) }}
+
@if ($hasColumnManagerDropdown)
@php
$columnManagerMaxHeight = $getColumnManagerMaxHeight();
$columnManagerWidth = $getColumnManagerWidth();
$columnManagerColumns = $getColumnManagerColumns();
@endphp
+
formats tables_resources_views_index.bla
/>
@endif
+
{{ FilamentView::renderHook(TablesRenderHook::TOOLBAR_COLUMN_MANAGER_TRIGGER_AFTER, scopes: static::class) }}
@endif
@@ -11632,6 +11739,7 @@ exports[`validation/filament-fixtures > formats tables_resources_views_index.bla
@php
$indicatorColor = $indicator->getColor();
@endphp
+
{{ $indicator->getLabel() }}
@@ -11639,6 +11747,7 @@ exports[`validation/filament-fixtures > formats tables_resources_views_index.bla
@php
$indicatorRemoveLivewireClickHandler = $indicator->getRemoveLivewireClickHandler();
@endphp
+
formats tables_resources_views_index.bla
fn (\\Filament\\Tables\\Columns\\Column $column): bool => $column->isSortable(),
);
@endphp
+
@if (($isSelectionEnabled && ($maxSelectableRecords !== 1) && (! $isReordering) && (! $selectsGroupsOnly)) || count($sortableColumns))
@endif
@endif
+
@if ($content)
{{ $content->with(['records' => $records]) }}
@else
@@ -11878,6 +11989,7 @@ exports[`validation/filament-fixtures > formats tables_resources_views_index.bla
initial: [],
);
@endphp
+
@if ((string) $recordGroupTitle !== (string) $previousRecordGroupTitle)
@if ($hasSummary && (! $isReordering) && filled($previousRecordGroupTitle))
formats tables_resources_views_index.bla
@endif
+
isCollapsible())
x-on:click="toggleCollapseGroup(@js($recordGroupTitle))"
@@ -12002,6 +12115,7 @@ exports[`validation/filament-fixtures > formats tables_resources_views_index.bla
@endif
@endif
+
formats tables_resources_views_index.bla
? "mountTableAction('{$recordAction}', '{$recordKey}')"
: $recordWireClickAction = "{$recordAction}('{$recordKey}')";
@endphp
+
formats tables_resources_views_index.bla
@endif
+
@php
$previousRecordGroupKey = $recordGroupKey;
$previousRecordGroupTitle = $recordGroupTitle;
@@ -12180,6 +12296,7 @@ exports[`validation/filament-fixtures > formats tables_resources_views_index.bla
@endif
@endif
+
@if (($content || $hasColumnsLayout) && $contentFooter)
{{
$contentFooter->with([
@@ -12188,6 +12305,7 @@ exports[`validation/filament-fixtures > formats tables_resources_views_index.bla
])
}}
@endif
+
@if ($hasSummary && (! $isReordering))
@@ -12213,6 +12331,7 @@ exports[`validation/filament-fixtures > formats tables_resources_views_index.bla
@if (count($defaultRecordActions) && in_array($recordActionsPosition, [RecordActionsPosition::BeforeCells, RecordActionsPosition::BeforeColumns]))
@endif
+
@if ($isSelectionEnabled && $recordCheckboxPosition === RecordCheckboxPosition::BeforeCells)
@endif
@@ -12228,6 +12347,7 @@ exports[`validation/filament-fixtures > formats tables_resources_views_index.bla
@php
$columnGroupColumnsCount = count($columnGroup->getVisibleColumns());
@endphp
+
@if ($columnGroupColumnsCount)
formats tables_resources_views_index.bla
@if (count($defaultRecordActions) && in_array($recordActionsPosition, [RecordActionsPosition::AfterColumns, RecordActionsPosition::AfterCells]))
@endif
+
@if ($isSelectionEnabled && $recordCheckboxPosition === RecordCheckboxPosition::AfterCells)
@endif
@@ -12275,6 +12396,7 @@ exports[`validation/filament-fixtures > formats tables_resources_views_index.bla
>
@endif
@endif
+
@if ($isSelectionEnabled && $recordCheckboxPosition === RecordCheckboxPosition::BeforeCells)
@if (($maxSelectableRecords !== 1) && (! $selectsGroupsOnly))
@@ -12319,6 +12441,7 @@ exports[`validation/filament-fixtures > formats tables_resources_views_index.bla
@endif
@endif
+
@if (count($defaultRecordActions) && $recordActionsPosition === RecordActionsPosition::BeforeColumns)
@if ($recordActionsColumnLabel)
formats tables_resources_views_index.bla
>
@endif
@endif
+
@if ($isSelectionEnabled && $recordCheckboxPosition === RecordCheckboxPosition::AfterCells)
@if (($maxSelectableRecords !== 1) && (! $selectsGroupsOnly))
@@ -12485,6 +12610,7 @@ exports[`validation/filament-fixtures > formats tables_resources_views_index.bla
@endif
@endif
+
@if (count($defaultRecordActions) && $recordActionsPosition === RecordActionsPosition::AfterCells)
@if ($recordActionsColumnLabel)
@endif
+
@if ($isSelectionEnabled && $recordCheckboxPosition === RecordCheckboxPosition::BeforeCells)
@endif
@@ -12533,6 +12660,7 @@ exports[`validation/filament-fixtures > formats tables_resources_views_index.bla
@php
$columnName = $column->getName();
@endphp
+
formats tables_resources_views_index.bla
@if (count($defaultRecordActions) && in_array($recordActionsPosition, [RecordActionsPosition::AfterColumns, RecordActionsPosition::AfterCells]))
@endif
+
@if ($isSelectionEnabled && $recordCheckboxPosition === RecordCheckboxPosition::AfterCells)
@endif
@@ -12568,6 +12697,7 @@ exports[`validation/filament-fixtures > formats tables_resources_views_index.bla
$previousRecordGroupKey = null;
$previousRecordGroupTitle = null;
@endphp
+
@foreach ($records as $record)
@php
$recordAction = $getRecordAction($record);
@@ -12597,12 +12727,14 @@ exports[`validation/filament-fixtures > formats tables_resources_views_index.bla
initial: [],
);
@endphp
+
@if ((string) $recordGroupTitle !== (string) $previousRecordGroupTitle)
@if ($hasSummary && (! $isReordering) && filled($previousRecordGroupTitle))
@php
$groupColumn = $group->getColumn();
$groupScopedAllTableSummaryQuery = $group->scopeQuery($this->getAllTableSummaryQuery(), $previousRecord);
@endphp
+
formats tables_resources_views_index.bla
:selection-enabled="$isSelectionEnabled"
/>
@endif
+
@if (! $isGroupsOnly)
@endif
+
@php
$isRecordRowStriped = false;
@endphp
@endif
+
@if (! $isGroupsOnly)
formats tables_resources_views_index.bla
}
}
@endphp
+
formats tables_resources_views_index.bla
@endif
@endif
+
@php
$isRecordRowStriped = ! $isRecordRowStriped;
$previousRecord = $record;
@@ -13041,11 +13179,13 @@ exports[`validation/filament-fixtures > formats tables_resources_views_index.bla
$previousRecordGroupTitle = $recordGroupTitle;
@endphp
@endforeach
+
@if ($hasSummary && (! $isReordering) && filled($previousRecordGroupTitle) && ((! $records instanceof \\Illuminate\\Contracts\\Pagination\\Paginator) || (! $records->hasMorePages())))
@php
$groupColumn = $group->getColumn();
$groupScopedAllTableSummaryQuery = $group->scopeQuery($this->getAllTableSummaryQuery(), $previousRecord);
@endphp
+
formats tables_resources_views_index.bla
:selection-enabled="$isSelectionEnabled"
/>
@endif
+
@if ($hasSummary && (! $isReordering))
@php
$groupColumn = $group?->getColumn();
@endphp
+
formats tables_resources_views_index.bla
$hasExtremePaginationLinks = $hasExtremePaginationLinks();
$paginationPageOptions = $getPaginationPageOptions();
@endphp
+
formats widgets_resources_views_componen
@php
$widgetClass = $normalizeWidgetClass($widget);
@endphp
+
@livewire (
$widgetClass,
[...(($widget instanceof \\Filament\\Widgets\\WidgetConfiguration) ? [...$widget->widget::getDefaultProperties(), ...$widget->getProperties()] : $widget::getDefaultProperties()), ...$data],
diff --git a/tests/validation/corpora/__snapshots__/laravel-documentation-fixtures.test.ts.snap b/tests/validation/corpora/__snapshots__/laravel-documentation-fixtures.test.ts.snap
index e2877a3..aacdc10 100644
--- a/tests/validation/corpora/__snapshots__/laravel-documentation-fixtures.test.ts.snap
+++ b/tests/validation/corpora/__snapshots__/laravel-documentation-fixtures.test.ts.snap
@@ -211,6 +211,7 @@ exports[`validation/laravel-documentation-fixtures > formats docs_23.blade.php (
@yield ('navigation')
+
@endif
"
@@ -279,7 +280,9 @@ exports[`validation/laravel-documentation-fixtures > formats docs_29.blade.php (
@if ($user->type == 1)
@continue
@endif
+
{{ $user->name }}
+
@if ($user->number == 5)
@break
@endif
@@ -290,6 +293,7 @@ exports[`validation/laravel-documentation-fixtures > formats docs_29.blade.php (
exports[`validation/laravel-documentation-fixtures > formats docs_30.blade.php (snapshot + idempotent + no-loss) 1`] = `
"@foreach ($users as $user)
@continue ($user->type == 1)
+
{{ $user->name }}
@break ($user->number == 5)
@endforeach
@@ -301,9 +305,11 @@ exports[`validation/laravel-documentation-fixtures > formats docs_31.blade.php (
@if ($loop->first)
This is the first iteration.
@endif
+
@if ($loop->last)
This is the last iteration.
@endif
+
This is user {{ $user->id }}
@endforeach
"