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