@rowakit/table 0.4.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 +157 -1430
- package/dist/index.cjs +1190 -635
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +49 -4
- package/dist/index.d.ts +49 -4
- package/dist/index.js +1191 -636
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState, useRef, useEffect } from 'react';
|
|
1
|
+
import { useState, useRef, useEffect, useMemo } from 'react';
|
|
2
2
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
3
3
|
|
|
4
4
|
// src/column-helpers.ts
|
|
@@ -103,20 +103,178 @@ var col = {
|
|
|
103
103
|
actions,
|
|
104
104
|
custom
|
|
105
105
|
};
|
|
106
|
-
function
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
106
|
+
function useColumnResizing(columns) {
|
|
107
|
+
const [columnWidths, setColumnWidths] = useState({});
|
|
108
|
+
const resizeRafRef = useRef(null);
|
|
109
|
+
const resizePendingRef = useRef(null);
|
|
110
|
+
const tableRef = useRef(null);
|
|
111
|
+
const isResizingRef = useRef(false);
|
|
112
|
+
const lastResizeEndTsRef = useRef(0);
|
|
113
|
+
const resizingColIdRef = useRef(null);
|
|
114
|
+
const scheduleColumnWidthUpdate = (colId, width) => {
|
|
115
|
+
resizePendingRef.current = { colId, width };
|
|
116
|
+
if (resizeRafRef.current != null) return;
|
|
117
|
+
resizeRafRef.current = requestAnimationFrame(() => {
|
|
118
|
+
resizeRafRef.current = null;
|
|
119
|
+
const pending = resizePendingRef.current;
|
|
120
|
+
if (!pending) return;
|
|
121
|
+
handleColumnResize(pending.colId, pending.width);
|
|
122
|
+
});
|
|
123
|
+
};
|
|
124
|
+
const handleColumnResize = (columnId, newWidth) => {
|
|
125
|
+
const minWidth = columns.find((c) => c.id === columnId)?.minWidth ?? 80;
|
|
126
|
+
const maxWidth = columns.find((c) => c.id === columnId)?.maxWidth;
|
|
127
|
+
let finalWidth = Math.max(minWidth, newWidth);
|
|
128
|
+
if (maxWidth) {
|
|
129
|
+
finalWidth = Math.min(finalWidth, maxWidth);
|
|
130
|
+
}
|
|
131
|
+
if (columnWidths[columnId] === finalWidth) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
setColumnWidths((prev) => ({
|
|
135
|
+
...prev,
|
|
136
|
+
[columnId]: finalWidth
|
|
137
|
+
}));
|
|
138
|
+
};
|
|
139
|
+
const autoFitColumnWidth = (columnId) => {
|
|
140
|
+
const tableEl = tableRef.current;
|
|
141
|
+
if (!tableEl) return;
|
|
142
|
+
const th = tableEl.querySelector(`th[data-col-id="${columnId}"]`);
|
|
143
|
+
if (!th) return;
|
|
144
|
+
const tds = Array.from(
|
|
145
|
+
tableEl.querySelectorAll(`td[data-col-id="${columnId}"]`)
|
|
146
|
+
);
|
|
147
|
+
const headerW = th.scrollWidth;
|
|
148
|
+
const cellsMaxW = tds.reduce((max, td) => Math.max(max, td.scrollWidth), 0);
|
|
149
|
+
const padding = 24;
|
|
150
|
+
const raw = Math.max(headerW, cellsMaxW) + padding;
|
|
151
|
+
const colDef = columns.find((c) => c.id === columnId);
|
|
152
|
+
const minW = colDef?.minWidth ?? 80;
|
|
153
|
+
const maxW = colDef?.maxWidth ?? 600;
|
|
154
|
+
const finalW = Math.max(minW, Math.min(raw, maxW));
|
|
155
|
+
setColumnWidths((prev) => ({ ...prev, [columnId]: finalW }));
|
|
156
|
+
};
|
|
157
|
+
const startColumnResize = (e, columnId) => {
|
|
158
|
+
e.preventDefault();
|
|
159
|
+
e.stopPropagation();
|
|
160
|
+
if (e.detail === 2) {
|
|
161
|
+
autoFitColumnWidth(columnId);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (e.pointerType === "mouse" && e.buttons !== 1) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
const target = e.currentTarget;
|
|
168
|
+
const pointerId = e.pointerId;
|
|
169
|
+
try {
|
|
170
|
+
target.setPointerCapture(pointerId);
|
|
171
|
+
} catch {
|
|
172
|
+
}
|
|
173
|
+
isResizingRef.current = true;
|
|
174
|
+
resizingColIdRef.current = columnId;
|
|
175
|
+
const startX = e.clientX;
|
|
176
|
+
const th = target.parentElement;
|
|
177
|
+
let startWidth = columnWidths[columnId] ?? th.offsetWidth;
|
|
178
|
+
const MIN_DRAG_WIDTH = 80;
|
|
179
|
+
if (startWidth < MIN_DRAG_WIDTH) {
|
|
180
|
+
const nextTh = th.nextElementSibling;
|
|
181
|
+
if (nextTh && nextTh.offsetWidth >= 50) {
|
|
182
|
+
startWidth = nextTh.offsetWidth;
|
|
183
|
+
} else {
|
|
184
|
+
startWidth = 100;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
document.body.classList.add("rowakit-resizing");
|
|
188
|
+
const handlePointerMove = (moveEvent) => {
|
|
189
|
+
const delta = moveEvent.clientX - startX;
|
|
190
|
+
const newWidth = startWidth + delta;
|
|
191
|
+
scheduleColumnWidthUpdate(columnId, newWidth);
|
|
192
|
+
};
|
|
193
|
+
const cleanupResize = () => {
|
|
194
|
+
target.removeEventListener("pointermove", handlePointerMove);
|
|
195
|
+
target.removeEventListener("pointerup", handlePointerUp);
|
|
196
|
+
target.removeEventListener("pointercancel", handlePointerCancel);
|
|
197
|
+
document.body.classList.remove("rowakit-resizing");
|
|
198
|
+
isResizingRef.current = false;
|
|
199
|
+
resizingColIdRef.current = null;
|
|
200
|
+
lastResizeEndTsRef.current = Date.now();
|
|
201
|
+
try {
|
|
202
|
+
target.releasePointerCapture(pointerId);
|
|
203
|
+
} catch {
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
const handlePointerUp = () => {
|
|
207
|
+
cleanupResize();
|
|
208
|
+
};
|
|
209
|
+
const handlePointerCancel = () => {
|
|
210
|
+
cleanupResize();
|
|
211
|
+
};
|
|
212
|
+
target.addEventListener("pointermove", handlePointerMove);
|
|
213
|
+
target.addEventListener("pointerup", handlePointerUp);
|
|
214
|
+
target.addEventListener("pointercancel", handlePointerCancel);
|
|
215
|
+
};
|
|
216
|
+
const handleColumnResizeDoubleClick = (e, columnId) => {
|
|
217
|
+
e.preventDefault();
|
|
218
|
+
e.stopPropagation();
|
|
219
|
+
autoFitColumnWidth(columnId);
|
|
220
|
+
};
|
|
221
|
+
return {
|
|
222
|
+
tableRef,
|
|
223
|
+
columnWidths,
|
|
224
|
+
setColumnWidths,
|
|
225
|
+
startColumnResize,
|
|
226
|
+
handleColumnResizeDoubleClick,
|
|
227
|
+
isResizingRef,
|
|
228
|
+
lastResizeEndTsRef,
|
|
229
|
+
resizingColIdRef
|
|
230
|
+
};
|
|
117
231
|
}
|
|
118
|
-
function
|
|
119
|
-
|
|
232
|
+
function useFetcherState(fetcher, query, setQuery) {
|
|
233
|
+
const [dataState, setDataState] = useState({
|
|
234
|
+
state: "idle",
|
|
235
|
+
items: [],
|
|
236
|
+
total: 0
|
|
237
|
+
});
|
|
238
|
+
const requestIdRef = useRef(0);
|
|
239
|
+
useEffect(() => {
|
|
240
|
+
const currentRequestId = ++requestIdRef.current;
|
|
241
|
+
setDataState((prev) => ({ ...prev, state: "loading" }));
|
|
242
|
+
fetcher(query).then((result) => {
|
|
243
|
+
if (currentRequestId !== requestIdRef.current) return;
|
|
244
|
+
if (result.items.length === 0) {
|
|
245
|
+
setDataState({
|
|
246
|
+
state: "empty",
|
|
247
|
+
items: [],
|
|
248
|
+
total: result.total
|
|
249
|
+
});
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
setDataState({
|
|
253
|
+
state: "success",
|
|
254
|
+
items: result.items,
|
|
255
|
+
total: result.total
|
|
256
|
+
});
|
|
257
|
+
}).catch((error) => {
|
|
258
|
+
if (currentRequestId !== requestIdRef.current) return;
|
|
259
|
+
setDataState({
|
|
260
|
+
state: "error",
|
|
261
|
+
items: [],
|
|
262
|
+
total: 0,
|
|
263
|
+
error: error instanceof Error ? error.message : "Failed to load data"
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
}, [fetcher, query]);
|
|
267
|
+
const handleRetry = () => {
|
|
268
|
+
setQuery({ ...query });
|
|
269
|
+
};
|
|
270
|
+
return {
|
|
271
|
+
dataState,
|
|
272
|
+
setDataState,
|
|
273
|
+
handleRetry,
|
|
274
|
+
isLoading: dataState.state === "loading",
|
|
275
|
+
isError: dataState.state === "error",
|
|
276
|
+
isEmpty: dataState.state === "empty"
|
|
277
|
+
};
|
|
120
278
|
}
|
|
121
279
|
function validateViewName(name) {
|
|
122
280
|
const trimmed = name.trim();
|
|
@@ -152,10 +310,7 @@ function getSavedViewsIndex() {
|
|
|
152
310
|
const key = localStorage.key(i);
|
|
153
311
|
if (key?.startsWith("rowakit-view-")) {
|
|
154
312
|
const name = key.substring("rowakit-view-".length);
|
|
155
|
-
rebuilt.push({
|
|
156
|
-
name,
|
|
157
|
-
updatedAt: Date.now()
|
|
158
|
-
});
|
|
313
|
+
rebuilt.push({ name, updatedAt: Date.now() });
|
|
159
314
|
}
|
|
160
315
|
}
|
|
161
316
|
} catch {
|
|
@@ -182,16 +337,206 @@ function loadSavedViewsFromStorage() {
|
|
|
182
337
|
const viewStr = localStorage.getItem(`rowakit-view-${entry.name}`);
|
|
183
338
|
if (viewStr) {
|
|
184
339
|
const state = JSON.parse(viewStr);
|
|
185
|
-
views.push({
|
|
186
|
-
name: entry.name,
|
|
187
|
-
state
|
|
188
|
-
});
|
|
340
|
+
views.push({ name: entry.name, state });
|
|
189
341
|
}
|
|
190
342
|
} catch {
|
|
191
343
|
}
|
|
192
344
|
}
|
|
193
345
|
return views;
|
|
194
346
|
}
|
|
347
|
+
function useSavedViews(options) {
|
|
348
|
+
const [savedViews, setSavedViews] = useState([]);
|
|
349
|
+
const [showSaveViewForm, setShowSaveViewForm] = useState(false);
|
|
350
|
+
const [saveViewInput, setSaveViewInput] = useState("");
|
|
351
|
+
const [saveViewError, setSaveViewError] = useState("");
|
|
352
|
+
const [overwriteConfirmName, setOverwriteConfirmName] = useState(null);
|
|
353
|
+
useEffect(() => {
|
|
354
|
+
if (!options.enableSavedViews) return;
|
|
355
|
+
setSavedViews(loadSavedViewsFromStorage());
|
|
356
|
+
}, [options.enableSavedViews]);
|
|
357
|
+
const saveCurrentView = (name) => {
|
|
358
|
+
const viewState = {
|
|
359
|
+
page: options.query.page,
|
|
360
|
+
pageSize: options.query.pageSize,
|
|
361
|
+
sort: options.query.sort,
|
|
362
|
+
filters: options.query.filters,
|
|
363
|
+
columnWidths: options.enableColumnResizing ? options.columnWidths : void 0
|
|
364
|
+
};
|
|
365
|
+
setSavedViews((prev) => {
|
|
366
|
+
const filtered = prev.filter((v) => v.name !== name);
|
|
367
|
+
return [...filtered, { name, state: viewState }];
|
|
368
|
+
});
|
|
369
|
+
if (typeof window !== "undefined" && window.localStorage) {
|
|
370
|
+
try {
|
|
371
|
+
localStorage.setItem(`rowakit-view-${name}`, JSON.stringify(viewState));
|
|
372
|
+
const index = getSavedViewsIndex();
|
|
373
|
+
const filtered = index.filter((v) => v.name !== name);
|
|
374
|
+
filtered.push({ name, updatedAt: Date.now() });
|
|
375
|
+
setSavedViewsIndex(filtered);
|
|
376
|
+
} catch {
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
};
|
|
380
|
+
const loadSavedView = (name) => {
|
|
381
|
+
const view = savedViews.find((v) => v.name === name);
|
|
382
|
+
if (!view) return;
|
|
383
|
+
const { state } = view;
|
|
384
|
+
options.setQuery({
|
|
385
|
+
page: state.page,
|
|
386
|
+
pageSize: state.pageSize,
|
|
387
|
+
sort: state.sort,
|
|
388
|
+
filters: state.filters
|
|
389
|
+
});
|
|
390
|
+
options.setFilters(state.filters ?? {});
|
|
391
|
+
if (state.columnWidths && options.enableColumnResizing) {
|
|
392
|
+
options.setColumnWidths(state.columnWidths);
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
const deleteSavedView = (name) => {
|
|
396
|
+
setSavedViews((prev) => prev.filter((v) => v.name !== name));
|
|
397
|
+
if (typeof window !== "undefined" && window.localStorage) {
|
|
398
|
+
try {
|
|
399
|
+
localStorage.removeItem(`rowakit-view-${name}`);
|
|
400
|
+
const index = getSavedViewsIndex();
|
|
401
|
+
const filtered = index.filter((v) => v.name !== name);
|
|
402
|
+
setSavedViewsIndex(filtered);
|
|
403
|
+
} catch {
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
};
|
|
407
|
+
const resetTableState = () => {
|
|
408
|
+
options.setQuery({
|
|
409
|
+
page: 1,
|
|
410
|
+
pageSize: options.defaultPageSize
|
|
411
|
+
});
|
|
412
|
+
options.setFilters({});
|
|
413
|
+
options.setColumnWidths({});
|
|
414
|
+
};
|
|
415
|
+
const shouldShowReset = useMemo(() => {
|
|
416
|
+
if (!options.enableSavedViews) return false;
|
|
417
|
+
return Boolean(options.query.page > 1 || options.query.sort || options.query.filters && Object.keys(options.query.filters).length > 0);
|
|
418
|
+
}, [options.enableSavedViews, options.query.page, options.query.sort, options.query.filters]);
|
|
419
|
+
const openSaveViewForm = () => {
|
|
420
|
+
setShowSaveViewForm(true);
|
|
421
|
+
setSaveViewInput("");
|
|
422
|
+
setSaveViewError("");
|
|
423
|
+
setOverwriteConfirmName(null);
|
|
424
|
+
};
|
|
425
|
+
const cancelSaveViewForm = () => {
|
|
426
|
+
setShowSaveViewForm(false);
|
|
427
|
+
setSaveViewInput("");
|
|
428
|
+
setSaveViewError("");
|
|
429
|
+
setOverwriteConfirmName(null);
|
|
430
|
+
};
|
|
431
|
+
const onSaveViewInputChange = (e) => {
|
|
432
|
+
setSaveViewInput(e.target.value);
|
|
433
|
+
setSaveViewError("");
|
|
434
|
+
};
|
|
435
|
+
const attemptSave = () => {
|
|
436
|
+
const validation = validateViewName(saveViewInput);
|
|
437
|
+
if (!validation.valid) {
|
|
438
|
+
setSaveViewError(validation.error || "Invalid name");
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
const trimmed = saveViewInput.trim();
|
|
442
|
+
if (savedViews.some((v) => v.name === trimmed)) {
|
|
443
|
+
setOverwriteConfirmName(trimmed);
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
saveCurrentView(trimmed);
|
|
447
|
+
cancelSaveViewForm();
|
|
448
|
+
};
|
|
449
|
+
const onSaveViewInputKeyDown = (e) => {
|
|
450
|
+
if (e.key !== "Enter") return;
|
|
451
|
+
attemptSave();
|
|
452
|
+
};
|
|
453
|
+
const confirmOverwrite = () => {
|
|
454
|
+
if (!overwriteConfirmName) return;
|
|
455
|
+
saveCurrentView(overwriteConfirmName);
|
|
456
|
+
cancelSaveViewForm();
|
|
457
|
+
};
|
|
458
|
+
const cancelOverwrite = () => {
|
|
459
|
+
setOverwriteConfirmName(null);
|
|
460
|
+
};
|
|
461
|
+
return {
|
|
462
|
+
savedViews,
|
|
463
|
+
showSaveViewForm,
|
|
464
|
+
saveViewInput,
|
|
465
|
+
saveViewError,
|
|
466
|
+
overwriteConfirmName,
|
|
467
|
+
openSaveViewForm,
|
|
468
|
+
cancelSaveViewForm,
|
|
469
|
+
onSaveViewInputChange,
|
|
470
|
+
onSaveViewInputKeyDown,
|
|
471
|
+
attemptSave,
|
|
472
|
+
confirmOverwrite,
|
|
473
|
+
cancelOverwrite,
|
|
474
|
+
loadSavedView,
|
|
475
|
+
deleteSavedView,
|
|
476
|
+
resetTableState,
|
|
477
|
+
shouldShowReset
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// src/hooks/useSortingState.ts
|
|
482
|
+
function useSortingState(query, setQuery) {
|
|
483
|
+
const handleSort = (field, isMultiSort = false) => {
|
|
484
|
+
setQuery((prev) => {
|
|
485
|
+
const currentSorts = prev.sorts || [];
|
|
486
|
+
const existingSort = currentSorts.find((s) => s.field === field);
|
|
487
|
+
if (!isMultiSort) {
|
|
488
|
+
if (existingSort?.priority === 0) {
|
|
489
|
+
if (existingSort.direction === "asc") {
|
|
490
|
+
return {
|
|
491
|
+
...prev,
|
|
492
|
+
sorts: [{ field, direction: "desc", priority: 0 }],
|
|
493
|
+
page: 1
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
const { sorts: _removed, ...rest } = prev;
|
|
497
|
+
return {
|
|
498
|
+
...rest,
|
|
499
|
+
page: 1
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
return {
|
|
503
|
+
...prev,
|
|
504
|
+
sorts: [{ field, direction: "asc", priority: 0 }],
|
|
505
|
+
page: 1
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
if (existingSort) {
|
|
509
|
+
const newSorts2 = currentSorts.map((s) => {
|
|
510
|
+
if (s.field === field) {
|
|
511
|
+
const newDirection = s.direction === "asc" ? "desc" : "asc";
|
|
512
|
+
return { ...s, direction: newDirection };
|
|
513
|
+
}
|
|
514
|
+
return s;
|
|
515
|
+
});
|
|
516
|
+
return { ...prev, sorts: newSorts2, page: 1 };
|
|
517
|
+
}
|
|
518
|
+
const nextPriority = currentSorts.length;
|
|
519
|
+
const newSorts = [...currentSorts, { field, direction: "asc", priority: nextPriority }];
|
|
520
|
+
return { ...prev, sorts: newSorts, page: 1 };
|
|
521
|
+
});
|
|
522
|
+
};
|
|
523
|
+
const getSortIndicator = (field) => {
|
|
524
|
+
const sorts = query.sorts || [];
|
|
525
|
+
const sort = sorts.find((s) => s.field === field);
|
|
526
|
+
if (!sort) {
|
|
527
|
+
return "";
|
|
528
|
+
}
|
|
529
|
+
const directionIcon = sort.direction === "asc" ? " \u2191" : " \u2193";
|
|
530
|
+
const priorityLabel = sort.priority === 0 ? "" : ` [${sort.priority + 1}]`;
|
|
531
|
+
return directionIcon + priorityLabel;
|
|
532
|
+
};
|
|
533
|
+
const getSortPriority = (field) => {
|
|
534
|
+
const sorts = query.sorts || [];
|
|
535
|
+
const sort = sorts.find((s) => s.field === field);
|
|
536
|
+
return sort ? sort.priority : null;
|
|
537
|
+
};
|
|
538
|
+
return { handleSort, getSortIndicator, getSortPriority };
|
|
539
|
+
}
|
|
195
540
|
function parseUrlState(params, defaultPageSize, pageSizeOptions) {
|
|
196
541
|
const pageStr = params.get("page");
|
|
197
542
|
let page = 1;
|
|
@@ -212,10 +557,33 @@ function parseUrlState(params, defaultPageSize, pageSizeOptions) {
|
|
|
212
557
|
}
|
|
213
558
|
}
|
|
214
559
|
const result = { page, pageSize };
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
560
|
+
const sortsStr = params.get("sorts");
|
|
561
|
+
if (sortsStr) {
|
|
562
|
+
try {
|
|
563
|
+
const parsed = JSON.parse(sortsStr);
|
|
564
|
+
if (Array.isArray(parsed) && parsed.every((s) => typeof s.field === "string" && (s.direction === "asc" || s.direction === "desc") && typeof s.priority === "number")) {
|
|
565
|
+
result.sorts = parsed;
|
|
566
|
+
}
|
|
567
|
+
} catch {
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
if (!result.sorts) {
|
|
571
|
+
const sortStr = params.get("sort");
|
|
572
|
+
if (sortStr) {
|
|
573
|
+
const [field, dir] = sortStr.split(":");
|
|
574
|
+
if (field && (dir === "asc" || dir === "desc")) {
|
|
575
|
+
result.sort = { field, direction: dir };
|
|
576
|
+
result.sorts = [{ field, direction: dir, priority: 0 }];
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
if (!result.sorts) {
|
|
581
|
+
const sortField = params.get("sortField");
|
|
582
|
+
const sortDir = params.get("sortDirection");
|
|
583
|
+
if (sortField && (sortDir === "asc" || sortDir === "desc")) {
|
|
584
|
+
result.sort = { field: sortField, direction: sortDir };
|
|
585
|
+
result.sorts = [{ field: sortField, direction: sortDir, priority: 0 }];
|
|
586
|
+
}
|
|
219
587
|
}
|
|
220
588
|
const filtersStr = params.get("filters");
|
|
221
589
|
if (filtersStr) {
|
|
@@ -253,7 +621,9 @@ function serializeUrlState(query, filters, columnWidths, defaultPageSize, enable
|
|
|
253
621
|
if (query.pageSize !== defaultPageSize) {
|
|
254
622
|
params.set("pageSize", String(query.pageSize));
|
|
255
623
|
}
|
|
256
|
-
if (query.
|
|
624
|
+
if (query.sorts && query.sorts.length > 0) {
|
|
625
|
+
params.set("sorts", JSON.stringify(query.sorts));
|
|
626
|
+
} else if (query.sort) {
|
|
257
627
|
params.set("sortField", query.sort.field);
|
|
258
628
|
params.set("sortDirection", query.sort.direction);
|
|
259
629
|
}
|
|
@@ -270,6 +640,385 @@ function serializeUrlState(query, filters, columnWidths, defaultPageSize, enable
|
|
|
270
640
|
}
|
|
271
641
|
return params.toString();
|
|
272
642
|
}
|
|
643
|
+
function useUrlSync({
|
|
644
|
+
syncToUrl,
|
|
645
|
+
enableColumnResizing,
|
|
646
|
+
defaultPageSize,
|
|
647
|
+
pageSizeOptions,
|
|
648
|
+
columns,
|
|
649
|
+
query,
|
|
650
|
+
setQuery,
|
|
651
|
+
filters,
|
|
652
|
+
setFilters,
|
|
653
|
+
columnWidths,
|
|
654
|
+
setColumnWidths
|
|
655
|
+
}) {
|
|
656
|
+
const didHydrateUrlRef = useRef(false);
|
|
657
|
+
const urlSyncDebounceRef = useRef(null);
|
|
658
|
+
const isApplyingUrlStateRef = useRef(false);
|
|
659
|
+
const clearApplyingTimerRef = useRef(null);
|
|
660
|
+
const hasWrittenUrlRef = useRef(false);
|
|
661
|
+
const lastQueryForUrlRef = useRef(null);
|
|
662
|
+
const defaultPageSizeRef = useRef(defaultPageSize);
|
|
663
|
+
const pageSizeOptionsRef = useRef(pageSizeOptions);
|
|
664
|
+
const enableColumnResizingRef = useRef(enableColumnResizing);
|
|
665
|
+
const columnsRef = useRef(columns);
|
|
666
|
+
defaultPageSizeRef.current = defaultPageSize;
|
|
667
|
+
pageSizeOptionsRef.current = pageSizeOptions;
|
|
668
|
+
enableColumnResizingRef.current = enableColumnResizing;
|
|
669
|
+
columnsRef.current = columns;
|
|
670
|
+
useEffect(() => {
|
|
671
|
+
if (!syncToUrl) {
|
|
672
|
+
hasWrittenUrlRef.current = false;
|
|
673
|
+
lastQueryForUrlRef.current = null;
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
if (!didHydrateUrlRef.current) return;
|
|
677
|
+
if (isApplyingUrlStateRef.current) return;
|
|
678
|
+
if (urlSyncDebounceRef.current) {
|
|
679
|
+
clearTimeout(urlSyncDebounceRef.current);
|
|
680
|
+
urlSyncDebounceRef.current = null;
|
|
681
|
+
}
|
|
682
|
+
const urlStr = serializeUrlState(query, filters, columnWidths, defaultPageSizeRef.current, enableColumnResizingRef.current);
|
|
683
|
+
const qs = urlStr ? `?${urlStr}` : "";
|
|
684
|
+
const nextUrl = `${window.location.pathname}${qs}${window.location.hash}`;
|
|
685
|
+
const prevQuery = lastQueryForUrlRef.current;
|
|
686
|
+
const shouldPush = hasWrittenUrlRef.current && prevQuery != null && prevQuery.page !== query.page;
|
|
687
|
+
if (shouldPush) {
|
|
688
|
+
window.history.pushState(null, "", nextUrl);
|
|
689
|
+
} else {
|
|
690
|
+
window.history.replaceState(null, "", nextUrl);
|
|
691
|
+
}
|
|
692
|
+
hasWrittenUrlRef.current = true;
|
|
693
|
+
lastQueryForUrlRef.current = query;
|
|
694
|
+
}, [
|
|
695
|
+
query,
|
|
696
|
+
filters,
|
|
697
|
+
syncToUrl,
|
|
698
|
+
columnWidths
|
|
699
|
+
]);
|
|
700
|
+
useEffect(() => {
|
|
701
|
+
if (!syncToUrl || !enableColumnResizingRef.current) return;
|
|
702
|
+
if (!didHydrateUrlRef.current) return;
|
|
703
|
+
if (!hasWrittenUrlRef.current) return;
|
|
704
|
+
if (isApplyingUrlStateRef.current) return;
|
|
705
|
+
if (urlSyncDebounceRef.current) {
|
|
706
|
+
clearTimeout(urlSyncDebounceRef.current);
|
|
707
|
+
}
|
|
708
|
+
urlSyncDebounceRef.current = setTimeout(() => {
|
|
709
|
+
const urlStr = serializeUrlState(query, filters, columnWidths, defaultPageSizeRef.current, enableColumnResizingRef.current);
|
|
710
|
+
const qs = urlStr ? `?${urlStr}` : "";
|
|
711
|
+
window.history.replaceState(null, "", `${window.location.pathname}${qs}${window.location.hash}`);
|
|
712
|
+
urlSyncDebounceRef.current = null;
|
|
713
|
+
}, 150);
|
|
714
|
+
return () => {
|
|
715
|
+
if (urlSyncDebounceRef.current) {
|
|
716
|
+
clearTimeout(urlSyncDebounceRef.current);
|
|
717
|
+
urlSyncDebounceRef.current = null;
|
|
718
|
+
}
|
|
719
|
+
};
|
|
720
|
+
}, [
|
|
721
|
+
columnWidths,
|
|
722
|
+
syncToUrl,
|
|
723
|
+
query,
|
|
724
|
+
filters
|
|
725
|
+
]);
|
|
726
|
+
function scheduleClearApplyingFlag() {
|
|
727
|
+
if (clearApplyingTimerRef.current) {
|
|
728
|
+
clearTimeout(clearApplyingTimerRef.current);
|
|
729
|
+
clearApplyingTimerRef.current = null;
|
|
730
|
+
}
|
|
731
|
+
clearApplyingTimerRef.current = setTimeout(() => {
|
|
732
|
+
isApplyingUrlStateRef.current = false;
|
|
733
|
+
clearApplyingTimerRef.current = null;
|
|
734
|
+
}, 0);
|
|
735
|
+
}
|
|
736
|
+
function applyUrlToState() {
|
|
737
|
+
const params = new URLSearchParams(window.location.search);
|
|
738
|
+
const parsed = parseUrlState(params, defaultPageSizeRef.current, pageSizeOptionsRef.current);
|
|
739
|
+
const nextQuery = {
|
|
740
|
+
page: parsed.page,
|
|
741
|
+
pageSize: parsed.pageSize,
|
|
742
|
+
sort: parsed.sort,
|
|
743
|
+
sorts: parsed.sorts,
|
|
744
|
+
filters: parsed.filters
|
|
745
|
+
};
|
|
746
|
+
isApplyingUrlStateRef.current = true;
|
|
747
|
+
scheduleClearApplyingFlag();
|
|
748
|
+
setQuery(nextQuery);
|
|
749
|
+
setFilters(parsed.filters ?? {});
|
|
750
|
+
if (!hasWrittenUrlRef.current) {
|
|
751
|
+
const urlStr = serializeUrlState(
|
|
752
|
+
nextQuery,
|
|
753
|
+
parsed.filters ?? {},
|
|
754
|
+
parsed.columnWidths ?? {},
|
|
755
|
+
defaultPageSizeRef.current,
|
|
756
|
+
enableColumnResizingRef.current
|
|
757
|
+
);
|
|
758
|
+
const qs = urlStr ? `?${urlStr}` : "";
|
|
759
|
+
window.history.replaceState(null, "", `${window.location.pathname}${qs}${window.location.hash}`);
|
|
760
|
+
hasWrittenUrlRef.current = true;
|
|
761
|
+
lastQueryForUrlRef.current = nextQuery;
|
|
762
|
+
}
|
|
763
|
+
if (enableColumnResizingRef.current && parsed.columnWidths) {
|
|
764
|
+
const clamped = {};
|
|
765
|
+
for (const [colId, rawWidth] of Object.entries(parsed.columnWidths)) {
|
|
766
|
+
const widthNum = typeof rawWidth === "number" ? rawWidth : Number(rawWidth);
|
|
767
|
+
if (!Number.isFinite(widthNum)) continue;
|
|
768
|
+
const colDef = columnsRef.current.find((c) => c.id === colId);
|
|
769
|
+
if (!colDef) continue;
|
|
770
|
+
const minW = colDef.minWidth ?? 80;
|
|
771
|
+
const maxW = colDef.maxWidth;
|
|
772
|
+
let finalW = Math.max(minW, widthNum);
|
|
773
|
+
if (maxW != null) {
|
|
774
|
+
finalW = Math.min(finalW, maxW);
|
|
775
|
+
}
|
|
776
|
+
clamped[colId] = finalW;
|
|
777
|
+
}
|
|
778
|
+
setColumnWidths(clamped);
|
|
779
|
+
} else if (enableColumnResizingRef.current) {
|
|
780
|
+
setColumnWidths({});
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
useEffect(() => {
|
|
784
|
+
if (!syncToUrl) {
|
|
785
|
+
didHydrateUrlRef.current = false;
|
|
786
|
+
hasWrittenUrlRef.current = false;
|
|
787
|
+
lastQueryForUrlRef.current = null;
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
790
|
+
if (didHydrateUrlRef.current) return;
|
|
791
|
+
didHydrateUrlRef.current = true;
|
|
792
|
+
applyUrlToState();
|
|
793
|
+
}, [
|
|
794
|
+
syncToUrl,
|
|
795
|
+
setQuery,
|
|
796
|
+
setFilters,
|
|
797
|
+
setColumnWidths
|
|
798
|
+
]);
|
|
799
|
+
useEffect(() => {
|
|
800
|
+
if (!syncToUrl) return;
|
|
801
|
+
const onPopState = () => {
|
|
802
|
+
applyUrlToState();
|
|
803
|
+
};
|
|
804
|
+
window.addEventListener("popstate", onPopState);
|
|
805
|
+
return () => {
|
|
806
|
+
window.removeEventListener("popstate", onPopState);
|
|
807
|
+
};
|
|
808
|
+
}, [syncToUrl, setQuery, setFilters, setColumnWidths]);
|
|
809
|
+
useEffect(() => {
|
|
810
|
+
return () => {
|
|
811
|
+
if (clearApplyingTimerRef.current) {
|
|
812
|
+
clearTimeout(clearApplyingTimerRef.current);
|
|
813
|
+
clearApplyingTimerRef.current = null;
|
|
814
|
+
}
|
|
815
|
+
};
|
|
816
|
+
}, []);
|
|
817
|
+
}
|
|
818
|
+
var FOCUSABLE_SELECTORS = [
|
|
819
|
+
"button",
|
|
820
|
+
"[href]",
|
|
821
|
+
"input",
|
|
822
|
+
"select",
|
|
823
|
+
"textarea",
|
|
824
|
+
'[tabindex]:not([tabindex="-1"])'
|
|
825
|
+
].join(",");
|
|
826
|
+
function useFocusTrap(ref, options = {}) {
|
|
827
|
+
const { onEscape, autoFocus = true } = options;
|
|
828
|
+
const firstFocusableRef = useRef(null);
|
|
829
|
+
const lastFocusableRef = useRef(null);
|
|
830
|
+
useEffect(() => {
|
|
831
|
+
const modalEl = ref.current;
|
|
832
|
+
if (!modalEl) return;
|
|
833
|
+
const getFocusableElements = () => {
|
|
834
|
+
const elements = Array.from(modalEl.querySelectorAll(FOCUSABLE_SELECTORS));
|
|
835
|
+
return elements.filter((el) => !el.hasAttribute("disabled") && el.offsetParent !== null);
|
|
836
|
+
};
|
|
837
|
+
let focusableElements = getFocusableElements();
|
|
838
|
+
if (focusableElements.length === 0) return;
|
|
839
|
+
firstFocusableRef.current = focusableElements[0] || null;
|
|
840
|
+
lastFocusableRef.current = focusableElements[focusableElements.length - 1] || null;
|
|
841
|
+
if (autoFocus && firstFocusableRef.current) {
|
|
842
|
+
firstFocusableRef.current.focus();
|
|
843
|
+
}
|
|
844
|
+
const handleKeyDown = (e) => {
|
|
845
|
+
focusableElements = getFocusableElements();
|
|
846
|
+
if (focusableElements.length === 0) return;
|
|
847
|
+
const activeEl = document.activeElement;
|
|
848
|
+
const firstEl = focusableElements[0] || null;
|
|
849
|
+
const lastEl = focusableElements[focusableElements.length - 1] || null;
|
|
850
|
+
if (e.key === "Escape") {
|
|
851
|
+
e.preventDefault();
|
|
852
|
+
onEscape?.();
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
855
|
+
if (e.key === "Tab") {
|
|
856
|
+
if (e.shiftKey) {
|
|
857
|
+
if (activeEl === firstEl && lastEl) {
|
|
858
|
+
e.preventDefault();
|
|
859
|
+
lastEl.focus();
|
|
860
|
+
}
|
|
861
|
+
} else {
|
|
862
|
+
if (activeEl === lastEl && firstEl) {
|
|
863
|
+
e.preventDefault();
|
|
864
|
+
firstEl.focus();
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
};
|
|
869
|
+
modalEl.addEventListener("keydown", handleKeyDown);
|
|
870
|
+
return () => {
|
|
871
|
+
modalEl.removeEventListener("keydown", handleKeyDown);
|
|
872
|
+
};
|
|
873
|
+
}, [ref, onEscape, autoFocus]);
|
|
874
|
+
}
|
|
875
|
+
function RowSelectionHeaderCell(props) {
|
|
876
|
+
const checkboxRef = useRef(null);
|
|
877
|
+
useEffect(() => {
|
|
878
|
+
if (!checkboxRef.current) return;
|
|
879
|
+
checkboxRef.current.indeterminate = props.indeterminate;
|
|
880
|
+
}, [props.indeterminate]);
|
|
881
|
+
return /* @__PURE__ */ jsx("th", { children: /* @__PURE__ */ jsx(
|
|
882
|
+
"input",
|
|
883
|
+
{
|
|
884
|
+
ref: checkboxRef,
|
|
885
|
+
type: "checkbox",
|
|
886
|
+
"aria-label": "Select all rows",
|
|
887
|
+
disabled: props.disabled,
|
|
888
|
+
checked: props.checked,
|
|
889
|
+
onChange: (e) => props.onChange(e.target.checked)
|
|
890
|
+
}
|
|
891
|
+
) });
|
|
892
|
+
}
|
|
893
|
+
function RowSelectionCell(props) {
|
|
894
|
+
return /* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx(
|
|
895
|
+
"input",
|
|
896
|
+
{
|
|
897
|
+
type: "checkbox",
|
|
898
|
+
"aria-label": `Select row ${props.rowKey}`,
|
|
899
|
+
disabled: props.disabled,
|
|
900
|
+
checked: props.checked,
|
|
901
|
+
onChange: (e) => props.onChange(e.target.checked)
|
|
902
|
+
}
|
|
903
|
+
) });
|
|
904
|
+
}
|
|
905
|
+
function BulkActionBar(props) {
|
|
906
|
+
if (props.selectedCount <= 0) return null;
|
|
907
|
+
return /* @__PURE__ */ jsxs("div", { className: "rowakit-bulk-action-bar", children: [
|
|
908
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
909
|
+
props.selectedCount,
|
|
910
|
+
" selected"
|
|
911
|
+
] }),
|
|
912
|
+
props.actions.map((action) => /* @__PURE__ */ jsx(
|
|
913
|
+
"button",
|
|
914
|
+
{
|
|
915
|
+
type: "button",
|
|
916
|
+
className: "rowakit-button rowakit-button-secondary",
|
|
917
|
+
onClick: () => props.onActionClick(action.id),
|
|
918
|
+
children: action.label
|
|
919
|
+
},
|
|
920
|
+
action.id
|
|
921
|
+
))
|
|
922
|
+
] });
|
|
923
|
+
}
|
|
924
|
+
function downloadBlob(blob, filename) {
|
|
925
|
+
if (typeof window === "undefined") return;
|
|
926
|
+
if (typeof URL === "undefined" || typeof URL.createObjectURL !== "function") return;
|
|
927
|
+
const url = URL.createObjectURL(blob);
|
|
928
|
+
try {
|
|
929
|
+
const a = document.createElement("a");
|
|
930
|
+
a.href = url;
|
|
931
|
+
a.download = filename;
|
|
932
|
+
a.rel = "noopener noreferrer";
|
|
933
|
+
a.click();
|
|
934
|
+
} finally {
|
|
935
|
+
URL.revokeObjectURL(url);
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
function openUrl(url) {
|
|
939
|
+
if (typeof window === "undefined") return;
|
|
940
|
+
if (typeof window.open === "function") {
|
|
941
|
+
window.open(url, "_blank", "noopener,noreferrer");
|
|
942
|
+
return;
|
|
943
|
+
}
|
|
944
|
+
try {
|
|
945
|
+
window.location.assign(url);
|
|
946
|
+
} catch {
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
function ExportButton(props) {
|
|
950
|
+
const [isExporting, setIsExporting] = useState(false);
|
|
951
|
+
const [error, setError] = useState(null);
|
|
952
|
+
const onClick = async () => {
|
|
953
|
+
if (isExporting) return;
|
|
954
|
+
setIsExporting(true);
|
|
955
|
+
setError(null);
|
|
956
|
+
try {
|
|
957
|
+
const snapshot = { ...props.query };
|
|
958
|
+
const result = await props.exporter(snapshot);
|
|
959
|
+
if (result instanceof Blob) {
|
|
960
|
+
downloadBlob(result, "rowakit-export.csv");
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
openUrl(result.url);
|
|
964
|
+
} catch (e) {
|
|
965
|
+
setError(e instanceof Error ? e.message : "Export failed");
|
|
966
|
+
} finally {
|
|
967
|
+
setIsExporting(false);
|
|
968
|
+
}
|
|
969
|
+
};
|
|
970
|
+
return /* @__PURE__ */ jsxs("div", { className: "rowakit-export", children: [
|
|
971
|
+
/* @__PURE__ */ jsx(
|
|
972
|
+
"button",
|
|
973
|
+
{
|
|
974
|
+
type: "button",
|
|
975
|
+
className: "rowakit-button rowakit-button-secondary",
|
|
976
|
+
onClick,
|
|
977
|
+
disabled: isExporting,
|
|
978
|
+
children: isExporting ? "Exporting\u2026" : "Export CSV"
|
|
979
|
+
}
|
|
980
|
+
),
|
|
981
|
+
error && /* @__PURE__ */ jsx("div", { className: "rowakit-export-error", children: error })
|
|
982
|
+
] });
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
// src/state/selection.ts
|
|
986
|
+
function toggleSelectionKey(selected, key) {
|
|
987
|
+
if (selected.includes(key)) {
|
|
988
|
+
return selected.filter((k) => k !== key);
|
|
989
|
+
}
|
|
990
|
+
return [...selected, key];
|
|
991
|
+
}
|
|
992
|
+
function isAllSelected(selected, pageKeys) {
|
|
993
|
+
if (pageKeys.length === 0) return false;
|
|
994
|
+
return pageKeys.every((k) => selected.includes(k));
|
|
995
|
+
}
|
|
996
|
+
function isIndeterminate(selected, pageKeys) {
|
|
997
|
+
if (pageKeys.length === 0) return false;
|
|
998
|
+
const selectedCount = pageKeys.filter((k) => selected.includes(k)).length;
|
|
999
|
+
return selectedCount > 0 && selectedCount < pageKeys.length;
|
|
1000
|
+
}
|
|
1001
|
+
function selectAll(pageKeys) {
|
|
1002
|
+
return [...pageKeys];
|
|
1003
|
+
}
|
|
1004
|
+
function clearSelection() {
|
|
1005
|
+
return [];
|
|
1006
|
+
}
|
|
1007
|
+
function getRowKey(row, rowKey) {
|
|
1008
|
+
if (typeof rowKey === "function") {
|
|
1009
|
+
return rowKey(row);
|
|
1010
|
+
}
|
|
1011
|
+
if (rowKey) {
|
|
1012
|
+
return String(row[rowKey]);
|
|
1013
|
+
}
|
|
1014
|
+
if (row && typeof row === "object" && "id" in row) {
|
|
1015
|
+
return String(row.id);
|
|
1016
|
+
}
|
|
1017
|
+
return String(row);
|
|
1018
|
+
}
|
|
1019
|
+
function getHeaderLabel(column) {
|
|
1020
|
+
return column.header ?? column.id;
|
|
1021
|
+
}
|
|
273
1022
|
function renderCell(column, row, isLoading, setConfirmState) {
|
|
274
1023
|
switch (column.kind) {
|
|
275
1024
|
case "text": {
|
|
@@ -351,380 +1100,146 @@ function renderCell(column, row, isLoading, setConfirmState) {
|
|
|
351
1100
|
);
|
|
352
1101
|
}) });
|
|
353
1102
|
}
|
|
354
|
-
case "custom": {
|
|
355
|
-
return column.render(row);
|
|
356
|
-
}
|
|
357
|
-
default: {
|
|
358
|
-
const _exhaustive = column;
|
|
359
|
-
return _exhaustive;
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
function RowaKitTable({
|
|
364
|
-
fetcher,
|
|
365
|
-
columns,
|
|
366
|
-
defaultPageSize = 20,
|
|
367
|
-
pageSizeOptions = [10, 20, 50],
|
|
368
|
-
rowKey,
|
|
369
|
-
className = "",
|
|
370
|
-
enableFilters = false,
|
|
371
|
-
enableColumnResizing = false,
|
|
372
|
-
syncToUrl = false,
|
|
373
|
-
enableSavedViews = false
|
|
374
|
-
}) {
|
|
375
|
-
const [dataState, setDataState] = useState({
|
|
376
|
-
state: "idle",
|
|
377
|
-
items: [],
|
|
378
|
-
total: 0
|
|
379
|
-
});
|
|
380
|
-
const [query, setQuery] = useState({
|
|
381
|
-
page: 1,
|
|
382
|
-
pageSize: defaultPageSize
|
|
383
|
-
});
|
|
384
|
-
const [filters, setFilters] = useState({});
|
|
385
|
-
const [columnWidths, setColumnWidths] = useState({});
|
|
386
|
-
const resizeRafRef = useRef(null);
|
|
387
|
-
const resizePendingRef = useRef(null);
|
|
388
|
-
const tableRef = useRef(null);
|
|
389
|
-
const isResizingRef = useRef(false);
|
|
390
|
-
const lastResizeEndTsRef = useRef(0);
|
|
391
|
-
const resizingColIdRef = useRef(null);
|
|
392
|
-
const didHydrateUrlRef = useRef(false);
|
|
393
|
-
const didSkipInitialUrlSyncRef = useRef(false);
|
|
394
|
-
const urlSyncDebounceRef = useRef(null);
|
|
395
|
-
const [savedViews, setSavedViews] = useState([]);
|
|
396
|
-
const [showSaveViewForm, setShowSaveViewForm] = useState(false);
|
|
397
|
-
const [saveViewInput, setSaveViewInput] = useState("");
|
|
398
|
-
const [saveViewError, setSaveViewError] = useState("");
|
|
399
|
-
const [overwriteConfirmName, setOverwriteConfirmName] = useState(null);
|
|
400
|
-
useEffect(() => {
|
|
401
|
-
if (!enableSavedViews) return;
|
|
402
|
-
const views = loadSavedViewsFromStorage();
|
|
403
|
-
setSavedViews(views);
|
|
404
|
-
}, [enableSavedViews]);
|
|
405
|
-
const [confirmState, setConfirmState] = useState(null);
|
|
406
|
-
const requestIdRef = useRef(0);
|
|
407
|
-
useEffect(() => {
|
|
408
|
-
if (!syncToUrl) {
|
|
409
|
-
didSkipInitialUrlSyncRef.current = false;
|
|
410
|
-
return;
|
|
411
|
-
}
|
|
412
|
-
if (!didSkipInitialUrlSyncRef.current) {
|
|
413
|
-
didSkipInitialUrlSyncRef.current = true;
|
|
414
|
-
return;
|
|
415
|
-
}
|
|
416
|
-
if (urlSyncDebounceRef.current) {
|
|
417
|
-
clearTimeout(urlSyncDebounceRef.current);
|
|
418
|
-
urlSyncDebounceRef.current = null;
|
|
419
|
-
}
|
|
420
|
-
const urlStr = serializeUrlState(query, filters, columnWidths, defaultPageSize, enableColumnResizing);
|
|
421
|
-
const qs = urlStr ? `?${urlStr}` : "";
|
|
422
|
-
window.history.replaceState(null, "", `${window.location.pathname}${qs}${window.location.hash}`);
|
|
423
|
-
}, [query, filters, syncToUrl, enableColumnResizing, defaultPageSize, columnWidths]);
|
|
424
|
-
useEffect(() => {
|
|
425
|
-
if (!syncToUrl || !enableColumnResizing) return;
|
|
426
|
-
if (!didSkipInitialUrlSyncRef.current) return;
|
|
427
|
-
if (urlSyncDebounceRef.current) {
|
|
428
|
-
clearTimeout(urlSyncDebounceRef.current);
|
|
429
|
-
}
|
|
430
|
-
urlSyncDebounceRef.current = setTimeout(() => {
|
|
431
|
-
const urlStr = serializeUrlState(query, filters, columnWidths, defaultPageSize, enableColumnResizing);
|
|
432
|
-
const qs = urlStr ? `?${urlStr}` : "";
|
|
433
|
-
window.history.replaceState(null, "", `${window.location.pathname}${qs}${window.location.hash}`);
|
|
434
|
-
urlSyncDebounceRef.current = null;
|
|
435
|
-
}, 150);
|
|
436
|
-
return () => {
|
|
437
|
-
if (urlSyncDebounceRef.current) {
|
|
438
|
-
clearTimeout(urlSyncDebounceRef.current);
|
|
439
|
-
urlSyncDebounceRef.current = null;
|
|
440
|
-
}
|
|
441
|
-
};
|
|
442
|
-
}, [columnWidths, syncToUrl, enableColumnResizing, query, filters, defaultPageSize]);
|
|
443
|
-
useEffect(() => {
|
|
444
|
-
if (!syncToUrl) {
|
|
445
|
-
didHydrateUrlRef.current = false;
|
|
446
|
-
return;
|
|
447
|
-
}
|
|
448
|
-
if (didHydrateUrlRef.current) return;
|
|
449
|
-
didHydrateUrlRef.current = true;
|
|
450
|
-
const params = new URLSearchParams(window.location.search);
|
|
451
|
-
const parsed = parseUrlState(params, defaultPageSize, pageSizeOptions);
|
|
452
|
-
setQuery({
|
|
453
|
-
page: parsed.page,
|
|
454
|
-
pageSize: parsed.pageSize,
|
|
455
|
-
sort: parsed.sort,
|
|
456
|
-
filters: parsed.filters
|
|
457
|
-
});
|
|
458
|
-
if (parsed.filters) {
|
|
459
|
-
setFilters(parsed.filters);
|
|
460
|
-
}
|
|
461
|
-
if (parsed.columnWidths && enableColumnResizing) {
|
|
462
|
-
const clamped = {};
|
|
463
|
-
for (const [colId, rawWidth] of Object.entries(parsed.columnWidths)) {
|
|
464
|
-
const widthNum = typeof rawWidth === "number" ? rawWidth : Number(rawWidth);
|
|
465
|
-
if (!Number.isFinite(widthNum)) continue;
|
|
466
|
-
const colDef = columns.find((c) => c.id === colId);
|
|
467
|
-
if (!colDef) continue;
|
|
468
|
-
const minW = colDef.minWidth ?? 80;
|
|
469
|
-
const maxW = colDef.maxWidth;
|
|
470
|
-
let finalW = Math.max(minW, widthNum);
|
|
471
|
-
if (maxW != null) {
|
|
472
|
-
finalW = Math.min(finalW, maxW);
|
|
473
|
-
}
|
|
474
|
-
clamped[colId] = finalW;
|
|
475
|
-
}
|
|
476
|
-
setColumnWidths(clamped);
|
|
477
|
-
}
|
|
478
|
-
}, [syncToUrl, defaultPageSize, enableColumnResizing, pageSizeOptions, columns]);
|
|
479
|
-
useEffect(() => {
|
|
480
|
-
if (!enableFilters) return;
|
|
481
|
-
const activeFilters = {};
|
|
482
|
-
let hasFilters = false;
|
|
483
|
-
for (const [field, value] of Object.entries(filters)) {
|
|
484
|
-
if (value !== void 0) {
|
|
485
|
-
activeFilters[field] = value;
|
|
486
|
-
hasFilters = true;
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
const filtersToSend = hasFilters ? activeFilters : void 0;
|
|
490
|
-
setQuery((prev) => ({
|
|
491
|
-
...prev,
|
|
492
|
-
filters: filtersToSend,
|
|
493
|
-
page: 1
|
|
494
|
-
// Reset page to 1 when filters change
|
|
495
|
-
}));
|
|
496
|
-
}, [filters, enableFilters]);
|
|
497
|
-
useEffect(() => {
|
|
498
|
-
const currentRequestId = ++requestIdRef.current;
|
|
499
|
-
setDataState((prev) => ({ ...prev, state: "loading" }));
|
|
500
|
-
fetcher(query).then((result) => {
|
|
501
|
-
if (currentRequestId !== requestIdRef.current) {
|
|
502
|
-
return;
|
|
503
|
-
}
|
|
504
|
-
if (result.items.length === 0) {
|
|
505
|
-
setDataState({
|
|
506
|
-
state: "empty",
|
|
507
|
-
items: [],
|
|
508
|
-
total: result.total
|
|
509
|
-
});
|
|
510
|
-
} else {
|
|
511
|
-
setDataState({
|
|
512
|
-
state: "success",
|
|
513
|
-
items: result.items,
|
|
514
|
-
total: result.total
|
|
515
|
-
});
|
|
516
|
-
}
|
|
517
|
-
}).catch((error) => {
|
|
518
|
-
if (currentRequestId !== requestIdRef.current) {
|
|
519
|
-
return;
|
|
520
|
-
}
|
|
521
|
-
setDataState({
|
|
522
|
-
state: "error",
|
|
523
|
-
items: [],
|
|
524
|
-
total: 0,
|
|
525
|
-
error: error instanceof Error ? error.message : "Failed to load data"
|
|
526
|
-
});
|
|
527
|
-
});
|
|
528
|
-
}, [fetcher, query]);
|
|
529
|
-
const handleRetry = () => {
|
|
530
|
-
setQuery({ ...query });
|
|
531
|
-
};
|
|
532
|
-
const handlePreviousPage = () => {
|
|
533
|
-
if (query.page > 1) {
|
|
534
|
-
setQuery((prev) => ({ ...prev, page: prev.page - 1 }));
|
|
535
|
-
}
|
|
536
|
-
};
|
|
537
|
-
const handleNextPage = () => {
|
|
538
|
-
const totalPages2 = Math.ceil(dataState.total / query.pageSize);
|
|
539
|
-
if (query.page < totalPages2) {
|
|
540
|
-
setQuery((prev) => ({ ...prev, page: prev.page + 1 }));
|
|
541
|
-
}
|
|
542
|
-
};
|
|
543
|
-
const handlePageSizeChange = (newPageSize) => {
|
|
544
|
-
setQuery({ ...query, pageSize: newPageSize, page: 1 });
|
|
545
|
-
};
|
|
546
|
-
const handleSort = (field) => {
|
|
547
|
-
setQuery((prev) => {
|
|
548
|
-
if (prev.sort?.field !== field) {
|
|
549
|
-
return { ...prev, sort: { field, direction: "asc" }, page: 1 };
|
|
550
|
-
}
|
|
551
|
-
if (prev.sort.direction === "asc") {
|
|
552
|
-
return { ...prev, sort: { field, direction: "desc" }, page: 1 };
|
|
553
|
-
}
|
|
554
|
-
const { sort: _sort, ...rest } = prev;
|
|
555
|
-
return { ...rest, page: 1 };
|
|
556
|
-
});
|
|
557
|
-
};
|
|
558
|
-
const getSortIndicator = (field) => {
|
|
559
|
-
if (!query.sort || query.sort.field !== field) {
|
|
560
|
-
return "";
|
|
561
|
-
}
|
|
562
|
-
return query.sort.direction === "asc" ? " \u2191" : " \u2193";
|
|
563
|
-
};
|
|
564
|
-
const scheduleColumnWidthUpdate = (colId, width) => {
|
|
565
|
-
resizePendingRef.current = { colId, width };
|
|
566
|
-
if (resizeRafRef.current != null) return;
|
|
567
|
-
resizeRafRef.current = requestAnimationFrame(() => {
|
|
568
|
-
resizeRafRef.current = null;
|
|
569
|
-
const pending = resizePendingRef.current;
|
|
570
|
-
if (!pending) return;
|
|
571
|
-
handleColumnResize(pending.colId, pending.width);
|
|
572
|
-
});
|
|
573
|
-
};
|
|
574
|
-
const handleColumnResize = (columnId, newWidth) => {
|
|
575
|
-
const minWidth = columns.find((c) => c.id === columnId)?.minWidth ?? 80;
|
|
576
|
-
const maxWidth = columns.find((c) => c.id === columnId)?.maxWidth;
|
|
577
|
-
let finalWidth = Math.max(minWidth, newWidth);
|
|
578
|
-
if (maxWidth) {
|
|
579
|
-
finalWidth = Math.min(finalWidth, maxWidth);
|
|
580
|
-
}
|
|
581
|
-
if (columnWidths[columnId] === finalWidth) {
|
|
582
|
-
return;
|
|
583
|
-
}
|
|
584
|
-
setColumnWidths((prev) => ({
|
|
585
|
-
...prev,
|
|
586
|
-
[columnId]: finalWidth
|
|
587
|
-
}));
|
|
588
|
-
};
|
|
589
|
-
const autoFitColumnWidth = (columnId) => {
|
|
590
|
-
const tableEl = tableRef.current;
|
|
591
|
-
if (!tableEl) return;
|
|
592
|
-
const th = tableEl.querySelector(`th[data-col-id="${columnId}"]`);
|
|
593
|
-
if (!th) return;
|
|
594
|
-
const tds = Array.from(
|
|
595
|
-
tableEl.querySelectorAll(`td[data-col-id="${columnId}"]`)
|
|
596
|
-
);
|
|
597
|
-
const headerW = th.scrollWidth;
|
|
598
|
-
const cellsMaxW = tds.reduce((max, td) => Math.max(max, td.scrollWidth), 0);
|
|
599
|
-
const padding = 24;
|
|
600
|
-
const raw = Math.max(headerW, cellsMaxW) + padding;
|
|
601
|
-
const colDef = columns.find((c) => c.id === columnId);
|
|
602
|
-
const minW = colDef?.minWidth ?? 80;
|
|
603
|
-
const maxW = colDef?.maxWidth ?? 600;
|
|
604
|
-
const finalW = Math.max(minW, Math.min(raw, maxW));
|
|
605
|
-
setColumnWidths((prev) => ({ ...prev, [columnId]: finalW }));
|
|
606
|
-
};
|
|
607
|
-
const startColumnResize = (e, columnId) => {
|
|
608
|
-
e.preventDefault();
|
|
609
|
-
e.stopPropagation();
|
|
610
|
-
if (e.detail === 2) {
|
|
611
|
-
autoFitColumnWidth(columnId);
|
|
612
|
-
return;
|
|
613
|
-
}
|
|
614
|
-
if (e.pointerType === "mouse" && e.buttons !== 1) {
|
|
615
|
-
return;
|
|
1103
|
+
case "custom": {
|
|
1104
|
+
return column.render(row);
|
|
616
1105
|
}
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
target.setPointerCapture(pointerId);
|
|
621
|
-
} catch {
|
|
1106
|
+
default: {
|
|
1107
|
+
const _exhaustive = column;
|
|
1108
|
+
return _exhaustive;
|
|
622
1109
|
}
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
function RowaKitTable({
|
|
1113
|
+
fetcher,
|
|
1114
|
+
columns,
|
|
1115
|
+
defaultPageSize = 20,
|
|
1116
|
+
pageSizeOptions = [10, 20, 50],
|
|
1117
|
+
rowKey,
|
|
1118
|
+
className = "",
|
|
1119
|
+
enableFilters = false,
|
|
1120
|
+
enableColumnResizing = false,
|
|
1121
|
+
syncToUrl = false,
|
|
1122
|
+
enableSavedViews = false,
|
|
1123
|
+
enableRowSelection = false,
|
|
1124
|
+
onSelectionChange,
|
|
1125
|
+
bulkActions,
|
|
1126
|
+
exporter
|
|
1127
|
+
}) {
|
|
1128
|
+
const [query, setQuery] = useState({
|
|
1129
|
+
page: 1,
|
|
1130
|
+
pageSize: defaultPageSize
|
|
1131
|
+
});
|
|
1132
|
+
const [filters, setFilters] = useState({});
|
|
1133
|
+
const [confirmState, setConfirmState] = useState(null);
|
|
1134
|
+
const [selectedKeys, setSelectedKeys] = useState([]);
|
|
1135
|
+
const [bulkConfirmState, setBulkConfirmState] = useState(null);
|
|
1136
|
+
const confirmModalRef = useRef(null);
|
|
1137
|
+
const bulkConfirmModalRef = useRef(null);
|
|
1138
|
+
const {
|
|
1139
|
+
tableRef,
|
|
1140
|
+
columnWidths,
|
|
1141
|
+
setColumnWidths,
|
|
1142
|
+
startColumnResize,
|
|
1143
|
+
handleColumnResizeDoubleClick,
|
|
1144
|
+
isResizingRef,
|
|
1145
|
+
lastResizeEndTsRef,
|
|
1146
|
+
resizingColIdRef
|
|
1147
|
+
} = useColumnResizing(columns);
|
|
1148
|
+
useUrlSync({
|
|
1149
|
+
syncToUrl,
|
|
1150
|
+
enableColumnResizing,
|
|
1151
|
+
defaultPageSize,
|
|
1152
|
+
pageSizeOptions,
|
|
1153
|
+
columns,
|
|
1154
|
+
query,
|
|
1155
|
+
setQuery,
|
|
1156
|
+
filters,
|
|
1157
|
+
setFilters,
|
|
1158
|
+
columnWidths,
|
|
1159
|
+
setColumnWidths
|
|
1160
|
+
});
|
|
1161
|
+
const { dataState, handleRetry, isLoading, isError, isEmpty } = useFetcherState(fetcher, query, setQuery);
|
|
1162
|
+
const { handleSort, getSortIndicator } = useSortingState(query, setQuery);
|
|
1163
|
+
const pageRowKeys = dataState.items.map((row) => getRowKey(row, rowKey));
|
|
1164
|
+
const headerChecked = isAllSelected(selectedKeys, pageRowKeys);
|
|
1165
|
+
const headerIndeterminate = isIndeterminate(selectedKeys, pageRowKeys);
|
|
1166
|
+
useEffect(() => {
|
|
1167
|
+
setSelectedKeys(clearSelection());
|
|
1168
|
+
}, [query.page]);
|
|
1169
|
+
useEffect(() => {
|
|
1170
|
+
if (!enableRowSelection) {
|
|
1171
|
+
setSelectedKeys(clearSelection());
|
|
636
1172
|
}
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
};
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
try {
|
|
685
|
-
localStorage.setItem(`rowakit-view-${name}`, JSON.stringify(viewState));
|
|
686
|
-
const index = getSavedViewsIndex();
|
|
687
|
-
const filtered = index.filter((v) => v.name !== name);
|
|
688
|
-
filtered.push({ name, updatedAt: Date.now() });
|
|
689
|
-
setSavedViewsIndex(filtered);
|
|
690
|
-
} catch {
|
|
1173
|
+
}, [enableRowSelection]);
|
|
1174
|
+
useEffect(() => {
|
|
1175
|
+
if (!enableRowSelection || !onSelectionChange) return;
|
|
1176
|
+
onSelectionChange(selectedKeys);
|
|
1177
|
+
}, [enableRowSelection, onSelectionChange, selectedKeys]);
|
|
1178
|
+
const {
|
|
1179
|
+
savedViews,
|
|
1180
|
+
showSaveViewForm,
|
|
1181
|
+
saveViewInput,
|
|
1182
|
+
saveViewError,
|
|
1183
|
+
overwriteConfirmName,
|
|
1184
|
+
openSaveViewForm,
|
|
1185
|
+
cancelSaveViewForm,
|
|
1186
|
+
onSaveViewInputChange,
|
|
1187
|
+
onSaveViewInputKeyDown,
|
|
1188
|
+
attemptSave,
|
|
1189
|
+
confirmOverwrite,
|
|
1190
|
+
cancelOverwrite,
|
|
1191
|
+
loadSavedView,
|
|
1192
|
+
deleteSavedView,
|
|
1193
|
+
resetTableState
|
|
1194
|
+
} = useSavedViews({
|
|
1195
|
+
enableSavedViews,
|
|
1196
|
+
enableColumnResizing,
|
|
1197
|
+
defaultPageSize,
|
|
1198
|
+
query,
|
|
1199
|
+
setQuery,
|
|
1200
|
+
setFilters,
|
|
1201
|
+
columnWidths,
|
|
1202
|
+
setColumnWidths
|
|
1203
|
+
});
|
|
1204
|
+
useFocusTrap(confirmModalRef, {
|
|
1205
|
+
onEscape: () => setConfirmState(null),
|
|
1206
|
+
autoFocus: true
|
|
1207
|
+
});
|
|
1208
|
+
useFocusTrap(bulkConfirmModalRef, {
|
|
1209
|
+
onEscape: () => setBulkConfirmState(null),
|
|
1210
|
+
autoFocus: true
|
|
1211
|
+
});
|
|
1212
|
+
useEffect(() => {
|
|
1213
|
+
if (!enableFilters) return;
|
|
1214
|
+
const activeFilters = {};
|
|
1215
|
+
let hasFilters = false;
|
|
1216
|
+
for (const [field, value] of Object.entries(filters)) {
|
|
1217
|
+
if (value !== void 0) {
|
|
1218
|
+
activeFilters[field] = value;
|
|
1219
|
+
hasFilters = true;
|
|
691
1220
|
}
|
|
692
1221
|
}
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
setFilters(state.filters ?? {});
|
|
705
|
-
if (state.columnWidths && enableColumnResizing) {
|
|
706
|
-
setColumnWidths(state.columnWidths);
|
|
1222
|
+
const filtersToSend = hasFilters ? activeFilters : void 0;
|
|
1223
|
+
setQuery((prev) => ({
|
|
1224
|
+
...prev,
|
|
1225
|
+
filters: filtersToSend,
|
|
1226
|
+
page: 1
|
|
1227
|
+
// Reset page to 1 when filters change
|
|
1228
|
+
}));
|
|
1229
|
+
}, [filters, enableFilters]);
|
|
1230
|
+
const handlePreviousPage = () => {
|
|
1231
|
+
if (query.page > 1) {
|
|
1232
|
+
setQuery((prev) => ({ ...prev, page: prev.page - 1 }));
|
|
707
1233
|
}
|
|
708
1234
|
};
|
|
709
|
-
const
|
|
710
|
-
|
|
711
|
-
if (
|
|
712
|
-
|
|
713
|
-
localStorage.removeItem(`rowakit-view-${name}`);
|
|
714
|
-
const index = getSavedViewsIndex();
|
|
715
|
-
const filtered = index.filter((v) => v.name !== name);
|
|
716
|
-
setSavedViewsIndex(filtered);
|
|
717
|
-
} catch {
|
|
718
|
-
}
|
|
1235
|
+
const handleNextPage = () => {
|
|
1236
|
+
const totalPages2 = Math.ceil(dataState.total / query.pageSize);
|
|
1237
|
+
if (query.page < totalPages2) {
|
|
1238
|
+
setQuery((prev) => ({ ...prev, page: prev.page + 1 }));
|
|
719
1239
|
}
|
|
720
1240
|
};
|
|
721
|
-
const
|
|
722
|
-
setQuery({
|
|
723
|
-
page: 1,
|
|
724
|
-
pageSize: defaultPageSize
|
|
725
|
-
});
|
|
726
|
-
setFilters({});
|
|
727
|
-
setColumnWidths({});
|
|
1241
|
+
const handlePageSizeChange = (newPageSize) => {
|
|
1242
|
+
setQuery({ ...query, pageSize: newPageSize, page: 1 });
|
|
728
1243
|
};
|
|
729
1244
|
const transformFilterValueForColumn = (column, value) => {
|
|
730
1245
|
if (!value || column?.kind !== "number") {
|
|
@@ -770,12 +1285,10 @@ function RowaKitTable({
|
|
|
770
1285
|
const handleClearAllFilters = () => {
|
|
771
1286
|
setFilters({});
|
|
772
1287
|
};
|
|
773
|
-
const isLoading = dataState.state === "loading";
|
|
774
|
-
const isError = dataState.state === "error";
|
|
775
|
-
const isEmpty = dataState.state === "empty";
|
|
776
1288
|
const totalPages = Math.ceil(dataState.total / query.pageSize);
|
|
777
1289
|
const canGoPrevious = query.page > 1 && !isLoading;
|
|
778
1290
|
const canGoNext = query.page < totalPages && !isLoading;
|
|
1291
|
+
const tableColumnCount = columns.length + (enableRowSelection ? 1 : 0);
|
|
779
1292
|
const hasActiveFilters = enableFilters && Object.values(filters).some((v) => v !== void 0);
|
|
780
1293
|
const containerClass = [
|
|
781
1294
|
"rowakit-table",
|
|
@@ -783,16 +1296,29 @@ function RowaKitTable({
|
|
|
783
1296
|
className
|
|
784
1297
|
].filter(Boolean).join(" ");
|
|
785
1298
|
return /* @__PURE__ */ jsxs("div", { className: containerClass, children: [
|
|
1299
|
+
enableRowSelection && bulkActions && bulkActions.length > 0 && selectedKeys.length > 0 && /* @__PURE__ */ jsx(
|
|
1300
|
+
BulkActionBar,
|
|
1301
|
+
{
|
|
1302
|
+
selectedCount: selectedKeys.length,
|
|
1303
|
+
actions: bulkActions,
|
|
1304
|
+
onActionClick: (actionId) => {
|
|
1305
|
+
const action = bulkActions.find((a) => a.id === actionId);
|
|
1306
|
+
if (!action) return;
|
|
1307
|
+
const snapshot = [...selectedKeys];
|
|
1308
|
+
if (action.confirm) {
|
|
1309
|
+
setBulkConfirmState({ action, selectedKeys: snapshot });
|
|
1310
|
+
return;
|
|
1311
|
+
}
|
|
1312
|
+
action.onClick(snapshot);
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
),
|
|
1316
|
+
exporter && /* @__PURE__ */ jsx(ExportButton, { exporter, query }),
|
|
786
1317
|
enableSavedViews && /* @__PURE__ */ jsxs("div", { className: "rowakit-saved-views-group", children: [
|
|
787
1318
|
!showSaveViewForm ? /* @__PURE__ */ jsx(
|
|
788
1319
|
"button",
|
|
789
1320
|
{
|
|
790
|
-
onClick:
|
|
791
|
-
setShowSaveViewForm(true);
|
|
792
|
-
setSaveViewInput("");
|
|
793
|
-
setSaveViewError("");
|
|
794
|
-
setOverwriteConfirmName(null);
|
|
795
|
-
},
|
|
1321
|
+
onClick: openSaveViewForm,
|
|
796
1322
|
className: "rowakit-saved-view-button",
|
|
797
1323
|
type: "button",
|
|
798
1324
|
children: "Save View"
|
|
@@ -806,13 +1332,7 @@ function RowaKitTable({
|
|
|
806
1332
|
/* @__PURE__ */ jsx(
|
|
807
1333
|
"button",
|
|
808
1334
|
{
|
|
809
|
-
onClick:
|
|
810
|
-
saveCurrentView(overwriteConfirmName);
|
|
811
|
-
setShowSaveViewForm(false);
|
|
812
|
-
setSaveViewInput("");
|
|
813
|
-
setSaveViewError("");
|
|
814
|
-
setOverwriteConfirmName(null);
|
|
815
|
-
},
|
|
1335
|
+
onClick: confirmOverwrite,
|
|
816
1336
|
className: "rowakit-saved-view-button",
|
|
817
1337
|
type: "button",
|
|
818
1338
|
children: "Overwrite"
|
|
@@ -821,9 +1341,7 @@ function RowaKitTable({
|
|
|
821
1341
|
/* @__PURE__ */ jsx(
|
|
822
1342
|
"button",
|
|
823
1343
|
{
|
|
824
|
-
onClick:
|
|
825
|
-
setOverwriteConfirmName(null);
|
|
826
|
-
},
|
|
1344
|
+
onClick: cancelOverwrite,
|
|
827
1345
|
className: "rowakit-saved-view-button",
|
|
828
1346
|
type: "button",
|
|
829
1347
|
children: "Cancel"
|
|
@@ -835,27 +1353,8 @@ function RowaKitTable({
|
|
|
835
1353
|
{
|
|
836
1354
|
type: "text",
|
|
837
1355
|
value: saveViewInput,
|
|
838
|
-
onChange:
|
|
839
|
-
|
|
840
|
-
setSaveViewError("");
|
|
841
|
-
},
|
|
842
|
-
onKeyDown: (e) => {
|
|
843
|
-
if (e.key === "Enter") {
|
|
844
|
-
const validation = validateViewName(saveViewInput);
|
|
845
|
-
if (!validation.valid) {
|
|
846
|
-
setSaveViewError(validation.error || "Invalid name");
|
|
847
|
-
return;
|
|
848
|
-
}
|
|
849
|
-
if (savedViews.some((v) => v.name === saveViewInput.trim())) {
|
|
850
|
-
setOverwriteConfirmName(saveViewInput.trim());
|
|
851
|
-
} else {
|
|
852
|
-
saveCurrentView(saveViewInput.trim());
|
|
853
|
-
setShowSaveViewForm(false);
|
|
854
|
-
setSaveViewInput("");
|
|
855
|
-
setSaveViewError("");
|
|
856
|
-
}
|
|
857
|
-
}
|
|
858
|
-
},
|
|
1356
|
+
onChange: onSaveViewInputChange,
|
|
1357
|
+
onKeyDown: onSaveViewInputKeyDown,
|
|
859
1358
|
placeholder: "Enter view name...",
|
|
860
1359
|
className: "rowakit-save-view-input"
|
|
861
1360
|
}
|
|
@@ -864,21 +1363,7 @@ function RowaKitTable({
|
|
|
864
1363
|
/* @__PURE__ */ jsx(
|
|
865
1364
|
"button",
|
|
866
1365
|
{
|
|
867
|
-
onClick:
|
|
868
|
-
const validation = validateViewName(saveViewInput);
|
|
869
|
-
if (!validation.valid) {
|
|
870
|
-
setSaveViewError(validation.error || "Invalid name");
|
|
871
|
-
return;
|
|
872
|
-
}
|
|
873
|
-
if (savedViews.some((v) => v.name === saveViewInput.trim())) {
|
|
874
|
-
setOverwriteConfirmName(saveViewInput.trim());
|
|
875
|
-
} else {
|
|
876
|
-
saveCurrentView(saveViewInput.trim());
|
|
877
|
-
setShowSaveViewForm(false);
|
|
878
|
-
setSaveViewInput("");
|
|
879
|
-
setSaveViewError("");
|
|
880
|
-
}
|
|
881
|
-
},
|
|
1366
|
+
onClick: attemptSave,
|
|
882
1367
|
className: "rowakit-saved-view-button",
|
|
883
1368
|
type: "button",
|
|
884
1369
|
children: "Save"
|
|
@@ -887,11 +1372,7 @@ function RowaKitTable({
|
|
|
887
1372
|
/* @__PURE__ */ jsx(
|
|
888
1373
|
"button",
|
|
889
1374
|
{
|
|
890
|
-
onClick:
|
|
891
|
-
setShowSaveViewForm(false);
|
|
892
|
-
setSaveViewInput("");
|
|
893
|
-
setSaveViewError("");
|
|
894
|
-
},
|
|
1375
|
+
onClick: cancelSaveViewForm,
|
|
895
1376
|
className: "rowakit-saved-view-button",
|
|
896
1377
|
type: "button",
|
|
897
1378
|
children: "Cancel"
|
|
@@ -940,167 +1421,143 @@ function RowaKitTable({
|
|
|
940
1421
|
) }),
|
|
941
1422
|
/* @__PURE__ */ jsxs("table", { ref: tableRef, children: [
|
|
942
1423
|
/* @__PURE__ */ jsxs("thead", { children: [
|
|
943
|
-
/* @__PURE__ */
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
const isResizable = enableColumnResizing && column.kind !== "actions";
|
|
947
|
-
const actualWidth = columnWidths[column.id] ?? column.width;
|
|
948
|
-
return /* @__PURE__ */ jsxs(
|
|
949
|
-
"th",
|
|
1424
|
+
/* @__PURE__ */ jsxs("tr", { children: [
|
|
1425
|
+
enableRowSelection && /* @__PURE__ */ jsx(
|
|
1426
|
+
RowSelectionHeaderCell,
|
|
950
1427
|
{
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
onKeyDown: isSortable ? (e) => {
|
|
960
|
-
if (e.key === "Enter" || e.key === " ") {
|
|
961
|
-
e.preventDefault();
|
|
962
|
-
handleSort(String(field));
|
|
1428
|
+
checked: headerChecked,
|
|
1429
|
+
indeterminate: headerIndeterminate,
|
|
1430
|
+
disabled: isLoading || pageRowKeys.length === 0,
|
|
1431
|
+
onChange: (checked) => {
|
|
1432
|
+
if (checked) {
|
|
1433
|
+
setSelectedKeys(selectAll(pageRowKeys));
|
|
1434
|
+
} else {
|
|
1435
|
+
setSelectedKeys(clearSelection());
|
|
963
1436
|
}
|
|
964
|
-
} : void 0,
|
|
965
|
-
"aria-sort": isSortable && query.sort?.field === String(field) ? query.sort.direction === "asc" ? "ascending" : "descending" : void 0,
|
|
966
|
-
style: {
|
|
967
|
-
width: actualWidth != null ? `${actualWidth}px` : void 0,
|
|
968
|
-
textAlign: column.align,
|
|
969
|
-
position: isResizable ? "relative" : void 0
|
|
970
|
-
},
|
|
971
|
-
className: [
|
|
972
|
-
column.truncate ? "rowakit-cell-truncate" : "",
|
|
973
|
-
resizingColIdRef.current === column.id ? "resizing" : ""
|
|
974
|
-
// PRD-01
|
|
975
|
-
].filter(Boolean).join(" ") || void 0,
|
|
976
|
-
children: [
|
|
977
|
-
getHeaderLabel(column),
|
|
978
|
-
isSortable && getSortIndicator(String(field)),
|
|
979
|
-
isResizable && /* @__PURE__ */ jsx(
|
|
980
|
-
"div",
|
|
981
|
-
{
|
|
982
|
-
className: "rowakit-column-resize-handle",
|
|
983
|
-
onPointerDown: (e) => startColumnResize(e, column.id),
|
|
984
|
-
onDoubleClick: (e) => handleColumnResizeDoubleClick(e, column.id),
|
|
985
|
-
title: "Drag to resize | Double-click to auto-fit content"
|
|
986
|
-
}
|
|
987
|
-
)
|
|
988
|
-
]
|
|
989
|
-
},
|
|
990
|
-
column.id
|
|
991
|
-
);
|
|
992
|
-
}) }),
|
|
993
|
-
enableFilters && /* @__PURE__ */ jsx("tr", { className: "rowakit-table-filter-row", children: columns.map((column) => {
|
|
994
|
-
const field = column.kind === "actions" || column.kind === "custom" ? "" : String(column.field);
|
|
995
|
-
const canFilter = field && column.kind !== "actions";
|
|
996
|
-
if (!canFilter) {
|
|
997
|
-
return /* @__PURE__ */ jsx("th", {}, column.id);
|
|
998
|
-
}
|
|
999
|
-
const filterValue = filters[field];
|
|
1000
|
-
if (column.kind === "badge") {
|
|
1001
|
-
const options = column.map ? Object.keys(column.map) : [];
|
|
1002
|
-
return /* @__PURE__ */ jsx("th", { children: /* @__PURE__ */ jsxs(
|
|
1003
|
-
"select",
|
|
1004
|
-
{
|
|
1005
|
-
className: "rowakit-filter-select",
|
|
1006
|
-
value: filterValue?.op === "equals" ? String(filterValue.value ?? "") : "",
|
|
1007
|
-
onChange: (e) => {
|
|
1008
|
-
const value = e.target.value;
|
|
1009
|
-
if (value === "") {
|
|
1010
|
-
handleClearFilter(field);
|
|
1011
|
-
} else {
|
|
1012
|
-
handleFilterChange(field, { op: "equals", value });
|
|
1013
|
-
}
|
|
1014
|
-
},
|
|
1015
|
-
children: [
|
|
1016
|
-
/* @__PURE__ */ jsx("option", { value: "", children: "All" }),
|
|
1017
|
-
options.map((opt) => /* @__PURE__ */ jsx("option", { value: opt, children: opt }, opt))
|
|
1018
|
-
]
|
|
1019
1437
|
}
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1438
|
+
}
|
|
1439
|
+
),
|
|
1440
|
+
columns.map((column) => {
|
|
1441
|
+
const isSortable = column.kind !== "actions" && (column.kind === "custom" ? false : column.sortable === true);
|
|
1442
|
+
const field = column.kind === "actions" ? "" : column.kind === "custom" ? column.field : column.field;
|
|
1443
|
+
const isResizable = enableColumnResizing && column.kind !== "actions";
|
|
1444
|
+
const actualWidth = columnWidths[column.id] ?? column.width;
|
|
1445
|
+
return /* @__PURE__ */ jsxs(
|
|
1446
|
+
"th",
|
|
1025
1447
|
{
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1448
|
+
"data-col-id": column.id,
|
|
1449
|
+
onClick: isSortable ? (e) => {
|
|
1450
|
+
if (isResizingRef.current) return;
|
|
1451
|
+
if (Date.now() - lastResizeEndTsRef.current < 150) return;
|
|
1452
|
+
const isMultiSort = e.ctrlKey || e.metaKey;
|
|
1453
|
+
handleSort(String(field), isMultiSort);
|
|
1454
|
+
} : void 0,
|
|
1455
|
+
role: isSortable ? "button" : void 0,
|
|
1456
|
+
tabIndex: isSortable ? 0 : void 0,
|
|
1457
|
+
onKeyDown: isSortable ? (e) => {
|
|
1458
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
1459
|
+
e.preventDefault();
|
|
1460
|
+
const isMultiSort = e.shiftKey;
|
|
1461
|
+
handleSort(String(field), isMultiSort);
|
|
1034
1462
|
}
|
|
1463
|
+
} : void 0,
|
|
1464
|
+
"aria-sort": isSortable && (query.sorts?.find((s) => s.field === String(field))?.priority === 0 || query.sort?.field === String(field)) ? (query.sorts?.find((s) => s.field === String(field))?.direction ?? query.sort?.direction) === "asc" ? "ascending" : "descending" : void 0,
|
|
1465
|
+
style: {
|
|
1466
|
+
width: actualWidth != null ? `${actualWidth}px` : void 0,
|
|
1467
|
+
textAlign: column.align,
|
|
1468
|
+
position: isResizable ? "relative" : void 0
|
|
1035
1469
|
},
|
|
1470
|
+
className: [
|
|
1471
|
+
column.truncate ? "rowakit-cell-truncate" : "",
|
|
1472
|
+
resizingColIdRef.current === column.id ? "resizing" : ""
|
|
1473
|
+
// PRD-01
|
|
1474
|
+
].filter(Boolean).join(" ") || void 0,
|
|
1036
1475
|
children: [
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
/* @__PURE__ */ jsx(
|
|
1476
|
+
getHeaderLabel(column),
|
|
1477
|
+
isSortable && getSortIndicator(String(field)),
|
|
1478
|
+
isResizable && /* @__PURE__ */ jsx(
|
|
1479
|
+
"div",
|
|
1480
|
+
{
|
|
1481
|
+
className: "rowakit-column-resize-handle",
|
|
1482
|
+
onPointerDown: (e) => startColumnResize(e, column.id),
|
|
1483
|
+
onDoubleClick: (e) => handleColumnResizeDoubleClick(e, column.id),
|
|
1484
|
+
title: "Drag to resize | Double-click to auto-fit content"
|
|
1485
|
+
}
|
|
1486
|
+
)
|
|
1040
1487
|
]
|
|
1041
|
-
}
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1488
|
+
},
|
|
1489
|
+
column.id
|
|
1490
|
+
);
|
|
1491
|
+
})
|
|
1492
|
+
] }),
|
|
1493
|
+
enableFilters && /* @__PURE__ */ jsxs("tr", { className: "rowakit-table-filter-row", children: [
|
|
1494
|
+
enableRowSelection && /* @__PURE__ */ jsx("th", {}),
|
|
1495
|
+
columns.map((column) => {
|
|
1496
|
+
const field = column.kind === "actions" || column.kind === "custom" ? "" : String(column.field);
|
|
1497
|
+
const canFilter = field && column.kind !== "actions";
|
|
1498
|
+
if (!canFilter) {
|
|
1499
|
+
return /* @__PURE__ */ jsx("th", {}, column.id);
|
|
1500
|
+
}
|
|
1501
|
+
const filterValue = filters[field];
|
|
1502
|
+
if (column.kind === "badge") {
|
|
1503
|
+
const options = column.map ? Object.keys(column.map) : [];
|
|
1504
|
+
return /* @__PURE__ */ jsx("th", { children: /* @__PURE__ */ jsxs(
|
|
1505
|
+
"select",
|
|
1050
1506
|
{
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
placeholder: "From",
|
|
1054
|
-
value: fromValue,
|
|
1507
|
+
className: "rowakit-filter-select",
|
|
1508
|
+
value: filterValue?.op === "equals" ? String(filterValue.value ?? "") : "",
|
|
1055
1509
|
onChange: (e) => {
|
|
1056
|
-
const
|
|
1057
|
-
|
|
1058
|
-
if (!from && !to) {
|
|
1510
|
+
const value = e.target.value;
|
|
1511
|
+
if (value === "") {
|
|
1059
1512
|
handleClearFilter(field);
|
|
1060
1513
|
} else {
|
|
1061
|
-
handleFilterChange(field, { op: "
|
|
1514
|
+
handleFilterChange(field, { op: "equals", value });
|
|
1062
1515
|
}
|
|
1063
|
-
}
|
|
1516
|
+
},
|
|
1517
|
+
children: [
|
|
1518
|
+
/* @__PURE__ */ jsx("option", { value: "", children: "All" }),
|
|
1519
|
+
options.map((opt) => /* @__PURE__ */ jsx("option", { value: opt, children: opt }, opt))
|
|
1520
|
+
]
|
|
1064
1521
|
}
|
|
1065
|
-
),
|
|
1066
|
-
|
|
1067
|
-
|
|
1522
|
+
) }, column.id);
|
|
1523
|
+
}
|
|
1524
|
+
if (column.kind === "boolean") {
|
|
1525
|
+
return /* @__PURE__ */ jsx("th", { children: /* @__PURE__ */ jsxs(
|
|
1526
|
+
"select",
|
|
1068
1527
|
{
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
placeholder: "To",
|
|
1072
|
-
value: toValue,
|
|
1528
|
+
className: "rowakit-filter-select",
|
|
1529
|
+
value: filterValue?.op === "equals" && typeof filterValue.value === "boolean" ? String(filterValue.value) : "",
|
|
1073
1530
|
onChange: (e) => {
|
|
1074
|
-
const
|
|
1075
|
-
|
|
1076
|
-
if (!from && !to) {
|
|
1531
|
+
const value = e.target.value;
|
|
1532
|
+
if (value === "") {
|
|
1077
1533
|
handleClearFilter(field);
|
|
1078
1534
|
} else {
|
|
1079
|
-
handleFilterChange(field, { op: "
|
|
1535
|
+
handleFilterChange(field, { op: "equals", value: value === "true" });
|
|
1080
1536
|
}
|
|
1081
|
-
}
|
|
1537
|
+
},
|
|
1538
|
+
children: [
|
|
1539
|
+
/* @__PURE__ */ jsx("option", { value: "", children: "All" }),
|
|
1540
|
+
/* @__PURE__ */ jsx("option", { value: "true", children: "True" }),
|
|
1541
|
+
/* @__PURE__ */ jsx("option", { value: "false", children: "False" })
|
|
1542
|
+
]
|
|
1082
1543
|
}
|
|
1083
|
-
)
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
const toValue = filterValue?.op === "range" ? String(filterValue.value.to ?? "") : "";
|
|
1090
|
-
const showRangeUI = !filterValue || filterValue.op === "range";
|
|
1091
|
-
if (showRangeUI) {
|
|
1092
|
-
return /* @__PURE__ */ jsx("th", { children: /* @__PURE__ */ jsxs("div", { className: "rowakit-filter-number-range", children: [
|
|
1544
|
+
) }, column.id);
|
|
1545
|
+
}
|
|
1546
|
+
if (column.kind === "date") {
|
|
1547
|
+
const fromValue = filterValue?.op === "range" ? filterValue.value.from ?? "" : "";
|
|
1548
|
+
const toValue = filterValue?.op === "range" ? filterValue.value.to ?? "" : "";
|
|
1549
|
+
return /* @__PURE__ */ jsx("th", { children: /* @__PURE__ */ jsxs("div", { className: "rowakit-filter-date-range", children: [
|
|
1093
1550
|
/* @__PURE__ */ jsx(
|
|
1094
1551
|
"input",
|
|
1095
1552
|
{
|
|
1096
|
-
type: "
|
|
1553
|
+
type: "date",
|
|
1097
1554
|
className: "rowakit-filter-input",
|
|
1098
|
-
placeholder: "
|
|
1555
|
+
placeholder: "From",
|
|
1099
1556
|
value: fromValue,
|
|
1100
1557
|
onChange: (e) => {
|
|
1101
|
-
const from = e.target.value
|
|
1102
|
-
const to = toValue
|
|
1103
|
-
if (from
|
|
1558
|
+
const from = e.target.value || void 0;
|
|
1559
|
+
const to = toValue || void 0;
|
|
1560
|
+
if (!from && !to) {
|
|
1104
1561
|
handleClearFilter(field);
|
|
1105
1562
|
} else {
|
|
1106
1563
|
handleFilterChange(field, { op: "range", value: { from, to } });
|
|
@@ -1111,14 +1568,14 @@ function RowaKitTable({
|
|
|
1111
1568
|
/* @__PURE__ */ jsx(
|
|
1112
1569
|
"input",
|
|
1113
1570
|
{
|
|
1114
|
-
type: "
|
|
1571
|
+
type: "date",
|
|
1115
1572
|
className: "rowakit-filter-input",
|
|
1116
|
-
placeholder: "
|
|
1573
|
+
placeholder: "To",
|
|
1117
1574
|
value: toValue,
|
|
1118
1575
|
onChange: (e) => {
|
|
1119
|
-
const to = e.target.value
|
|
1120
|
-
const from = fromValue
|
|
1121
|
-
if (from
|
|
1576
|
+
const to = e.target.value || void 0;
|
|
1577
|
+
const from = fromValue || void 0;
|
|
1578
|
+
if (!from && !to) {
|
|
1122
1579
|
handleClearFilter(field);
|
|
1123
1580
|
} else {
|
|
1124
1581
|
handleFilterChange(field, { op: "range", value: { from, to } });
|
|
@@ -1128,39 +1585,85 @@ function RowaKitTable({
|
|
|
1128
1585
|
)
|
|
1129
1586
|
] }) }, column.id);
|
|
1130
1587
|
}
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1588
|
+
const isNumberColumn = column.kind === "number";
|
|
1589
|
+
if (isNumberColumn) {
|
|
1590
|
+
const fromValue = filterValue?.op === "range" ? String(filterValue.value.from ?? "") : filterValue?.op === "equals" && typeof filterValue.value === "number" ? String(filterValue.value) : "";
|
|
1591
|
+
const toValue = filterValue?.op === "range" ? String(filterValue.value.to ?? "") : "";
|
|
1592
|
+
const showRangeUI = !filterValue || filterValue.op === "range";
|
|
1593
|
+
if (showRangeUI) {
|
|
1594
|
+
return /* @__PURE__ */ jsx("th", { children: /* @__PURE__ */ jsxs("div", { className: "rowakit-filter-number-range", children: [
|
|
1595
|
+
/* @__PURE__ */ jsx(
|
|
1596
|
+
"input",
|
|
1597
|
+
{
|
|
1598
|
+
type: "number",
|
|
1599
|
+
className: "rowakit-filter-input",
|
|
1600
|
+
placeholder: "Min",
|
|
1601
|
+
value: fromValue,
|
|
1602
|
+
onChange: (e) => {
|
|
1603
|
+
const from = e.target.value ? Number(e.target.value) : void 0;
|
|
1604
|
+
const to = toValue ? Number(toValue) : void 0;
|
|
1605
|
+
if (from === void 0 && to === void 0) {
|
|
1606
|
+
handleClearFilter(field);
|
|
1607
|
+
} else {
|
|
1608
|
+
handleFilterChange(field, { op: "range", value: { from, to } });
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
),
|
|
1613
|
+
/* @__PURE__ */ jsx(
|
|
1614
|
+
"input",
|
|
1615
|
+
{
|
|
1616
|
+
type: "number",
|
|
1617
|
+
className: "rowakit-filter-input",
|
|
1618
|
+
placeholder: "Max",
|
|
1619
|
+
value: toValue,
|
|
1620
|
+
onChange: (e) => {
|
|
1621
|
+
const to = e.target.value ? Number(e.target.value) : void 0;
|
|
1622
|
+
const from = fromValue ? Number(fromValue) : void 0;
|
|
1623
|
+
if (from === void 0 && to === void 0) {
|
|
1624
|
+
handleClearFilter(field);
|
|
1625
|
+
} else {
|
|
1626
|
+
handleFilterChange(field, { op: "range", value: { from, to } });
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
)
|
|
1631
|
+
] }) }, column.id);
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
return /* @__PURE__ */ jsx("th", { children: /* @__PURE__ */ jsx(
|
|
1635
|
+
"input",
|
|
1636
|
+
{
|
|
1637
|
+
type: isNumberColumn ? "number" : "text",
|
|
1638
|
+
className: "rowakit-filter-input",
|
|
1639
|
+
placeholder: `Filter ${getHeaderLabel(column)}...`,
|
|
1640
|
+
value: filterValue?.op === "contains" ? filterValue.value : filterValue?.op === "equals" && typeof filterValue.value === "string" ? filterValue.value : filterValue?.op === "equals" && typeof filterValue.value === "number" ? String(filterValue.value) : "",
|
|
1641
|
+
onChange: (e) => {
|
|
1642
|
+
const rawValue = e.target.value;
|
|
1643
|
+
if (rawValue === "") {
|
|
1148
1644
|
handleClearFilter(field);
|
|
1645
|
+
} else if (isNumberColumn) {
|
|
1646
|
+
const numValue = Number(rawValue);
|
|
1647
|
+
if (!isNaN(numValue)) {
|
|
1648
|
+
handleFilterChange(field, { op: "equals", value: numValue });
|
|
1649
|
+
} else {
|
|
1650
|
+
handleClearFilter(field);
|
|
1651
|
+
}
|
|
1652
|
+
} else {
|
|
1653
|
+
handleFilterChange(field, { op: "contains", value: rawValue });
|
|
1149
1654
|
}
|
|
1150
|
-
} else {
|
|
1151
|
-
handleFilterChange(field, { op: "contains", value: rawValue });
|
|
1152
1655
|
}
|
|
1153
1656
|
}
|
|
1154
|
-
}
|
|
1155
|
-
|
|
1156
|
-
|
|
1657
|
+
) }, column.id);
|
|
1658
|
+
})
|
|
1659
|
+
] })
|
|
1157
1660
|
] }),
|
|
1158
1661
|
/* @__PURE__ */ jsxs("tbody", { children: [
|
|
1159
|
-
isLoading && /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsxs("td", { colSpan:
|
|
1662
|
+
isLoading && /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsxs("td", { colSpan: tableColumnCount, className: "rowakit-table-loading", children: [
|
|
1160
1663
|
/* @__PURE__ */ jsx("div", { className: "rowakit-table-loading-spinner" }),
|
|
1161
1664
|
/* @__PURE__ */ jsx("span", { children: "Loading..." })
|
|
1162
1665
|
] }) }),
|
|
1163
|
-
isError && /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsxs("td", { colSpan:
|
|
1666
|
+
isError && /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsxs("td", { colSpan: tableColumnCount, className: "rowakit-table-error", children: [
|
|
1164
1667
|
/* @__PURE__ */ jsx("div", { className: "rowakit-table-error-message", children: dataState.error ?? "An error occurred" }),
|
|
1165
1668
|
/* @__PURE__ */ jsx(
|
|
1166
1669
|
"button",
|
|
@@ -1172,29 +1675,42 @@ function RowaKitTable({
|
|
|
1172
1675
|
}
|
|
1173
1676
|
)
|
|
1174
1677
|
] }) }),
|
|
1175
|
-
isEmpty && /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", { colSpan:
|
|
1678
|
+
isEmpty && /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", { colSpan: tableColumnCount, className: "rowakit-table-empty", children: "No data" }) }),
|
|
1176
1679
|
dataState.state === "success" && dataState.items.map((row) => {
|
|
1177
1680
|
const key = getRowKey(row, rowKey);
|
|
1178
|
-
return /* @__PURE__ */
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
column.truncate ? "rowakit-cell-truncate" : ""
|
|
1182
|
-
].filter(Boolean).join(" ") || void 0;
|
|
1183
|
-
const actualWidth = columnWidths[column.id] ?? column.width;
|
|
1184
|
-
return /* @__PURE__ */ jsx(
|
|
1185
|
-
"td",
|
|
1681
|
+
return /* @__PURE__ */ jsxs("tr", { children: [
|
|
1682
|
+
enableRowSelection && /* @__PURE__ */ jsx(
|
|
1683
|
+
RowSelectionCell,
|
|
1186
1684
|
{
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1685
|
+
rowKey: key,
|
|
1686
|
+
disabled: isLoading,
|
|
1687
|
+
checked: selectedKeys.includes(key),
|
|
1688
|
+
onChange: () => {
|
|
1689
|
+
setSelectedKeys((prev) => toggleSelectionKey(prev, key));
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
),
|
|
1693
|
+
columns.map((column) => {
|
|
1694
|
+
const cellClass = [
|
|
1695
|
+
column.kind === "number" ? "rowakit-cell-number" : "",
|
|
1696
|
+
column.truncate ? "rowakit-cell-truncate" : ""
|
|
1697
|
+
].filter(Boolean).join(" ") || void 0;
|
|
1698
|
+
const actualWidth = columnWidths[column.id] ?? column.width;
|
|
1699
|
+
return /* @__PURE__ */ jsx(
|
|
1700
|
+
"td",
|
|
1701
|
+
{
|
|
1702
|
+
"data-col-id": column.id,
|
|
1703
|
+
className: cellClass,
|
|
1704
|
+
style: {
|
|
1705
|
+
width: actualWidth != null ? `${actualWidth}px` : void 0,
|
|
1706
|
+
textAlign: column.align || (column.kind === "number" ? "right" : void 0)
|
|
1707
|
+
},
|
|
1708
|
+
children: renderCell(column, row, isLoading, setConfirmState)
|
|
1192
1709
|
},
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
}) }, key);
|
|
1710
|
+
column.id
|
|
1711
|
+
);
|
|
1712
|
+
})
|
|
1713
|
+
] }, key);
|
|
1198
1714
|
})
|
|
1199
1715
|
] })
|
|
1200
1716
|
] }),
|
|
@@ -1249,6 +1765,7 @@ function RowaKitTable({
|
|
|
1249
1765
|
confirmState && /* @__PURE__ */ jsx(
|
|
1250
1766
|
"div",
|
|
1251
1767
|
{
|
|
1768
|
+
ref: confirmModalRef,
|
|
1252
1769
|
className: "rowakit-modal-backdrop",
|
|
1253
1770
|
onClick: () => setConfirmState(null),
|
|
1254
1771
|
role: "dialog",
|
|
@@ -1286,13 +1803,51 @@ function RowaKitTable({
|
|
|
1286
1803
|
] })
|
|
1287
1804
|
] })
|
|
1288
1805
|
}
|
|
1806
|
+
),
|
|
1807
|
+
bulkConfirmState && /* @__PURE__ */ jsx(
|
|
1808
|
+
"div",
|
|
1809
|
+
{
|
|
1810
|
+
ref: bulkConfirmModalRef,
|
|
1811
|
+
className: "rowakit-modal-backdrop",
|
|
1812
|
+
onClick: () => setBulkConfirmState(null),
|
|
1813
|
+
role: "dialog",
|
|
1814
|
+
"aria-modal": "true",
|
|
1815
|
+
"aria-labelledby": "bulk-confirm-dialog-title",
|
|
1816
|
+
children: /* @__PURE__ */ jsxs("div", { className: "rowakit-modal", onClick: (e) => e.stopPropagation(), children: [
|
|
1817
|
+
/* @__PURE__ */ jsx("h2", { id: "bulk-confirm-dialog-title", className: "rowakit-modal-title", children: bulkConfirmState.action.confirm?.title ?? "Confirm Action" }),
|
|
1818
|
+
/* @__PURE__ */ jsx("p", { className: "rowakit-modal-content", children: bulkConfirmState.action.confirm?.description ?? "Are you sure you want to perform this action? This action cannot be undone." }),
|
|
1819
|
+
/* @__PURE__ */ jsxs("div", { className: "rowakit-modal-actions", children: [
|
|
1820
|
+
/* @__PURE__ */ jsx(
|
|
1821
|
+
"button",
|
|
1822
|
+
{
|
|
1823
|
+
onClick: () => setBulkConfirmState(null),
|
|
1824
|
+
className: "rowakit-button rowakit-button-secondary",
|
|
1825
|
+
type: "button",
|
|
1826
|
+
children: "Cancel"
|
|
1827
|
+
}
|
|
1828
|
+
),
|
|
1829
|
+
/* @__PURE__ */ jsx(
|
|
1830
|
+
"button",
|
|
1831
|
+
{
|
|
1832
|
+
onClick: () => {
|
|
1833
|
+
bulkConfirmState.action.onClick(bulkConfirmState.selectedKeys);
|
|
1834
|
+
setBulkConfirmState(null);
|
|
1835
|
+
},
|
|
1836
|
+
className: "rowakit-button rowakit-button-danger",
|
|
1837
|
+
type: "button",
|
|
1838
|
+
children: "Confirm"
|
|
1839
|
+
}
|
|
1840
|
+
)
|
|
1841
|
+
] })
|
|
1842
|
+
] })
|
|
1843
|
+
}
|
|
1289
1844
|
)
|
|
1290
1845
|
] });
|
|
1291
1846
|
}
|
|
1292
1847
|
var SmartTable = RowaKitTable;
|
|
1293
1848
|
|
|
1294
1849
|
// src/index.ts
|
|
1295
|
-
var VERSION = "0.
|
|
1850
|
+
var VERSION = "1.0.0" ;
|
|
1296
1851
|
|
|
1297
1852
|
export { RowaKitTable, SmartTable, VERSION, col };
|
|
1298
1853
|
//# sourceMappingURL=index.js.map
|