From 148e08ad236dfd446ddd9685edcefd375b38557c Mon Sep 17 00:00:00 2001 From: Laszlo Stahorszki Date: Fri, 3 Jul 2026 12:02:39 +0200 Subject: [PATCH 1/2] feat(tables): allow columns to override search term splitting for individual searches Columns can now define `splitSearchTerms()` to override the table-level setting for their individual search inputs. By default, columns do not define this, and the table's `splitSearchTerms()` setting is used. Co-Authored-By: Claude Fable 5 --- .../tables/docs/02-columns/01-overview.md | 10 +++++ .../src/Columns/Concerns/CanBeSearchable.php | 14 +++++++ .../tables/src/Concerns/CanSearchRecords.php | 4 +- ...eWithColumnSearchTermSplittingDisabled.php | 40 ++++++++++++++++++ ...leWithColumnSearchTermSplittingEnabled.php | 41 +++++++++++++++++++ tests/src/Tables/ColumnTest.php | 40 ++++++++++++++++++ 6 files changed, 147 insertions(+), 2 deletions(-) create mode 100644 tests/src/Fixtures/Livewire/PostsTableWithColumnSearchTermSplittingDisabled.php create mode 100644 tests/src/Fixtures/Livewire/PostsTableWithColumnSearchTermSplittingEnabled.php diff --git a/packages/tables/docs/02-columns/01-overview.md b/packages/tables/docs/02-columns/01-overview.md index 57f2f799ddf..5cf16268737 100644 --- a/packages/tables/docs/02-columns/01-overview.md +++ b/packages/tables/docs/02-columns/01-overview.md @@ -542,6 +542,16 @@ public function table(Table $table): Table } ``` +You can also use the `splitSearchTerms()` method on a column to override the table's setting for that column's [individual search](#searching-individually) only: + +```php +use Filament\Tables\Columns\TextColumn; + +TextColumn::make('title') + ->searchable(isIndividual: true) + ->splitSearchTerms(false) +``` + ## Clickable cell content When a cell is clicked, you may open a URL or trigger an "action". diff --git a/packages/tables/src/Columns/Concerns/CanBeSearchable.php b/packages/tables/src/Columns/Concerns/CanBeSearchable.php index 884b627e409..7b35a4a3989 100644 --- a/packages/tables/src/Columns/Concerns/CanBeSearchable.php +++ b/packages/tables/src/Columns/Concerns/CanBeSearchable.php @@ -23,6 +23,8 @@ trait CanBeSearchable protected bool | Closure | null $isSearchForcedCaseInsensitive = null; + protected bool | Closure | null $shouldSplitSearchTerms = null; + /** * @param bool | array | string | Closure $condition */ @@ -54,6 +56,13 @@ public function forceSearchCaseInsensitive(bool | Closure | null $condition = tr return $this; } + public function splitSearchTerms(bool | Closure | null $condition = true): static + { + $this->shouldSplitSearchTerms = $condition; + + return $this; + } + /** * @return array */ @@ -82,6 +91,11 @@ public function isSearchForcedCaseInsensitive(): ?bool return $this->evaluate($this->isSearchForcedCaseInsensitive); } + public function shouldSplitSearchTerms(): ?bool + { + return $this->evaluate($this->shouldSplitSearchTerms); + } + /** * @return array{0: string} */ diff --git a/packages/tables/src/Concerns/CanSearchRecords.php b/packages/tables/src/Concerns/CanSearchRecords.php index 21a451e7e05..119403d420c 100644 --- a/packages/tables/src/Concerns/CanSearchRecords.php +++ b/packages/tables/src/Concerns/CanSearchRecords.php @@ -75,7 +75,7 @@ protected function applySearchToTableQuery(Builder $query): Builder protected function applyColumnSearchesToTableQuery(Builder $query): Builder { $table = $this->getTable(); - $shouldSplitSearchTerms = $table->shouldSplitSearchTerms(); + $shouldSplitSearchTermsByDefault = $table->shouldSplitSearchTerms(); foreach ($this->getTableColumnSearches() as $column => $search) { if (blank($search)) { @@ -96,7 +96,7 @@ protected function applyColumnSearchesToTableQuery(Builder $query): Builder continue; } - if (! $shouldSplitSearchTerms) { + if (! ($column->shouldSplitSearchTerms() ?? $shouldSplitSearchTermsByDefault)) { $isFirst = true; $column->applySearchConstraint( diff --git a/tests/src/Fixtures/Livewire/PostsTableWithColumnSearchTermSplittingDisabled.php b/tests/src/Fixtures/Livewire/PostsTableWithColumnSearchTermSplittingDisabled.php new file mode 100644 index 00000000000..fb138a99cee --- /dev/null +++ b/tests/src/Fixtures/Livewire/PostsTableWithColumnSearchTermSplittingDisabled.php @@ -0,0 +1,40 @@ +query(Post::query()) + ->columns([ + TextColumn::make('title') + ->searchable(isIndividual: true, isGlobal: false), + TextColumn::make('content') + ->searchable(isIndividual: true, isGlobal: false) + ->splitSearchTerms(false), + ]); + } + + public function render(): View + { + return view('livewire.table'); + } +} diff --git a/tests/src/Fixtures/Livewire/PostsTableWithColumnSearchTermSplittingEnabled.php b/tests/src/Fixtures/Livewire/PostsTableWithColumnSearchTermSplittingEnabled.php new file mode 100644 index 00000000000..2dc638c1a6a --- /dev/null +++ b/tests/src/Fixtures/Livewire/PostsTableWithColumnSearchTermSplittingEnabled.php @@ -0,0 +1,41 @@ +query(Post::query()) + ->splitSearchTerms(false) + ->columns([ + TextColumn::make('title') + ->searchable(isIndividual: true, isGlobal: false), + TextColumn::make('content') + ->searchable(isIndividual: true, isGlobal: false) + ->splitSearchTerms(), + ]); + } + + public function render(): View + { + return view('livewire.table'); + } +} diff --git a/tests/src/Tables/ColumnTest.php b/tests/src/Tables/ColumnTest.php index 8dd688a45f7..db21e24b904 100644 --- a/tests/src/Tables/ColumnTest.php +++ b/tests/src/Tables/ColumnTest.php @@ -4,6 +4,8 @@ use Filament\Tables\Columns\TextColumn; use Filament\Tests\Fixtures\Livewire\CustomDataTable; use Filament\Tests\Fixtures\Livewire\PostsTable; +use Filament\Tests\Fixtures\Livewire\PostsTableWithColumnSearchTermSplittingDisabled; +use Filament\Tests\Fixtures\Livewire\PostsTableWithColumnSearchTermSplittingEnabled; use Filament\Tests\Fixtures\Livewire\PostsTableWithQualifiedColumns; use Filament\Tests\Fixtures\Livewire\PostsTableWithReservedJsPropertyColumnSearch; use Filament\Tests\Fixtures\Livewire\PostsTableWithTableSearchableColumns; @@ -356,6 +358,44 @@ ->assertCanNotSeeTableRecords($posts->where('author.email', '!=', $authorEmail)); }); + it('can use `splitSearchTerms(false)` on a column to disable search term splitting for its individual search', function (): void { + $exactPhrasePost = Post::factory()->create(['content' => 'ipsum dolor']); + $separateWordsPost = Post::factory()->create(['content' => 'ipsum sit dolor']); + + livewire(PostsTableWithColumnSearchTermSplittingDisabled::class) + ->searchTableColumns(['content' => 'ipsum dolor']) + ->assertCanSeeTableRecords([$exactPhrasePost]) + ->assertCanNotSeeTableRecords([$separateWordsPost]); + }); + + it('splits search terms for an individual column search by default, when the column does not define `splitSearchTerms()`', function (): void { + $exactPhrasePost = Post::factory()->create(['title' => 'ipsum dolor']); + $separateWordsPost = Post::factory()->create(['title' => 'ipsum sit dolor']); + + livewire(PostsTableWithColumnSearchTermSplittingDisabled::class) + ->searchTableColumns(['title' => 'ipsum dolor']) + ->assertCanSeeTableRecords([$exactPhrasePost, $separateWordsPost]); + }); + + it('can use `splitSearchTerms()` on a column to enable search term splitting for its individual search when the table uses `splitSearchTerms(false)`', function (): void { + $exactPhrasePost = Post::factory()->create(['content' => 'ipsum dolor']); + $separateWordsPost = Post::factory()->create(['content' => 'ipsum sit dolor']); + + livewire(PostsTableWithColumnSearchTermSplittingEnabled::class) + ->searchTableColumns(['content' => 'ipsum dolor']) + ->assertCanSeeTableRecords([$exactPhrasePost, $separateWordsPost]); + }); + + it('does not split search terms for an individual column search when the table uses `splitSearchTerms(false)` and the column does not define `splitSearchTerms()`', function (): void { + $exactPhrasePost = Post::factory()->create(['title' => 'ipsum dolor']); + $separateWordsPost = Post::factory()->create(['title' => 'ipsum sit dolor']); + + livewire(PostsTableWithColumnSearchTermSplittingEnabled::class) + ->searchTableColumns(['title' => 'ipsum dolor']) + ->assertCanSeeTableRecords([$exactPhrasePost]) + ->assertCanNotSeeTableRecords([$separateWordsPost]); + }); + it('seeds individually searchable columns whose names collide with JS array properties on mount so `tableColumnSearches` serializes as a JSON object', function (): void { livewire(PostsTableWithReservedJsPropertyColumnSearch::class) ->assertSet('tableColumnSearches', ['length' => '', 'sort' => '']); From 970662d630d9c3d960f453a8dc6a837fca0540ce Mon Sep 17 00:00:00 2001 From: Dan Harrin Date: Wed, 8 Jul 2026 14:29:09 +0100 Subject: [PATCH 2/2] rename --- .../tables/docs/02-columns/01-overview.md | 8 ++++++-- .../src/Columns/Concerns/CanBeSearchable.php | 10 +++++----- .../tables/src/Concerns/CanSearchRecords.php | 2 +- ...IndividualSearchTermSplittingDisabled.php} | 4 ++-- ...nIndividualSearchTermSplittingEnabled.php} | 4 ++-- tests/src/Tables/ColumnTest.php | 20 +++++++++---------- 6 files changed, 26 insertions(+), 22 deletions(-) rename tests/src/Fixtures/Livewire/{PostsTableWithColumnSearchTermSplittingDisabled.php => PostsTableWithColumnIndividualSearchTermSplittingDisabled.php} (85%) rename tests/src/Fixtures/Livewire/{PostsTableWithColumnSearchTermSplittingEnabled.php => PostsTableWithColumnIndividualSearchTermSplittingEnabled.php} (86%) diff --git a/packages/tables/docs/02-columns/01-overview.md b/packages/tables/docs/02-columns/01-overview.md index 5cf16268737..7373b758c48 100644 --- a/packages/tables/docs/02-columns/01-overview.md +++ b/packages/tables/docs/02-columns/01-overview.md @@ -542,16 +542,20 @@ public function table(Table $table): Table } ``` -You can also use the `splitSearchTerms()` method on a column to override the table's setting for that column's [individual search](#searching-individually) only: +You can also use the `splitIndividualSearchTerms()` method on a column to override the table's setting for that column's [individual search](#searching-individually): ```php use Filament\Tables\Columns\TextColumn; TextColumn::make('title') ->searchable(isIndividual: true) - ->splitSearchTerms(false) + ->splitIndividualSearchTerms(false) ``` + + ## Clickable cell content When a cell is clicked, you may open a URL or trigger an "action". diff --git a/packages/tables/src/Columns/Concerns/CanBeSearchable.php b/packages/tables/src/Columns/Concerns/CanBeSearchable.php index 7b35a4a3989..ae8d311e2e3 100644 --- a/packages/tables/src/Columns/Concerns/CanBeSearchable.php +++ b/packages/tables/src/Columns/Concerns/CanBeSearchable.php @@ -23,7 +23,7 @@ trait CanBeSearchable protected bool | Closure | null $isSearchForcedCaseInsensitive = null; - protected bool | Closure | null $shouldSplitSearchTerms = null; + protected bool | Closure | null $shouldSplitIndividualSearchTerms = null; /** * @param bool | array | string | Closure $condition @@ -56,9 +56,9 @@ public function forceSearchCaseInsensitive(bool | Closure | null $condition = tr return $this; } - public function splitSearchTerms(bool | Closure | null $condition = true): static + public function splitIndividualSearchTerms(bool | Closure | null $condition = true): static { - $this->shouldSplitSearchTerms = $condition; + $this->shouldSplitIndividualSearchTerms = $condition; return $this; } @@ -91,9 +91,9 @@ public function isSearchForcedCaseInsensitive(): ?bool return $this->evaluate($this->isSearchForcedCaseInsensitive); } - public function shouldSplitSearchTerms(): ?bool + public function shouldSplitIndividualSearchTerms(): ?bool { - return $this->evaluate($this->shouldSplitSearchTerms); + return $this->evaluate($this->shouldSplitIndividualSearchTerms); } /** diff --git a/packages/tables/src/Concerns/CanSearchRecords.php b/packages/tables/src/Concerns/CanSearchRecords.php index 119403d420c..d9a1db03a1c 100644 --- a/packages/tables/src/Concerns/CanSearchRecords.php +++ b/packages/tables/src/Concerns/CanSearchRecords.php @@ -96,7 +96,7 @@ protected function applyColumnSearchesToTableQuery(Builder $query): Builder continue; } - if (! ($column->shouldSplitSearchTerms() ?? $shouldSplitSearchTermsByDefault)) { + if (! ($column->shouldSplitIndividualSearchTerms() ?? $shouldSplitSearchTermsByDefault)) { $isFirst = true; $column->applySearchConstraint( diff --git a/tests/src/Fixtures/Livewire/PostsTableWithColumnSearchTermSplittingDisabled.php b/tests/src/Fixtures/Livewire/PostsTableWithColumnIndividualSearchTermSplittingDisabled.php similarity index 85% rename from tests/src/Fixtures/Livewire/PostsTableWithColumnSearchTermSplittingDisabled.php rename to tests/src/Fixtures/Livewire/PostsTableWithColumnIndividualSearchTermSplittingDisabled.php index fb138a99cee..4da9fcfdcdf 100644 --- a/tests/src/Fixtures/Livewire/PostsTableWithColumnSearchTermSplittingDisabled.php +++ b/tests/src/Fixtures/Livewire/PostsTableWithColumnIndividualSearchTermSplittingDisabled.php @@ -14,7 +14,7 @@ use Illuminate\Contracts\View\View; use Livewire\Component; -class PostsTableWithColumnSearchTermSplittingDisabled extends Component implements HasActions, HasSchemas, HasTable +class PostsTableWithColumnIndividualSearchTermSplittingDisabled extends Component implements HasActions, HasSchemas, HasTable { use InteractsWithActions; use InteractsWithSchemas; @@ -29,7 +29,7 @@ public function table(Table $table): Table ->searchable(isIndividual: true, isGlobal: false), TextColumn::make('content') ->searchable(isIndividual: true, isGlobal: false) - ->splitSearchTerms(false), + ->splitIndividualSearchTerms(false), ]); } diff --git a/tests/src/Fixtures/Livewire/PostsTableWithColumnSearchTermSplittingEnabled.php b/tests/src/Fixtures/Livewire/PostsTableWithColumnIndividualSearchTermSplittingEnabled.php similarity index 86% rename from tests/src/Fixtures/Livewire/PostsTableWithColumnSearchTermSplittingEnabled.php rename to tests/src/Fixtures/Livewire/PostsTableWithColumnIndividualSearchTermSplittingEnabled.php index 2dc638c1a6a..8ff192a5963 100644 --- a/tests/src/Fixtures/Livewire/PostsTableWithColumnSearchTermSplittingEnabled.php +++ b/tests/src/Fixtures/Livewire/PostsTableWithColumnIndividualSearchTermSplittingEnabled.php @@ -14,7 +14,7 @@ use Illuminate\Contracts\View\View; use Livewire\Component; -class PostsTableWithColumnSearchTermSplittingEnabled extends Component implements HasActions, HasSchemas, HasTable +class PostsTableWithColumnIndividualSearchTermSplittingEnabled extends Component implements HasActions, HasSchemas, HasTable { use InteractsWithActions; use InteractsWithSchemas; @@ -30,7 +30,7 @@ public function table(Table $table): Table ->searchable(isIndividual: true, isGlobal: false), TextColumn::make('content') ->searchable(isIndividual: true, isGlobal: false) - ->splitSearchTerms(), + ->splitIndividualSearchTerms(), ]); } diff --git a/tests/src/Tables/ColumnTest.php b/tests/src/Tables/ColumnTest.php index db21e24b904..66e689b4fd6 100644 --- a/tests/src/Tables/ColumnTest.php +++ b/tests/src/Tables/ColumnTest.php @@ -4,8 +4,8 @@ use Filament\Tables\Columns\TextColumn; use Filament\Tests\Fixtures\Livewire\CustomDataTable; use Filament\Tests\Fixtures\Livewire\PostsTable; -use Filament\Tests\Fixtures\Livewire\PostsTableWithColumnSearchTermSplittingDisabled; -use Filament\Tests\Fixtures\Livewire\PostsTableWithColumnSearchTermSplittingEnabled; +use Filament\Tests\Fixtures\Livewire\PostsTableWithColumnIndividualSearchTermSplittingDisabled; +use Filament\Tests\Fixtures\Livewire\PostsTableWithColumnIndividualSearchTermSplittingEnabled; use Filament\Tests\Fixtures\Livewire\PostsTableWithQualifiedColumns; use Filament\Tests\Fixtures\Livewire\PostsTableWithReservedJsPropertyColumnSearch; use Filament\Tests\Fixtures\Livewire\PostsTableWithTableSearchableColumns; @@ -358,39 +358,39 @@ ->assertCanNotSeeTableRecords($posts->where('author.email', '!=', $authorEmail)); }); - it('can use `splitSearchTerms(false)` on a column to disable search term splitting for its individual search', function (): void { + it('can use `splitIndividualSearchTerms(false)` on a column to disable search term splitting for its individual search', function (): void { $exactPhrasePost = Post::factory()->create(['content' => 'ipsum dolor']); $separateWordsPost = Post::factory()->create(['content' => 'ipsum sit dolor']); - livewire(PostsTableWithColumnSearchTermSplittingDisabled::class) + livewire(PostsTableWithColumnIndividualSearchTermSplittingDisabled::class) ->searchTableColumns(['content' => 'ipsum dolor']) ->assertCanSeeTableRecords([$exactPhrasePost]) ->assertCanNotSeeTableRecords([$separateWordsPost]); }); - it('splits search terms for an individual column search by default, when the column does not define `splitSearchTerms()`', function (): void { + it('splits search terms for an individual column search by default, when the column does not define `splitIndividualSearchTerms()`', function (): void { $exactPhrasePost = Post::factory()->create(['title' => 'ipsum dolor']); $separateWordsPost = Post::factory()->create(['title' => 'ipsum sit dolor']); - livewire(PostsTableWithColumnSearchTermSplittingDisabled::class) + livewire(PostsTableWithColumnIndividualSearchTermSplittingDisabled::class) ->searchTableColumns(['title' => 'ipsum dolor']) ->assertCanSeeTableRecords([$exactPhrasePost, $separateWordsPost]); }); - it('can use `splitSearchTerms()` on a column to enable search term splitting for its individual search when the table uses `splitSearchTerms(false)`', function (): void { + it('can use `splitIndividualSearchTerms()` on a column to enable search term splitting for its individual search when the table uses `splitSearchTerms(false)`', function (): void { $exactPhrasePost = Post::factory()->create(['content' => 'ipsum dolor']); $separateWordsPost = Post::factory()->create(['content' => 'ipsum sit dolor']); - livewire(PostsTableWithColumnSearchTermSplittingEnabled::class) + livewire(PostsTableWithColumnIndividualSearchTermSplittingEnabled::class) ->searchTableColumns(['content' => 'ipsum dolor']) ->assertCanSeeTableRecords([$exactPhrasePost, $separateWordsPost]); }); - it('does not split search terms for an individual column search when the table uses `splitSearchTerms(false)` and the column does not define `splitSearchTerms()`', function (): void { + it('does not split search terms for an individual column search when the table uses `splitSearchTerms(false)` and the column does not define `splitIndividualSearchTerms()`', function (): void { $exactPhrasePost = Post::factory()->create(['title' => 'ipsum dolor']); $separateWordsPost = Post::factory()->create(['title' => 'ipsum sit dolor']); - livewire(PostsTableWithColumnSearchTermSplittingEnabled::class) + livewire(PostsTableWithColumnIndividualSearchTermSplittingEnabled::class) ->searchTableColumns(['title' => 'ipsum dolor']) ->assertCanSeeTableRecords([$exactPhrasePost]) ->assertCanNotSeeTableRecords([$separateWordsPost]);