diff --git a/resources/js/components/index.js b/resources/js/components/index.js
index 28301645..f3a7bdb6 100644
--- a/resources/js/components/index.js
+++ b/resources/js/components/index.js
@@ -10,6 +10,7 @@ import './select/slimSelect'
import './pg-dropdown'
import './pg-export'
import './pg-toggle-detail'
+import './pg-select-all'
import './pg-filter-panel'
import './directives'
import '../plugins'
diff --git a/resources/js/components/pg-select-all.js b/resources/js/components/pg-select-all.js
new file mode 100644
index 00000000..a8e182c1
--- /dev/null
+++ b/resources/js/components/pg-select-all.js
@@ -0,0 +1,62 @@
+window.pgAlpine.data('pgSelectAll', () => ({
+ init() {
+ this.pgTable = this.$el.closest('table')
+
+ this.onRowChange = (event) => {
+ const target = event.target
+
+ if (target === this.$el || !(target instanceof HTMLInputElement) || target.type !== 'checkbox') {
+ return
+ }
+
+ this.refresh()
+ }
+
+ this.pgTable?.addEventListener('change', this.onRowChange)
+
+ this.onFinished = () => this.refresh()
+ window.addEventListener('pg-select-all:refresh', this.onFinished)
+
+ this.$nextTick(() => this.refresh())
+ },
+
+ destroy() {
+ this.pgTable?.removeEventListener('change', this.onRowChange)
+ window.removeEventListener('pg-select-all:refresh', this.onFinished)
+ },
+
+ refresh() {
+ const table = this.pgTable
+
+ if (!table) {
+ return
+ }
+
+ const boxes = Array.from(table.querySelectorAll('tbody input[type="checkbox"]'))
+ .filter((checkbox) => !checkbox.disabled)
+
+ const total = boxes.length
+ const checked = boxes.filter((checkbox) => checkbox.checked).length
+
+ this.$el.checked = total > 0 && checked === total
+ this.$el.indeterminate = checked > 0 && checked < total
+ },
+}))
+
+if (!window.pgSelectAllCommitHook) {
+ window.pgSelectAllCommitHook = true
+
+ document.addEventListener('DOMContentLoaded', () => {
+ if (!window.Livewire) {
+ return
+ }
+
+ Livewire.hook('commit', ({ succeed }) => {
+ succeed(() => {
+ requestAnimationFrame(() => {
+ window.dispatchEvent(new CustomEvent('pg-select-all:refresh'))
+ })
+ })
+ })
+ })
+}
diff --git a/resources/views/components/structure/table/checkbox-all.blade.php b/resources/views/components/structure/table/checkbox-all.blade.php
index dd5b071d..811248d9 100644
--- a/resources/views/components/structure/table/checkbox-all.blade.php
+++ b/resources/views/components/structure/table/checkbox-all.blade.php
@@ -25,10 +25,10 @@
diff --git a/resources/views/components/themes/tailwind/table/checkbox-all.blade.php b/resources/views/components/themes/tailwind/table/checkbox-all.blade.php
index 6f99e3c4..f9d38969 100644
--- a/resources/views/components/themes/tailwind/table/checkbox-all.blade.php
+++ b/resources/views/components/themes/tailwind/table/checkbox-all.blade.php
@@ -25,10 +25,10 @@
diff --git a/src/Concerns/Checkbox.php b/src/Concerns/Checkbox.php
index 659a95a8..d32ae9d6 100644
--- a/src/Concerns/Checkbox.php
+++ b/src/Concerns/Checkbox.php
@@ -26,8 +26,12 @@ trait Checkbox
/**
* @throws Exception|Throwable
*/
- public function selectCheckboxAll(): void
+ public function selectCheckboxAll(?bool $checked = null): void
{
+ if ($checked !== null) {
+ $this->checkboxAll = $checked;
+ }
+
if (! $this->checkboxAll) {
$this->checkboxValues = [];
diff --git a/src/PowerGridComponent.php b/src/PowerGridComponent.php
index e22f65e9..a3f90962 100644
--- a/src/PowerGridComponent.php
+++ b/src/PowerGridComponent.php
@@ -112,6 +112,10 @@ public function setSummaryValues(array $values): void
public function resetToFirstPage(string $pageName = 'page'): void
{
+ if ((int) $this->getPage($pageName) === 1) {
+ return;
+ }
+
$this->gotoPage(1, $pageName);
}
diff --git a/tests/Feature/CheckboxTest.php b/tests/Feature/CheckboxTest.php
index 29cb22ff..6d7b4c7e 100644
--- a/tests/Feature/CheckboxTest.php
+++ b/tests/Feature/CheckboxTest.php
@@ -65,6 +65,147 @@ public function columns(): array
->toBe([]);
});
+it('resetToFirstPage does not navigate when already on the first page', function () {
+ $component = new class() extends PowerGridComponent
+ {
+ public string $tableName = 'test-reset-first-page';
+
+ /** @var array */
+ public array $gotoPageCalls = [];
+
+ public function datasource()
+ {
+ return collect([
+ ['id' => 1, 'name' => 'Dish 1'],
+ ['id' => 2, 'name' => 'Dish 2'],
+ ]);
+ }
+
+ public function gotoPage($page, $pageName = 'page'): void
+ {
+ $this->gotoPageCalls[] = $page;
+
+ parent::gotoPage($page, $pageName);
+ }
+
+ public function setUp(): array
+ {
+ return [
+ PowerGrid::footer()->showPerPage(10),
+ ];
+ }
+
+ public function fields(): PowerGridFields
+ {
+ return PowerGrid::fields()->add('id')->add('name');
+ }
+
+ public function columns(): array
+ {
+ return [Column::make('Id', 'id'), Column::make('Name', 'name')];
+ }
+ };
+
+ $lw = Livewire::test($component::class);
+
+ $lw->call('resetToFirstPage');
+ expect($lw->get('gotoPageCalls'))->toBe([]);
+
+ $lw->call('gotoPage', 3)
+ ->call('resetToFirstPage');
+ expect($lw->get('gotoPageCalls'))->toContain(1);
+});
+
+it('preserves the row selection when the per-page size changes', function () {
+ $component = new class() extends PowerGridComponent
+ {
+ public string $tableName = 'test-checkbox-perpage-change';
+
+ public function datasource()
+ {
+ $data = [];
+ for ($i = 1; $i <= 30; $i++) {
+ $data[] = ['id' => $i, 'name' => 'Dish '.$i];
+ }
+
+ return collect($data);
+ }
+
+ public function setUp(): array
+ {
+ $this->showCheckBox();
+
+ return [
+ PowerGrid::footer()->showPerPage(10, [10, 30]),
+ ];
+ }
+
+ public function fields(): PowerGridFields
+ {
+ return PowerGrid::fields()->add('id')->add('name');
+ }
+
+ public function columns(): array
+ {
+ return [Column::make('Id', 'id'), Column::make('Name', 'name')];
+ }
+ };
+
+ $lw = Livewire::test($component::class)
+ ->set('checkboxAll', true)
+ ->call('selectCheckboxAll');
+
+ expect($lw->checkboxValues)->toMatchArray(range(1, 10));
+
+ $lw->set('setUp.footer.perPage', 30);
+
+ expect($lw->checkboxValues)->toMatchArray(range(1, 10));
+});
+
+it('selectCheckboxAll honours the explicit checked intent argument', function () {
+ $component = new class() extends PowerGridComponent
+ {
+ public string $tableName = 'test-checkbox-intent';
+
+ public function datasource()
+ {
+ return collect([
+ ['id' => 1, 'name' => 'Dish 1'],
+ ['id' => 2, 'name' => 'Dish 2'],
+ ['id' => 3, 'name' => 'Dish 3'],
+ ]);
+ }
+
+ public function setUp(): array
+ {
+ $this->showCheckBox();
+
+ return [PowerGrid::footer()->showPerPage(10)];
+ }
+
+ public function fields(): PowerGridFields
+ {
+ return PowerGrid::fields()->add('id')->add('name');
+ }
+
+ public function columns(): array
+ {
+ return [Column::make('Id', 'id'), Column::make('Name', 'name')];
+ }
+ };
+
+ $lw = Livewire::test($component::class)
+ ->call('selectCheckboxAll', true);
+
+ expect($lw->get('checkboxAll'))->toBeTrue()
+ ->and($lw->checkboxValues)->toMatchArray(['1', '2', '3']);
+
+ $lw->call('selectCheckboxAll', false);
+
+ expect($lw->get('checkboxAll'))->toBeFalse()
+ ->and($lw->checkboxValues)->toBe([]);
+});
+
it('selectCheckboxAll works properly with actionRules disable', function () {
$component = new class() extends PowerGridComponent
{