Merge pull request #17783 from filamentphp/fix/partially-rendering-hidden-schema-components

fix: Partially rendering hidden schema components
This commit is contained in:
Dan Harrin
2025-09-14 10:52:18 +01:00
committed by GitHub
4 changed files with 111 additions and 111 deletions
+101 -29
View File
@@ -34,9 +34,11 @@ use Filament\Support\Concerns\CanGrow;
use Filament\Support\Concerns\CanOrderColumns;
use Filament\Support\Concerns\CanSpanColumns;
use Filament\Support\Concerns\HasExtraAttributes;
use Filament\Support\Enums\Width;
use Illuminate\Database\Eloquent\Model;
use Livewire\Drawer\Utils;
use Livewire\Exceptions\RootTagMissingFromViewException;
use Illuminate\Support\Arr;
use Illuminate\Support\Js;
use Illuminate\View\ComponentAttributeBag;
class Component extends ViewComponent
{
@@ -115,33 +117,6 @@ class Component extends ViewComponent
};
}
public function toHtml(): string
{
if ($this->isLiberatedFromContainerGrid()) {
return parent::toHtml();
}
$key = $this->getKey();
if (blank($key)) {
return parent::toHtml();
}
$html = parent::toHtml();
if (blank($html)) {
return '';
}
try {
return Utils::insertAttributesIntoHtmlRoot($html, [
'wire:partial' => "schema-component::{$key}",
]);
} catch (RootTagMissingFromViewException) {
return $html;
}
}
/**
* @return array<string, mixed>
*/
@@ -153,4 +128,101 @@ class Component extends ViewComponent
'record' => $this->getRecord(),
];
}
/**
* @internal Do not use this method outside the internals of Filament. It is subject to breaking changes in minor and patch releases.
*/
public function toSchemaHtml(?bool $isVisible = null): string
{
$isVisible ??= $this->isVisible();
$container = $this->getContainer();
$isContainerEmbeddedInParentComponent = $container->isEmbeddedInParentComponent();
$containerParentComponent = $isContainerEmbeddedInParentComponent
? $container->getParentComponent()
: null;
$containerStatePath = $isContainerEmbeddedInParentComponent
? $containerParentComponent->getContainer()->getStatePath()
: $container->getStatePath();
/**
* Instead of only rendering the hidden components, we should
* render the `<div>` wrappers for all fields, regardless of
* if they are hidden or not. This is to solve Livewire DOM
* diffing issues.
*
* Additionally, any `<div>` elements that wrap hidden
* components need to have `class="fi-hidden"`, so that they
* don't consume grid space.
*/
$hiddenJs = $this->getHiddenJs();
$visibleJs = $this->getVisibleJs();
$maxWidth = $this->getMaxWidth();
$statePath = $isContainerEmbeddedInParentComponent
? $containerParentComponent->getStatePath()
: $this->getStatePath();
$key = $this->getKey();
$attributes = (new ComponentAttributeBag)
->when(
! $container->isInline(),
fn (ComponentAttributeBag $attributes) => $attributes->gridColumn($this->getColumnSpan(), $this->getColumnStart(), $this->getColumnOrder(), ! $isVisible),
)
->merge([
'wire:key' => $this->getLivewireKey(),
...(($pollingInterval = $this->getPollingInterval()) ? ["wire:poll.{$pollingInterval}" => "partiallyRenderSchemaComponent('{$this->getKey()}')"] : []),
], escape: false)
->class([
($maxWidth instanceof Width) ? "fi-width-{$maxWidth->value}" : $maxWidth,
]);
ob_start(); ?>
<div
<?php if (filled($key)) { ?>
wire:partial="schema-component::<?= $key ?>"
<?php } ?>
<?php if ($isVisible) { ?>
x-data="filamentSchemaComponent({
path: <?= Js::from($statePath) ?>,
containerPath: <?= Js::from($containerStatePath) ?>,
isLive: <?= Js::from($this->isLive()) ?>,
$wire,
})"
<?php if ($afterStateUpdatedJs = $this->getAfterStateUpdatedJs()) { ?>
x-init="<?= implode(';', array_map(
fn (string $js): string => '$wire.watch(' . Js::from($statePath) . ', ($state, $old) => ($state !== undefined) && eval(' . Js::from($js) . '))',
$afterStateUpdatedJs,
)) ?>"
<?php } ?>
<?php if (filled($visibilityJs = match ([filled($hiddenJs), filled($visibleJs)]) {
[true, true] => "(! ({$hiddenJs})) && ({$visibleJs})",
[true, false] => "! ({$hiddenJs})",
[false, true] => $visibleJs,
default => null,
})) { ?>
x-bind:class="{ 'fi-hidden': ! (<?= $visibilityJs ?>) }"
x-cloak
<?php } ?>
<?php } ?>
<?= $attributes->toHtml() ?>
>
<?php if ($isVisible) { ?>
<div
class="<?= Arr::toCssClasses([
'fi-sc-component',
'fi-grid-ctn' => $this->isGridContainer(),
]) ?>"
>
<?= $this->toHtml() ?>
</div>
<?php } ?>
</div>
<?php return ob_get_clean();
}
}
@@ -69,7 +69,7 @@ trait CanPartiallyRender
}
return [
"schema-component::{$key}" => $this->hasView() ? $this->render() : $this->toHtml(...),
"schema-component::{$key}" => $this->toSchemaHtml(...),
];
});
}
@@ -14,6 +14,7 @@ use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use InvalidArgumentException;
use Livewire\Livewire;
use LogicException;
@@ -168,7 +169,13 @@ trait HasState
if (filled($components = $this->getComponentsToPartiallyRenderAfterStateUpdated())) {
foreach ($components as $key) {
$this->getLivewire()->getSchemaComponent($this->resolveRelativeKey($key))->partiallyRender();
$component = $this->getLivewire()->getSchemaComponent($this->resolveRelativeKey($key), withHidden: true);
if (! $component) {
throw new InvalidArgumentException("Could not find component [{$key}] to partially render.");
}
$component->partiallyRender();
}
}
+1 -80
View File
@@ -13,10 +13,8 @@ use Filament\Support\Concerns\HasAlignment;
use Filament\Support\Concerns\HasDefaultDataFormattingSettings;
use Filament\Support\Concerns\HasExtraAttributes;
use Filament\Support\Enums\Alignment;
use Filament\Support\Enums\Width;
use Illuminate\Contracts\Support\Htmlable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr;
use Illuminate\Support\Js;
use Illuminate\View\ComponentAttributeBag;
use Livewire\Component as LivewireComponent;
@@ -175,14 +173,6 @@ class Schema extends ViewComponent implements HasEmbeddedView
$isInline = $this->isInline();
$isRoot = $this->isRoot();
$isEmbeddedInParentComponent = $this->isEmbeddedInParentComponent();
$parentComponent = $isEmbeddedInParentComponent
? $this->getParentComponent()
: null;
$statePath = $isEmbeddedInParentComponent
? $parentComponent->getContainer()->getStatePath()
: $this->getStatePath();
$attributes = $this->getExtraAttributeBag()
->when(
! $isInline,
@@ -212,76 +202,7 @@ class Schema extends ViewComponent implements HasEmbeddedView
<?php } ?>
</div>
<?php } elseif (! $schemaComponent->isLiberatedFromContainerGrid()) { ?>
<?php
/**
* Instead of only rendering the hidden components, we should
* render the `<div>` wrappers for all fields, regardless of
* if they are hidden or not. This is to solve Livewire DOM
* diffing issues.
*
* Additionally, any `<div>` elements that wrap hidden
* components need to have `class="fi-hidden"`, so that they
* don't consume grid space.
*/
$hiddenJs = $schemaComponent->getHiddenJs();
$visibleJs = $schemaComponent->getVisibleJs();
$maxWidth = $schemaComponent->getMaxWidth();
$schemaComponentStatePath = $isEmbeddedInParentComponent
? $parentComponent->getStatePath()
: $schemaComponent->getStatePath();
$attributes = (new ComponentAttributeBag)
->when(
! $isInline,
fn (ComponentAttributeBag $attributes) => $attributes->gridColumn($schemaComponent->getColumnSpan(), $schemaComponent->getColumnStart(), $schemaComponent->getColumnOrder(), ! $isSchemaComponentVisible),
)
->merge([
'wire:key' => $schemaComponent->getLivewireKey(),
...(($pollingInterval = $schemaComponent->getPollingInterval()) ? ["wire:poll.{$pollingInterval}" => "partiallyRenderSchemaComponent('{$schemaComponent->getKey()}')"] : []),
], escape: false)
->class([
($maxWidth instanceof Width) ? "fi-width-{$maxWidth->value}" : $maxWidth,
]);
?>
<div
<?php if ($isSchemaComponentVisible) { ?>
x-data="filamentSchemaComponent({
path: <?= Js::from($schemaComponentStatePath) ?>,
containerPath: <?= Js::from($statePath) ?>,
isLive: <?= Js::from($schemaComponent->isLive()) ?>,
$wire,
})"
<?php if ($afterStateUpdatedJs = $schemaComponent->getAfterStateUpdatedJs()) { ?>
x-init="<?= implode(';', array_map(
fn (string $js): string => '$wire.watch(' . Js::from($schemaComponentStatePath) . ', ($state, $old) => ($state !== undefined) && eval(' . Js::from($js) . '))',
$afterStateUpdatedJs,
)) ?>"
<?php } ?>
<?php if (filled($visibilityJs = match ([filled($hiddenJs), filled($visibleJs)]) {
[true, true] => "(! ({$hiddenJs})) && ({$visibleJs})",
[true, false] => "! ({$hiddenJs})",
[false, true] => $visibleJs,
default => null,
})) { ?>
x-bind:class="{ 'fi-hidden': ! (<?= $visibilityJs ?>) }"
x-cloak
<?php } ?>
<?php } ?>
<?= $attributes->toHtml() ?>
>
<?php if ($isSchemaComponentVisible) { ?>
<div
class="<?= Arr::toCssClasses([
'fi-sc-component',
'fi-grid-ctn' => $schemaComponent->isGridContainer(),
]) ?>"
>
<?= $schemaComponent->toHtml() ?>
</div>
<?php } ?>
</div>
<?= $schemaComponent->toSchemaHtml(isVisible: $isSchemaComponentVisible) ?>
<?php } elseif ($isSchemaComponentVisible) { ?>
<?= $schemaComponent->toHtml() ?>
<?php } ?>