+
{!! $treeHtml !!}
@else
diff --git a/src/UI/resources/views/fields/stack.blade.php b/src/UI/resources/views/fields/stack.blade.php
index f61ee1c3a..617d81ba2 100644
--- a/src/UI/resources/views/fields/stack.blade.php
+++ b/src/UI/resources/views/fields/stack.blade.php
@@ -1,6 +1,8 @@
@props([
'fields' => [],
])
-
+
+
+
diff --git a/src/UI/src/Fields/File.php b/src/UI/src/Fields/File.php
index 6d95564d8..72f022024 100644
--- a/src/UI/src/Fields/File.php
+++ b/src/UI/src/Fields/File.php
@@ -24,6 +24,8 @@ class File extends Field implements FileableContract, RemovableContract
protected string $view = 'moonshine::fields.file';
+ protected bool $hasOld = false;
+
protected string $type = 'file';
protected string $accept = '*/*';
diff --git a/src/UI/src/Fields/FormElement.php b/src/UI/src/Fields/FormElement.php
index 223aaed53..f783f8917 100644
--- a/src/UI/src/Fields/FormElement.php
+++ b/src/UI/src/Fields/FormElement.php
@@ -79,6 +79,8 @@ abstract class FormElement extends MoonShineComponent implements FormElementCont
protected bool $hasOld = true;
+ protected bool $isOldValue = false;
+
protected bool $isGroup = false;
protected ComponentAttributesBagContract $wrapperAttributes;
@@ -90,19 +92,19 @@ abstract class FormElement extends MoonShineComponent implements FormElementCont
public function __construct(
Closure|string|null $label = null,
?string $column = null,
- ?Closure $formatted = null
+ ?Closure $formatted = null,
) {
parent::__construct();
$this->attributes = new MoonShineComponentAttributeBag(
- $this->getPropertyAttributes()->toArray()
+ $this->getPropertyAttributes()->toArray(),
);
$this->wrapperAttributes = new MoonShineComponentAttributeBag();
$this->setLabel($label ?? $this->getLabel());
$this->setColumn(
- trim($column ?? str($this->getLabel())->lower()->snake()->value())
+ trim($column ?? str($this->getLabel())->lower()->snake()->value()),
);
if (! \is_null($formatted)) {
@@ -119,7 +121,7 @@ function ($attr): array {
return isset($this->{$property})
? [$attr => $this->{$property}]
: [];
- }
+ },
);
}
@@ -199,7 +201,7 @@ protected function prepareFill(array $raw = [], ?DataWrapperContract $casted = n
return \call_user_func(
$this->fillCallback,
\is_null($casted) ? $raw : $casted->getOriginal(),
- $this
+ $this,
);
}
@@ -252,7 +254,7 @@ public function fillData(mixed $value, int $index = 0): static
return $this->resolveFill(
$casted->toArray(),
$casted,
- $index
+ $index,
);
}
@@ -335,7 +337,11 @@ public function getValue(bool $withOld = true): mixed
: $empty;
if ($withOld && $old !== $empty) {
- return $old;
+ $this->isOldValue = true;
+
+ $this->setValue(
+ $this->resolveOldValue($old),
+ );
}
$this->isValueResolved = true;
@@ -343,6 +349,16 @@ public function getValue(bool $withOld = true): mixed
return $this->resolvedValue = $this->resolveValue();
}
+ protected function resolveOldValue(mixed $old): mixed
+ {
+ return $old;
+ }
+
+ public function isOldValue(): bool
+ {
+ return $this->isOldValue;
+ }
+
protected function resolveValue(): mixed
{
return $this->toValue();
@@ -379,8 +395,8 @@ public function toFormattedValue(): mixed
$this->getFormattedValueCallback(),
$this->getData()?->getOriginal(),
$this->getRowIndex(),
- $this
- )
+ $this,
+ ),
);
}
@@ -497,8 +513,8 @@ public function appendRequestKeyPrefix(string $value, ?string $prefix = null): s
$this->setRequestKeyPrefix(
str($value)->when(
$prefix,
- static fn ($str) => $str->prepend("$prefix.")
- )->value()
+ static fn ($str) => $str->prepend("$prefix."),
+ )->value(),
);
return $this;
@@ -541,8 +557,8 @@ public function getRequestValue(string|int|null $index = null): mixed
$value = $this->prepareRequestValue(
$this->getCore()->getRequest()->get(
$this->getRequestNameDot($index),
- $this->getDefaultIfExists()
- ) ?? false
+ $this->getDefaultIfExists(),
+ ) ?? false,
);
if ($this->onRequestValue instanceof Closure) {
@@ -564,12 +580,12 @@ public function getRequestNameDot(string|int|null $index = null): string
->when(
$this->getRequestKeyPrefix(),
fn (Stringable $str): Stringable => $str->prepend(
- "{$this->getRequestKeyPrefix()}."
- )
+ "{$this->getRequestKeyPrefix()}.",
+ ),
)
->when(
! \is_null($index) && $index !== '',
- static fn (Stringable $str): Stringable => $str->append(".$index")
+ static fn (Stringable $str): Stringable => $str->append(".$index"),
)->value();
}
diff --git a/src/UI/src/Fields/Hidden.php b/src/UI/src/Fields/Hidden.php
index 34b9497ae..b80ec0068 100644
--- a/src/UI/src/Fields/Hidden.php
+++ b/src/UI/src/Fields/Hidden.php
@@ -17,6 +17,8 @@ class Hidden extends Field implements HasDefaultValueContract, CanBeString
protected string $type = 'hidden';
+ protected bool $hasOld = false;
+
protected bool $showValue = false;
protected bool $columnSelection = false;
diff --git a/src/UI/src/Fields/HiddenIds.php b/src/UI/src/Fields/HiddenIds.php
index 2c6974fe9..e336bc4ae 100644
--- a/src/UI/src/Fields/HiddenIds.php
+++ b/src/UI/src/Fields/HiddenIds.php
@@ -11,6 +11,8 @@ class HiddenIds extends Field
{
protected string $view = 'moonshine::fields.hidden-ids';
+ protected bool $hasOld = false;
+
protected string $type = 'hidden';
public function __construct(
diff --git a/src/UI/src/Fields/Json.php b/src/UI/src/Fields/Json.php
index 0582613cc..1edaabcb7 100644
--- a/src/UI/src/Fields/Json.php
+++ b/src/UI/src/Fields/Json.php
@@ -12,6 +12,7 @@
use MoonShine\Contracts\UI\ComponentAttributesBagContract;
use MoonShine\Contracts\UI\ComponentContract;
use MoonShine\Contracts\UI\FieldContract;
+use MoonShine\Contracts\UI\FieldWithComponentContract;
use MoonShine\Contracts\UI\HasFieldsContract;
use MoonShine\Contracts\UI\TableBuilderContract;
use MoonShine\UI\Collections\Fields;
@@ -33,9 +34,11 @@
/**
* @implements HasFieldsContract
+ * @implements FieldWithComponentContract
*/
class Json extends Field implements
HasFieldsContract,
+ FieldWithComponentContract,
RemovableContract,
HasDefaultValueContract,
CanBeArray
@@ -55,8 +58,6 @@ class Json extends Field implements
protected bool $isGroup = true;
- protected bool $hasOld = false;
-
protected bool $isCreatable = true;
protected ?int $creatableLimit = null;
@@ -75,6 +76,8 @@ class Json extends Field implements
protected bool $resolveValueOnce = true;
+ protected null|TableBuilderContract|FieldsGroup $resolvedComponent = null;
+
/**
* @throws Throwable
*/
@@ -154,7 +157,7 @@ public function isKeyOrOnlyValue(): bool
public function creatable(
Closure|bool|null $condition = null,
?int $limit = null,
- ?ActionButtonContract $button = null
+ ?ActionButtonContract $button = null,
): static {
$this->isCreatable = value($condition, $this) ?? true;
@@ -285,15 +288,16 @@ protected function resolveRawValue(): mixed
protected function resolvePreview(): Renderable|string
{
- $value = $this->getValue();
+ $component = $this->getComponent();
+ // FieldsGroup component
if ($this->isObjectMode()) {
- return $value
+ return $component
->previewMode()
->render();
}
- return $value
+ return $component
->simple()
->preview()
->render();
@@ -303,7 +307,7 @@ protected function reformatFilledValue(mixed $data): mixed
{
if ($this->isKeyOrOnlyValue() && ! $this->isFilterMode()) {
return collect($data)->map(fn ($data, $key): array => $this->extractKeyValue(
- $this->isOnlyValue() ? [$data] : [$key => $data]
+ $this->isOnlyValue() ? [$data] : [$key => $data],
))->values()->toArray();
}
@@ -340,33 +344,63 @@ protected function isBlankValue(): bool
/**
* @throws Throwable
*/
- protected function resolveValue(): mixed
+ public function prepareOnApplyRecursive(iterable $collection): array
+ {
+ $collection = $this->prepareOnApply($collection);
+
+ foreach ($this->getFields() as $field) {
+ if ($field instanceof self) {
+ foreach ($collection as $index => $value) {
+ $column = $field->getColumn();
+ $collection[$index][$column] = $field->prepareOnApplyRecursive(
+ $value[$column] ?? []
+ );
+ }
+ }
+ }
+
+ return $collection;
+ }
+
+ /**
+ * @throws Throwable
+ */
+ protected function resolveOldValue(mixed $old): mixed
+ {
+ return $this->prepareOnApplyRecursive($old);
+ }
+
+ public function getComponent(): ComponentContract
{
+ if (! \is_null($this->resolvedComponent)) {
+ return $this->resolvedComponent;
+ }
+
$value = $this->isPreviewMode()
? $this->toFormattedValue()
- : $this->toValue();
+ : $this->getValue();
$values = Collection::make(
is_iterable($value)
? $value
- : []
+ : [],
);
$fields = $this->getPreparedFields();
if ($this->isObjectMode()) {
return FieldsGroup::make(
- $fields
+ $fields,
)->fill($values->toArray())->mapFields(
fn (FieldContract $field): FieldContract => $field
->formName($this->getFormName())
- ->setParent($this)
+ ->setParent($this),
);
}
$values = $values->when(
! $this->isPreviewMode() && ! $this->isCreatable() && blank($values),
- static fn ($values): Collection => $values->push([null])
+ static fn ($values): Collection => $values->push([null]),
);
$reorderable = ! $this->isPreviewMode() && $this->isReorderable();
@@ -375,12 +409,12 @@ protected function resolveValue(): mixed
$fields->prepend(
Preview::make(
column: '__handle',
- formatted: static fn () => Icon::make('bars-4')
- )->customAttributes(['class' => 'handle', 'style' => 'cursor: move'])
+ formatted: static fn () => Icon::make('bars-4'),
+ )->customAttributes(['class' => 'handle', 'style' => 'cursor: move']),
);
}
- return TableBuilder::make($fields, $values)
+ $component = TableBuilder::make($fields, $values)
->name('repeater_' . $this->getColumn())
->inside('field')
->customAttributes(
@@ -390,13 +424,13 @@ protected function resolveValue(): mixed
$reorderable,
static fn (ComponentAttributesBagContract $attr): ComponentAttributesBagContract => $attr->merge([
'data-handle' => '.handle',
- ])
+ ]),
)
- ->jsonSerialize()
+ ->jsonSerialize(),
)
->when(
$reorderable,
- static fn (TableBuilderContract $table): TableBuilderContract => $table->reorderable()
+ static fn (TableBuilderContract $table): TableBuilderContract => $table->reorderable(),
)
->when(
$this->isVertical(),
@@ -411,18 +445,35 @@ protected function resolveValue(): mixed
/** @var Column $default */
/** @phpstan-ignore-next-line */
: $default->columnSpan($this->verticalValueSpan) : null,
- )
+ ),
)
->when(
! \is_null($this->modifyTable),
- fn (TableBuilder $tableBuilder) => value($this->modifyTable, $tableBuilder, $this->isPreviewMode())
+ fn (TableBuilder $tableBuilder) => value($this->modifyTable, $tableBuilder, $this->isPreviewMode()),
);
+
+ if (! $this->isPreviewMode()) {
+ $component = $component
+ ->editable()
+ ->reindex(prepared: true)
+ ->when(
+ $this->isCreatable(),
+ fn (TableBuilderContract $table): TableBuilderContract => $table->creatable(
+ limit: $this->getCreateLimit(),
+ button: $this->getCreateButton(),
+ )->removeAfterClone(),
+ )
+ ->buttons($this->getButtons())
+ ->simple();
+ }
+
+ return $this->resolvedComponent = $component;
}
/**
* @throws Throwable
*/
- protected function prepareOnApply(iterable $collection): array
+ public function prepareOnApply(iterable $collection): array
{
$collection = collect($collection);
@@ -431,8 +482,8 @@ protected function prepareOnApply(iterable $collection): array
fn ($data): Collection => $data->mapWithKeys(
fn ($data, $key): array => $this->isOnlyValue()
? [$key => $data['value']]
- : [$data['key'] => $data['value']]
- )
+ : [$data['key'] => $data['value']],
+ ),
)->filter(fn ($value): bool => $this->filterEmpty($value))->toArray();
}
@@ -454,7 +505,7 @@ protected function resolveAppliesCallback(
mixed $data,
Closure $callback,
?Closure $response = null,
- bool $fill = false
+ bool $fill = false,
): mixed {
$requestValues = array_filter($this->getRequestValue() ?: []);
$applyValues = [];
@@ -481,7 +532,7 @@ protected function resolveAppliesCallback(
/** @phpstan-ignore-next-line */
$applyValues[$index],
$field->getColumn(),
- data_get($apply, $field->getColumn())
+ data_get($apply, $field->getColumn()),
);
}
}
@@ -496,7 +547,7 @@ protected function resolveAppliesCallback(
return \is_null($response) ? data_set(
$data,
str_replace('.', '->', $this->getColumn()),
- $values
+ $values,
) : $response($values, $data);
}
@@ -506,7 +557,7 @@ protected function resolveOnApply(): ?Closure
data: $item,
callback: static fn (FieldContract $field, mixed $values): mixed => $field->apply(
static fn ($data): mixed => data_set($data, $field->getColumn(), data_get($values, $field->getColumn(), '')),
- $values
+ $values,
),
);
}
@@ -548,7 +599,7 @@ protected function resolveAfterDestroy(mixed $data): mixed
->each(
static fn (FieldContract $field): mixed => $field
->fillData($value)
- ->afterDestroy($value)
+ ->afterDestroy($value),
);
}
}
@@ -573,28 +624,8 @@ public function getReactiveValue(): mixed
*/
protected function viewData(): array
{
- /** @var TableBuilderContract $value */
- $value = $this->getValue();
-
- if ($this->isObjectMode()) {
- return [
- 'component' => $value,
- ];
- }
-
return [
- 'component' => $value
- ->editable()
- ->reindex(prepared: true)
- ->when(
- $this->isCreatable(),
- fn (TableBuilderContract $table): TableBuilderContract => $table->creatable(
- limit: $this->getCreateLimit(),
- button: $this->getCreateButton()
- )->removeAfterClone()
- )
- ->buttons($this->getButtons())
- ->simple(),
+ 'component' => $this->getComponent(),
];
}
}
diff --git a/src/UI/src/Fields/Password.php b/src/UI/src/Fields/Password.php
index df0dfb5ee..f52517174 100644
--- a/src/UI/src/Fields/Password.php
+++ b/src/UI/src/Fields/Password.php
@@ -11,6 +11,8 @@ class Password extends Text
{
protected string $type = 'password';
+ protected bool $hasOld = false;
+
protected function resolvePreview(): string
{
return '***';
diff --git a/src/UI/src/Fields/Template.php b/src/UI/src/Fields/Template.php
index 817415a8c..bd8ce8e63 100644
--- a/src/UI/src/Fields/Template.php
+++ b/src/UI/src/Fields/Template.php
@@ -20,6 +20,8 @@ class Template extends Field implements HasFieldsContract
{
use WithFields;
+ protected bool $hasOld = false;
+
protected function prepareFields(): FieldsContract
{
return tap(
diff --git a/src/UI/src/Traits/Fields/WithQuickFormElementAttributes.php b/src/UI/src/Traits/Fields/WithQuickFormElementAttributes.php
index e6652c8ba..76dd7fe4a 100644
--- a/src/UI/src/Traits/Fields/WithQuickFormElementAttributes.php
+++ b/src/UI/src/Traits/Fields/WithQuickFormElementAttributes.php
@@ -22,7 +22,7 @@ public function setNameAttribute(string $name): static
{
$this->nameAttribute = $name;
- return $this;
+ return $this->setAttribute('name', $name);
}
public function getNameAttribute(?string $index = null): string