@rowakit/table 0.5.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,56 +1,32 @@
1
1
  # @rowakit/table
2
2
 
3
- **Server-side-first React table for internal & business applications**
3
+ **Server-side-first React table for internal & business applications.**
4
+ Predictable API. Thin client. No data-grid bloat.
4
5
 
5
- RowaKit Table is an **opinionated React table component** designed for real-world internal apps and admin dashboards. It assumes **all data operations live on the server** and provides a clean, predictable API optimized for CRUD-style business screens.
6
+ ## Stability
6
7
 
7
- > If you are looking for a spreadsheet-like data grid with virtualization, grouping, or pivoting, this is intentionally **not** that library.
8
+ `@rowakit/table` is **stable as of v1.0.0**.
8
9
 
9
- ---
10
-
11
- ## Why RowaKit Table?
12
-
13
- Most React table libraries are **client-first** and optimized for maximum flexibility. RowaKit takes the opposite approach:
14
-
15
- * ✅ Server-side pagination, sorting, filtering by default
16
- * ✅ Minimal, convention-driven API (less boilerplate)
17
- * ✅ Strong TypeScript contracts between UI and backend
18
- * ✅ Built for long-lived internal tools, not demo-heavy grids
10
+ See:
11
+ - `docs/API_STABILITY.md`
12
+ - `docs/API_FREEZE_SUMMARY.md`
19
13
 
20
- This makes RowaKit especially suitable for:
14
+ ---
21
15
 
22
- * Admin dashboards
23
- * Back-office / internal tools
24
- * B2B SaaS management screens
25
- * Enterprise CRUD applications
16
+ ## Why @rowakit/table?
26
17
 
27
- ---
18
+ Most React table libraries grow into complex data grids.
19
+ RowaKit Table is intentionally different:
28
20
 
29
- ## Features
30
-
31
- * 🚀 **Server-side first** pagination, sorting, filtering handled by your backend
32
- * 🎯 **Type-safe** full TypeScript support with generics
33
- * 🧠 **Minimal API** – convention over configuration
34
- * 🪝 **Escape hatch** – `col.custom()` for full rendering control
35
- * 🎛️ **7 column types** – text, number, date, boolean, badge, actions, custom
36
- * 🖱️ **Column resizing** – drag handles, min/max width, double-click auto-fit (v0.4.0+)
37
- * 📌 **Saved views** – persist table state to localStorage (v0.4.0+)
38
- * 🔗 **URL sync** – share exact table state via query string (v0.4.0+)
39
- * 🧮 **Number range filters** – min/max with optional value transforms
40
- * ✅ **Row selection** – select/deselect rows with bulk header checkbox (v0.5.0+)
41
- * 🎬 **Bulk actions** – execute operations on multiple selected rows (v0.5.0+)
42
- * 💾 **CSV export** – server-triggered export with customizable formatter (v0.5.0+)
43
- * 🔄 **Multi-column sorting** – Ctrl+Click to sort by multiple columns with priority (v0.5.0+)
44
- * ♿ **Accessibility** – ARIA labels, keyboard navigation, focus management (v0.5.0+)
45
- * 🔄 **Smart fetching** – retry on error, stale request protection
46
- * ✅ **Built-in states** – loading, error, empty handled automatically
21
+ * Backend owns data logic (pagination, sorting, filtering)
22
+ * Frontend stays thin and predictable
23
+ * API is opinionated and stable
24
+ * Workflow features are built-in, not bolted on
47
25
 
48
26
  ---
49
27
 
50
28
  ## Installation
51
29
 
52
- RowaKit Table is published on npm and works with **npm**, **pnpm**, or **yarn**.
53
-
54
30
  ```bash
55
31
  npm install @rowakit/table
56
32
  # or
@@ -59,50 +35,41 @@ pnpm add @rowakit/table
59
35
  yarn add @rowakit/table
60
36
  ```
61
37
 
62
- ---
38
+ Import base styles:
63
39
 
64
- ## Quick Start (5 minutes)
40
+ ```ts
41
+ import '@rowakit/table/styles';
42
+ ```
65
43
 
66
- ### 1. Import
44
+ ---
45
+
46
+ ## Quick Start
67
47
 
68
48
  ```tsx
69
49
  import { RowaKitTable, col } from '@rowakit/table';
70
50
  import type { Fetcher } from '@rowakit/table';
71
51
  import '@rowakit/table/styles';
72
- ```
73
52
 
74
- ### 2. Define a fetcher (server contract)
53
+ type User = { id: string; name: string; email: string; active: boolean };
75
54
 
76
- ```tsx
77
- interface User {
78
- id: string;
79
- name: string;
80
- email: string;
81
- active: boolean;
82
- }
83
-
84
- const fetchUsers: Fetcher<User> = async ({ page, pageSize, sort, filters }) => {
55
+ const fetchUsers: Fetcher<User> = async ({ page, pageSize, sort }) => {
85
56
  const params = new URLSearchParams({
86
57
  page: String(page),
87
58
  pageSize: String(pageSize),
88
59
  });
89
60
 
90
61
  if (sort) {
91
- params.set('sortBy', sort.field);
62
+ params.set('sortField', sort.field);
92
63
  params.set('sortDir', sort.direction);
93
64
  }
94
65
 
95
66
  const res = await fetch(`/api/users?${params}`);
96
67
  if (!res.ok) throw new Error('Failed to fetch users');
97
68
 
98
- return res.json(); // { items: User[], total: number }
69
+ return res.json();
99
70
  };
100
- ```
101
71
 
102
- ### 3. Render the table
103
-
104
- ```tsx
105
- function UsersTable() {
72
+ export function UsersTable() {
106
73
  return (
107
74
  <RowaKitTable
108
75
  fetcher={fetchUsers}
@@ -112,7 +79,7 @@ function UsersTable() {
112
79
  col.text('email', { header: 'Email' }),
113
80
  col.boolean('active', { header: 'Active' }),
114
81
  col.actions([
115
- { id: 'edit', label: 'Edit', onClick: (row) => console.log(row) },
82
+ { id: 'edit', label: 'Edit' },
116
83
  { id: 'delete', label: 'Delete', confirm: true },
117
84
  ]),
118
85
  ]}
@@ -121,139 +88,179 @@ function UsersTable() {
121
88
  }
122
89
  ```
123
90
 
124
- That’s it — loading, error, pagination, sorting, and retry are handled automatically.
125
-
126
91
  ---
127
92
 
128
- ## Core Concepts
93
+ ## Features (v1.0.0)
129
94
 
130
- ### Fetcher Contract
95
+ ### Core table
96
+
97
+ * Server-side pagination, sorting, filtering
98
+ * Typed `Fetcher<T>` contract
99
+ * Built-in loading / error / empty states
100
+ * Stale request protection
101
+
102
+ ### Columns
103
+
104
+ * `col.text`
105
+ * `col.number`
106
+ * `col.date`
107
+ * `col.boolean`
108
+ * `col.badge`
109
+ * `col.actions`
110
+ * `col.custom`
111
+
112
+ ### UX & workflows
113
+
114
+ * Column resizing (pointer events)
115
+ * Double-click auto-fit
116
+ * URL sync
117
+ * Saved views
118
+ * Row selection (page-scoped)
119
+ * Bulk actions
120
+ * Export via `exporter` callback
121
+
122
+ ---
131
123
 
132
- The **Fetcher** defines the contract between the table and your backend.
124
+ ## Fetcher Contract
133
125
 
134
126
  ```ts
135
127
  type Fetcher<T> = (query: {
136
128
  page: number;
137
129
  pageSize: number;
130
+ /** Deprecated (kept for backward compatibility; planned removal in v2.0.0). */
138
131
  sort?: { field: string; direction: 'asc' | 'desc' };
132
+ /** Multi-column sorting (preferred). */
133
+ sorts?: Array<{ field: string; direction: 'asc' | 'desc'; priority: number }>;
139
134
  filters?: Record<string, unknown>;
140
135
  }) => Promise<{ items: T[]; total: number }>;
141
136
  ```
142
137
 
143
- * Fetcher is called on mount and whenever table state changes
144
- * Throw an error to trigger the built-in error + retry UI
145
- * Stale requests are ignored automatically
138
+ Guidelines:
146
139
 
147
- ---
148
-
149
- ## Column API
140
+ * Backend is the source of truth
141
+ * Throw errors to trigger built-in error UI
142
+ * Ignore stale requests (handled internally)
150
143
 
151
- RowaKit provides a **column factory API** via `col.*` helpers.
144
+ ---
152
145
 
153
- ### Basic columns
146
+ ## Row Selection
154
147
 
155
- ```ts
156
- col.text('name')
157
- col.number('price', { format: { style: 'currency', currency: 'USD' } })
158
- col.date('createdAt', { sortable: true })
159
- col.boolean('active')
148
+ ```tsx
149
+ <RowaKitTable
150
+ enableRowSelection
151
+ onSelectionChange={(keys) => console.log(keys)}
152
+ fetcher={fetchUsers}
153
+ columns={[/* ... */]}
154
+ />
160
155
  ```
161
156
 
162
- ### Badge column (enum/status)
163
-
164
- ```ts
165
- col.badge('status', {
166
- header: 'Status',
167
- sortable: true,
168
- map: {
169
- active: { label: 'Active', tone: 'success' },
170
- pending: { label: 'Pending', tone: 'warning' },
171
- error: { label: 'Error', tone: 'danger' },
172
- },
173
- });
174
- ```
157
+ * Selection is page-scoped
158
+ * Resets on page change
175
159
 
176
- ### Actions column
160
+ ---
177
161
 
178
- ```ts
179
- col.actions([
180
- { id: 'edit', label: 'Edit', onClick: (row) => edit(row) },
181
- { id: 'delete', label: 'Delete', confirm: true, onClick: (row) => remove(row) },
182
- ]);
183
- ```
162
+ ## Multi-Column Sorting
184
163
 
185
- ### Custom column (escape hatch)
164
+ Sort by multiple columns simultaneously using **Ctrl+Click** (Windows/Linux) or **Cmd+Click** (Mac) on column headers:
186
165
 
187
166
  ```tsx
188
- col.custom('user', (row) => (
189
- <div style={{ display: 'flex', gap: 8 }}>
190
- <img src={row.avatar} width={24} />
191
- <span>{row.name}</span>
192
- </div>
193
- ));
194
- ```
167
+ // Hold Ctrl/Cmd and click column headers in order
168
+ // Priority is determined by click order (first click = priority 1)
169
+
170
+ // The fetcher receives sorts array:
171
+ const fetcher = async (query: FetcherQuery) => {
172
+ // query.sorts = [
173
+ // { field: 'lastName', direction: 'asc', priority: 1 },
174
+ // { field: 'firstName', direction: 'asc', priority: 2 },
175
+ // { field: 'salary', direction: 'desc', priority: 3 }
176
+ // ]
177
+ const res = await fetch('/api/users', {
178
+ method: 'POST',
179
+ body: JSON.stringify(query),
180
+ });
181
+ return res.json();
182
+ };
195
183
 
196
- ---
184
+ <RowaKitTable fetcher={fetcher} columns={[/* ... */]} />
185
+ ```
197
186
 
198
- ## Advanced Features (v0.4.0+)
187
+ **Migration from deprecated `sort` field:**
188
+ - Old format: `query.sort = { field: 'name', direction: 'asc' }`
189
+ - New format: `query.sorts = [{ field: 'name', direction: 'asc', priority: 1 }]`
190
+ - Both fields coexist during transition; `sort` will be removed in v2.0.0
199
191
 
200
- ### Column Resizing
192
+ **UI Indicators:**
193
+ - Single column: Standard sort arrow indicator
194
+ - Multiple columns: Priority number displayed on sorted column headers
201
195
 
202
- * Drag handle on header edge
203
- * Min/max width constraints
204
- * Double-click to auto-fit content
205
- * Pointer Events (mouse / touch / pen)
206
- * No accidental sort while resizing
196
+ ---
207
197
 
208
- ### Saved Views + URL Sync
198
+ ## Bulk Actions
209
199
 
210
- * Persist page, sort, filters, and column widths
211
- * Share URLs that restore exact table state
212
- * Named views saved to localStorage
213
- * Safe parsing & corruption tolerance
200
+ ```tsx
201
+ <RowaKitTable
202
+ enableRowSelection
203
+ bulkActions={[
204
+ {
205
+ id: 'delete',
206
+ label: 'Delete selected',
207
+ confirm: { title: 'Confirm delete' },
208
+ onClick: (keys) => console.log(keys),
209
+ },
210
+ ]}
211
+ fetcher={fetchUsers}
212
+ columns={[/* ... */]}
213
+ />
214
+ ```
214
215
 
215
216
  ---
216
217
 
217
- ## Styling
218
+ ## Export (CSV)
219
+
220
+ ```tsx
221
+ const exporter = async (query) => {
222
+ const res = await fetch('/api/export', {
223
+ method: 'POST',
224
+ body: JSON.stringify(query),
225
+ });
218
226
 
219
- RowaKit ships with minimal default styles via CSS variables.
227
+ const { url } = await res.json();
228
+ return { url };
229
+ };
220
230
 
221
- ```ts
222
- import '@rowakit/table/styles';
231
+ <RowaKitTable exporter={exporter} fetcher={fetchUsers} columns={[/* ... */]} />
223
232
  ```
224
233
 
225
- You can:
226
-
227
- * Override CSS variables for theming
228
- * Use `className` to scope custom styles
229
- * Skip default styles and fully style from scratch
234
+ Export is server-triggered and scales well for large datasets.
230
235
 
231
236
  ---
232
237
 
233
- ## Philosophy & Scope
238
+ ## Roadmap & Versioning
234
239
 
235
- * **Server-side first** – client stays thin
236
- * **Small core** no grid bloat
237
- * **Clear escape hatch** `col.custom()`
238
- * **Business tables ≠ spreadsheets**
240
+ * Current: **1.0.0** (stable)
241
+ * No breaking changes in 1.x (breaking changes require v2.0.0)
242
+ * Public API stability policy applies from v1.0.0
239
243
 
240
- See the scope lock and rationale in the root repository docs.
244
+ See roadmap: [docs/ROADMAP.md](docs/ROADMAP.md)
241
245
 
242
246
  ---
243
247
 
244
- ## Versioning & Roadmap
248
+ ## Support RowaKit
249
+
250
+ If RowaKit helps your team:
251
+
252
+ * ⭐ Star the repo
253
+ * 💖 [Sponsor on GitHub](https://github.com/sponsors/midflow)
254
+ * ☕ [Buy us a coffee](https://buymeacoffee.com/midflow)
245
255
 
246
- * Current: **v0.5.x** (Stage E row selection, bulk actions, export, multi-sort, a11y)
247
- * API is stable; patches are backward compatible
248
- * Completed: Stages A-E with full feature set for internal business applications
249
- * See [CHANGELOG.md](./CHANGELOG.md) for detailed v0.5.0 features and [docs/ROADMAP.md](../../docs/ROADMAP.md)
256
+ Every bit of support helps sustain long-term maintenance.
250
257
 
251
258
  ---
252
259
 
253
260
  ## License
254
261
 
255
- MIT
262
+ MIT © RowaKit Contributors
256
263
 
257
264
  ---
258
265
 
259
- Built for teams shipping serious internal tools, not toy demos.
266
+ **Built for teams shipping internal tools, not demos.**
package/dist/index.cjs CHANGED
@@ -569,11 +569,22 @@ function parseUrlState(params, defaultPageSize, pageSizeOptions) {
569
569
  } catch {
570
570
  }
571
571
  }
572
+ if (!result.sorts) {
573
+ const sortStr = params.get("sort");
574
+ if (sortStr) {
575
+ const [field, dir] = sortStr.split(":");
576
+ if (field && (dir === "asc" || dir === "desc")) {
577
+ result.sort = { field, direction: dir };
578
+ result.sorts = [{ field, direction: dir, priority: 0 }];
579
+ }
580
+ }
581
+ }
572
582
  if (!result.sorts) {
573
583
  const sortField = params.get("sortField");
574
584
  const sortDir = params.get("sortDirection");
575
585
  if (sortField && (sortDir === "asc" || sortDir === "desc")) {
576
586
  result.sort = { field: sortField, direction: sortDir };
587
+ result.sorts = [{ field: sortField, direction: sortDir, priority: 0 }];
577
588
  }
578
589
  }
579
590
  const filtersStr = params.get("filters");
@@ -645,40 +656,59 @@ function useUrlSync({
645
656
  setColumnWidths
646
657
  }) {
647
658
  const didHydrateUrlRef = react.useRef(false);
648
- const didSkipInitialUrlSyncRef = react.useRef(false);
649
659
  const urlSyncDebounceRef = react.useRef(null);
660
+ const isApplyingUrlStateRef = react.useRef(false);
661
+ const clearApplyingTimerRef = react.useRef(null);
662
+ const hasWrittenUrlRef = react.useRef(false);
663
+ const lastQueryForUrlRef = react.useRef(null);
664
+ const defaultPageSizeRef = react.useRef(defaultPageSize);
665
+ const pageSizeOptionsRef = react.useRef(pageSizeOptions);
666
+ const enableColumnResizingRef = react.useRef(enableColumnResizing);
667
+ const columnsRef = react.useRef(columns);
668
+ defaultPageSizeRef.current = defaultPageSize;
669
+ pageSizeOptionsRef.current = pageSizeOptions;
670
+ enableColumnResizingRef.current = enableColumnResizing;
671
+ columnsRef.current = columns;
650
672
  react.useEffect(() => {
651
673
  if (!syncToUrl) {
652
- didSkipInitialUrlSyncRef.current = false;
653
- return;
654
- }
655
- if (!didSkipInitialUrlSyncRef.current) {
656
- didSkipInitialUrlSyncRef.current = true;
674
+ hasWrittenUrlRef.current = false;
675
+ lastQueryForUrlRef.current = null;
657
676
  return;
658
677
  }
678
+ if (!didHydrateUrlRef.current) return;
679
+ if (isApplyingUrlStateRef.current) return;
659
680
  if (urlSyncDebounceRef.current) {
660
681
  clearTimeout(urlSyncDebounceRef.current);
661
682
  urlSyncDebounceRef.current = null;
662
683
  }
663
- const urlStr = serializeUrlState(query, filters, columnWidths, defaultPageSize, enableColumnResizing);
684
+ const urlStr = serializeUrlState(query, filters, columnWidths, defaultPageSizeRef.current, enableColumnResizingRef.current);
664
685
  const qs = urlStr ? `?${urlStr}` : "";
665
- window.history.replaceState(null, "", `${window.location.pathname}${qs}${window.location.hash}`);
686
+ const nextUrl = `${window.location.pathname}${qs}${window.location.hash}`;
687
+ const prevQuery = lastQueryForUrlRef.current;
688
+ const shouldPush = hasWrittenUrlRef.current && prevQuery != null && prevQuery.page !== query.page;
689
+ if (shouldPush) {
690
+ window.history.pushState(null, "", nextUrl);
691
+ } else {
692
+ window.history.replaceState(null, "", nextUrl);
693
+ }
694
+ hasWrittenUrlRef.current = true;
695
+ lastQueryForUrlRef.current = query;
666
696
  }, [
667
697
  query,
668
698
  filters,
669
699
  syncToUrl,
670
- enableColumnResizing,
671
- defaultPageSize,
672
700
  columnWidths
673
701
  ]);
674
702
  react.useEffect(() => {
675
- if (!syncToUrl || !enableColumnResizing) return;
676
- if (!didSkipInitialUrlSyncRef.current) return;
703
+ if (!syncToUrl || !enableColumnResizingRef.current) return;
704
+ if (!didHydrateUrlRef.current) return;
705
+ if (!hasWrittenUrlRef.current) return;
706
+ if (isApplyingUrlStateRef.current) return;
677
707
  if (urlSyncDebounceRef.current) {
678
708
  clearTimeout(urlSyncDebounceRef.current);
679
709
  }
680
710
  urlSyncDebounceRef.current = setTimeout(() => {
681
- const urlStr = serializeUrlState(query, filters, columnWidths, defaultPageSize, enableColumnResizing);
711
+ const urlStr = serializeUrlState(query, filters, columnWidths, defaultPageSizeRef.current, enableColumnResizingRef.current);
682
712
  const qs = urlStr ? `?${urlStr}` : "";
683
713
  window.history.replaceState(null, "", `${window.location.pathname}${qs}${window.location.hash}`);
684
714
  urlSyncDebounceRef.current = null;
@@ -692,36 +722,52 @@ function useUrlSync({
692
722
  }, [
693
723
  columnWidths,
694
724
  syncToUrl,
695
- enableColumnResizing,
696
725
  query,
697
- filters,
698
- defaultPageSize
726
+ filters
699
727
  ]);
700
- react.useEffect(() => {
701
- if (!syncToUrl) {
702
- didHydrateUrlRef.current = false;
703
- return;
728
+ function scheduleClearApplyingFlag() {
729
+ if (clearApplyingTimerRef.current) {
730
+ clearTimeout(clearApplyingTimerRef.current);
731
+ clearApplyingTimerRef.current = null;
704
732
  }
705
- if (didHydrateUrlRef.current) return;
706
- didHydrateUrlRef.current = true;
733
+ clearApplyingTimerRef.current = setTimeout(() => {
734
+ isApplyingUrlStateRef.current = false;
735
+ clearApplyingTimerRef.current = null;
736
+ }, 0);
737
+ }
738
+ function applyUrlToState() {
707
739
  const params = new URLSearchParams(window.location.search);
708
- const parsed = parseUrlState(params, defaultPageSize, pageSizeOptions);
709
- setQuery({
740
+ const parsed = parseUrlState(params, defaultPageSizeRef.current, pageSizeOptionsRef.current);
741
+ const nextQuery = {
710
742
  page: parsed.page,
711
743
  pageSize: parsed.pageSize,
712
744
  sort: parsed.sort,
713
745
  sorts: parsed.sorts,
714
746
  filters: parsed.filters
715
- });
716
- if (parsed.filters) {
717
- setFilters(parsed.filters);
747
+ };
748
+ isApplyingUrlStateRef.current = true;
749
+ scheduleClearApplyingFlag();
750
+ setQuery(nextQuery);
751
+ setFilters(parsed.filters ?? {});
752
+ if (!hasWrittenUrlRef.current) {
753
+ const urlStr = serializeUrlState(
754
+ nextQuery,
755
+ parsed.filters ?? {},
756
+ parsed.columnWidths ?? {},
757
+ defaultPageSizeRef.current,
758
+ enableColumnResizingRef.current
759
+ );
760
+ const qs = urlStr ? `?${urlStr}` : "";
761
+ window.history.replaceState(null, "", `${window.location.pathname}${qs}${window.location.hash}`);
762
+ hasWrittenUrlRef.current = true;
763
+ lastQueryForUrlRef.current = nextQuery;
718
764
  }
719
- if (parsed.columnWidths && enableColumnResizing) {
765
+ if (enableColumnResizingRef.current && parsed.columnWidths) {
720
766
  const clamped = {};
721
767
  for (const [colId, rawWidth] of Object.entries(parsed.columnWidths)) {
722
768
  const widthNum = typeof rawWidth === "number" ? rawWidth : Number(rawWidth);
723
769
  if (!Number.isFinite(widthNum)) continue;
724
- const colDef = columns.find((c) => c.id === colId);
770
+ const colDef = columnsRef.current.find((c) => c.id === colId);
725
771
  if (!colDef) continue;
726
772
  const minW = colDef.minWidth ?? 80;
727
773
  const maxW = colDef.maxWidth;
@@ -732,17 +778,44 @@ function useUrlSync({
732
778
  clamped[colId] = finalW;
733
779
  }
734
780
  setColumnWidths(clamped);
781
+ } else if (enableColumnResizingRef.current) {
782
+ setColumnWidths({});
735
783
  }
784
+ }
785
+ react.useEffect(() => {
786
+ if (!syncToUrl) {
787
+ didHydrateUrlRef.current = false;
788
+ hasWrittenUrlRef.current = false;
789
+ lastQueryForUrlRef.current = null;
790
+ return;
791
+ }
792
+ if (didHydrateUrlRef.current) return;
793
+ didHydrateUrlRef.current = true;
794
+ applyUrlToState();
736
795
  }, [
737
796
  syncToUrl,
738
- defaultPageSize,
739
- enableColumnResizing,
740
- pageSizeOptions,
741
- columns,
742
797
  setQuery,
743
798
  setFilters,
744
799
  setColumnWidths
745
800
  ]);
801
+ react.useEffect(() => {
802
+ if (!syncToUrl) return;
803
+ const onPopState = () => {
804
+ applyUrlToState();
805
+ };
806
+ window.addEventListener("popstate", onPopState);
807
+ return () => {
808
+ window.removeEventListener("popstate", onPopState);
809
+ };
810
+ }, [syncToUrl, setQuery, setFilters, setColumnWidths]);
811
+ react.useEffect(() => {
812
+ return () => {
813
+ if (clearApplyingTimerRef.current) {
814
+ clearTimeout(clearApplyingTimerRef.current);
815
+ clearApplyingTimerRef.current = null;
816
+ }
817
+ };
818
+ }, []);
746
819
  }
747
820
  var FOCUSABLE_SELECTORS = [
748
821
  "button",
@@ -1093,9 +1166,13 @@ function RowaKitTable({
1093
1166
  const headerChecked = isAllSelected(selectedKeys, pageRowKeys);
1094
1167
  const headerIndeterminate = isIndeterminate(selectedKeys, pageRowKeys);
1095
1168
  react.useEffect(() => {
1096
- if (!enableRowSelection) return;
1097
1169
  setSelectedKeys(clearSelection());
1098
- }, [enableRowSelection, query.page, dataState.items]);
1170
+ }, [query.page]);
1171
+ react.useEffect(() => {
1172
+ if (!enableRowSelection) {
1173
+ setSelectedKeys(clearSelection());
1174
+ }
1175
+ }, [enableRowSelection]);
1099
1176
  react.useEffect(() => {
1100
1177
  if (!enableRowSelection || !onSelectionChange) return;
1101
1178
  onSelectionChange(selectedKeys);
@@ -1772,7 +1849,7 @@ function RowaKitTable({
1772
1849
  var SmartTable = RowaKitTable;
1773
1850
 
1774
1851
  // src/index.ts
1775
- var VERSION = "0.5.0" ;
1852
+ var VERSION = "1.0.0" ;
1776
1853
 
1777
1854
  exports.RowaKitTable = RowaKitTable;
1778
1855
  exports.SmartTable = SmartTable;