diff --git a/packages/tables/docs/02-columns/01-overview.md b/packages/tables/docs/02-columns/01-overview.md
index 57f2f799ddf..7373b758c48 100644
--- a/packages/tables/docs/02-columns/01-overview.md
+++ b/packages/tables/docs/02-columns/01-overview.md
@@ -542,6 +542,20 @@ public function table(Table $table): Table
}
```
+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)
+ ->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 884b627e409..ae8d311e2e3 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 $shouldSplitIndividualSearchTerms = null;
+
/**
* @param bool | array | string | Closure $condition
*/
@@ -54,6 +56,13 @@ public function forceSearchCaseInsensitive(bool | Closure | null $condition = tr
return $this;
}
+ public function splitIndividualSearchTerms(bool | Closure | null $condition = true): static
+ {
+ $this->shouldSplitIndividualSearchTerms = $condition;
+
+ return $this;
+ }
+
/**
* @return array
*/
@@ -82,6 +91,11 @@ public function isSearchForcedCaseInsensitive(): ?bool
return $this->evaluate($this->isSearchForcedCaseInsensitive);
}
+ public function shouldSplitIndividualSearchTerms(): ?bool
+ {
+ return $this->evaluate($this->shouldSplitIndividualSearchTerms);
+ }
+
/**
* @return array{0: string}
*/
diff --git a/packages/tables/src/Concerns/CanSearchRecords.php b/packages/tables/src/Concerns/CanSearchRecords.php
index 21a451e7e05..d9a1db03a1c 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->shouldSplitIndividualSearchTerms() ?? $shouldSplitSearchTermsByDefault)) {
$isFirst = true;
$column->applySearchConstraint(
diff --git a/tests/src/Fixtures/Livewire/PostsTableWithColumnIndividualSearchTermSplittingDisabled.php b/tests/src/Fixtures/Livewire/PostsTableWithColumnIndividualSearchTermSplittingDisabled.php
new file mode 100644
index 00000000000..4da9fcfdcdf
--- /dev/null
+++ b/tests/src/Fixtures/Livewire/PostsTableWithColumnIndividualSearchTermSplittingDisabled.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)
+ ->splitIndividualSearchTerms(false),
+ ]);
+ }
+
+ public function render(): View
+ {
+ return view('livewire.table');
+ }
+}
diff --git a/tests/src/Fixtures/Livewire/PostsTableWithColumnIndividualSearchTermSplittingEnabled.php b/tests/src/Fixtures/Livewire/PostsTableWithColumnIndividualSearchTermSplittingEnabled.php
new file mode 100644
index 00000000000..8ff192a5963
--- /dev/null
+++ b/tests/src/Fixtures/Livewire/PostsTableWithColumnIndividualSearchTermSplittingEnabled.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)
+ ->splitIndividualSearchTerms(),
+ ]);
+ }
+
+ public function render(): View
+ {
+ return view('livewire.table');
+ }
+}
diff --git a/tests/src/Tables/ColumnTest.php b/tests/src/Tables/ColumnTest.php
index 8dd688a45f7..66e689b4fd6 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\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;
@@ -356,6 +358,44 @@
->assertCanNotSeeTableRecords($posts->where('author.email', '!=', $authorEmail));
});
+ 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(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 `splitIndividualSearchTerms()`', function (): void {
+ $exactPhrasePost = Post::factory()->create(['title' => 'ipsum dolor']);
+ $separateWordsPost = Post::factory()->create(['title' => 'ipsum sit dolor']);
+
+ livewire(PostsTableWithColumnIndividualSearchTermSplittingDisabled::class)
+ ->searchTableColumns(['title' => 'ipsum dolor'])
+ ->assertCanSeeTableRecords([$exactPhrasePost, $separateWordsPost]);
+ });
+
+ 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(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 `splitIndividualSearchTerms()`', function (): void {
+ $exactPhrasePost = Post::factory()->create(['title' => 'ipsum dolor']);
+ $separateWordsPost = Post::factory()->create(['title' => 'ipsum sit dolor']);
+
+ livewire(PostsTableWithColumnIndividualSearchTermSplittingEnabled::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' => '']);