fix: Strip characters for numeric input (#19454)

This commit is contained in:
Dan Harrin
2026-03-08 20:12:39 +00:00
committed by GitHub
parent 0ac5476193
commit 44205a1d2b
5 changed files with 107 additions and 10 deletions
+11 -6
View File
@@ -6,6 +6,7 @@ use Closure;
use Filament\Schemas\Components\Concerns\CanStripCharactersFromState;
use Filament\Schemas\Components\Concerns\CanTrimState;
use Filament\Schemas\Components\Contracts\HasAffixActions;
use Filament\Schemas\Components\StateCasts\StripCharactersStateCast;
use Filament\Support\Concerns\HasColor;
use Filament\Support\Concerns\HasExtraAlpineAttributes;
use Filament\Support\Concerns\HasReorderAnimationDuration;
@@ -175,17 +176,21 @@ class TagsInput extends Field implements Contracts\HasNestedRecursiveValidationR
return (bool) $this->evaluate($this->isReorderable);
}
public function getDefaultStateCasts(): array
{
return [
...parent::getDefaultStateCasts(),
...($this->hasStripCharacters() ? [app(StripCharactersStateCast::class, ['characters' => $this->getStripCharacters()])] : []),
];
}
public function mutateDehydratedState(mixed $state): mixed
{
if (is_array($state)) {
$state = array_map(function (mixed $value): mixed {
$value = $this->stripCharactersFromState($value);
$value = $this->trimState($value);
return $value;
return $this->trimState($value);
}, $state);
} else {
$state = $this->stripCharactersFromState($state);
$state = $this->trimState($state);
}
@@ -211,7 +216,7 @@ class TagsInput extends Field implements Contracts\HasNestedRecursiveValidationR
public function mutatesDehydratedState(): bool
{
return parent::mutatesDehydratedState() || $this->hasStripCharacters() || $this->isTrimmed();
return parent::mutatesDehydratedState() || $this->isTrimmed();
}
public function mutatesStateForValidation(): bool
+3 -2
View File
@@ -9,6 +9,7 @@ use Filament\Schemas\Components\Concerns\CanTrimState;
use Filament\Schemas\Components\Contracts\HasAffixActions;
use Filament\Schemas\Components\StateCasts\Contracts\StateCast;
use Filament\Schemas\Components\StateCasts\NumberStateCast;
use Filament\Schemas\Components\StateCasts\StripCharactersStateCast;
use Filament\Support\Concerns\HasExtraAlpineAttributes;
use Filament\Support\RawJs;
use LogicException;
@@ -302,13 +303,13 @@ class TextInput extends Field implements CanHaveNumericState, Contracts\CanBeLen
{
return [
...parent::getDefaultStateCasts(),
...($this->hasStripCharacters() ? [app(StripCharactersStateCast::class, ['characters' => $this->getStripCharacters()])] : []),
...($this->isNumeric() ? [app(NumberStateCast::class, ['isNullable' => true])] : []),
];
}
public function mutateDehydratedState(mixed $state): mixed
{
$state = $this->stripCharactersFromState($state);
$state = $this->trimState($state);
return parent::mutateDehydratedState($state);
@@ -324,7 +325,7 @@ class TextInput extends Field implements CanHaveNumericState, Contracts\CanBeLen
public function mutatesDehydratedState(): bool
{
return parent::mutatesDehydratedState() || $this->hasStripCharacters() || $this->isTrimmed();
return parent::mutatesDehydratedState() || $this->isTrimmed();
}
public function mutatesStateForValidation(): bool
+10 -2
View File
@@ -6,6 +6,7 @@ use Closure;
use Filament\Forms\Components\Concerns\CanDisableGrammarly;
use Filament\Schemas\Components\Concerns\CanStripCharactersFromState;
use Filament\Schemas\Components\Concerns\CanTrimState;
use Filament\Schemas\Components\StateCasts\StripCharactersStateCast;
use Filament\Support\Concerns\HasExtraAlpineAttributes;
class Textarea extends Field implements Contracts\CanBeLengthConstrained
@@ -67,9 +68,16 @@ class Textarea extends Field implements Contracts\CanBeLengthConstrained
return (bool) $this->evaluate($this->shouldAutosize);
}
public function getDefaultStateCasts(): array
{
return [
...parent::getDefaultStateCasts(),
...($this->hasStripCharacters() ? [app(StripCharactersStateCast::class, ['characters' => $this->getStripCharacters()])] : []),
];
}
public function mutateDehydratedState(mixed $state): mixed
{
$state = $this->stripCharactersFromState($state);
$state = $this->trimState($state);
return parent::mutateDehydratedState($state);
@@ -85,7 +93,7 @@ class Textarea extends Field implements Contracts\CanBeLengthConstrained
public function mutatesDehydratedState(): bool
{
return parent::mutatesDehydratedState() || $this->hasStripCharacters() || $this->isTrimmed();
return parent::mutatesDehydratedState() || $this->isTrimmed();
}
public function mutatesStateForValidation(): bool
@@ -0,0 +1,49 @@
<?php
namespace Filament\Schemas\Components\StateCasts;
use Filament\Schemas\Components\StateCasts\Contracts\StateCast;
class StripCharactersStateCast implements StateCast
{
/**
* @param array<string> $characters
*/
public function __construct(
protected array $characters = [],
) {}
public function get(mixed $state): mixed
{
if (empty($this->characters)) {
return $state;
}
if (is_array($state)) {
return array_map(fn (mixed $value): mixed => is_string($value) ? str_replace($this->characters, '', $value) : $value, $state);
}
if (! is_string($state)) {
return $state;
}
return str_replace($this->characters, '', $state);
}
public function set(mixed $state): mixed
{
if (empty($this->characters)) {
return $state;
}
if (is_array($state)) {
return array_map(fn (mixed $value): mixed => is_string($value) ? str_replace($this->characters, '', $value) : $value, $state);
}
if (! is_string($state)) {
return $state;
}
return str_replace($this->characters, '', $state);
}
}
@@ -22,6 +22,40 @@ it('can trim whitespace from TextInput', function (mixed $input, mixed $expected
[123, 123],
]);
it('can strip characters before applying `numeric()` state cast', function (mixed $input, mixed $expected): void {
livewire(TestComponentWithNumericAndStripCharacters::class)
->fillForm(['price' => $input])
->call('save')
->assertSet('data.price', $expected);
})->with([
['1,234.56', 1234.56],
['1,234,567.89', 1234567.89],
['999.99', 999.99],
['1,000', 1000.0],
[null, null],
]);
class TestComponentWithNumericAndStripCharacters extends Livewire
{
public $data = [];
public function form(Schema $form): Schema
{
return $form
->schema([
TextInput::make('price')
->numeric()
->stripCharacters(','),
])
->statePath('data');
}
public function save(): void
{
$this->data = $this->form->getState();
}
}
class TestComponentWithTextInputTrim extends Livewire
{
public $data = [];