diff --git a/packages/forms/src/Components/TagsInput.php b/packages/forms/src/Components/TagsInput.php index e24faab9be..0b9b02d2f8 100644 --- a/packages/forms/src/Components/TagsInput.php +++ b/packages/forms/src/Components/TagsInput.php @@ -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 diff --git a/packages/forms/src/Components/TextInput.php b/packages/forms/src/Components/TextInput.php index 49027b964b..dd34a41add 100644 --- a/packages/forms/src/Components/TextInput.php +++ b/packages/forms/src/Components/TextInput.php @@ -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 diff --git a/packages/forms/src/Components/Textarea.php b/packages/forms/src/Components/Textarea.php index 8bbfc36208..f15e719150 100644 --- a/packages/forms/src/Components/Textarea.php +++ b/packages/forms/src/Components/Textarea.php @@ -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 diff --git a/packages/schemas/src/Components/StateCasts/StripCharactersStateCast.php b/packages/schemas/src/Components/StateCasts/StripCharactersStateCast.php new file mode 100644 index 0000000000..132438e0da --- /dev/null +++ b/packages/schemas/src/Components/StateCasts/StripCharactersStateCast.php @@ -0,0 +1,49 @@ + $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); + } +} diff --git a/tests/src/Forms/Components/TextInputTest.php b/tests/src/Forms/Components/TextInputTest.php index 92fc73ef10..3f03eac5a0 100644 --- a/tests/src/Forms/Components/TextInputTest.php +++ b/tests/src/Forms/Components/TextInputTest.php @@ -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 = [];