@rowakit/table 0.4.0 → 0.5.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 +136 -1416
- package/dist/index.cjs +1104 -626
- 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 +1105 -627
- 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,22 @@ 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 sortField = params.get("sortField");
|
|
574
|
+
const sortDir = params.get("sortDirection");
|
|
575
|
+
if (sortField && (sortDir === "asc" || sortDir === "desc")) {
|
|
576
|
+
result.sort = { field: sortField, direction: sortDir };
|
|
577
|
+
}
|
|
221
578
|
}
|
|
222
579
|
const filtersStr = params.get("filters");
|
|
223
580
|
if (filtersStr) {
|
|
@@ -255,7 +612,9 @@ function serializeUrlState(query, filters, columnWidths, defaultPageSize, enable
|
|
|
255
612
|
if (query.pageSize !== defaultPageSize) {
|
|
256
613
|
params.set("pageSize", String(query.pageSize));
|
|
257
614
|
}
|
|
258
|
-
if (query.
|
|
615
|
+
if (query.sorts && query.sorts.length > 0) {
|
|
616
|
+
params.set("sorts", JSON.stringify(query.sorts));
|
|
617
|
+
} else if (query.sort) {
|
|
259
618
|
params.set("sortField", query.sort.field);
|
|
260
619
|
params.set("sortDirection", query.sort.direction);
|
|
261
620
|
}
|
|
@@ -272,140 +631,22 @@ function serializeUrlState(query, filters, columnWidths, defaultPageSize, enable
|
|
|
272
631
|
}
|
|
273
632
|
return params.toString();
|
|
274
633
|
}
|
|
275
|
-
function
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
return column.format(value);
|
|
281
|
-
}
|
|
282
|
-
return String(value ?? "");
|
|
283
|
-
}
|
|
284
|
-
case "date": {
|
|
285
|
-
const value = row[column.field];
|
|
286
|
-
if (column.format) {
|
|
287
|
-
return column.format(value);
|
|
288
|
-
}
|
|
289
|
-
if (value instanceof Date) {
|
|
290
|
-
return value.toLocaleDateString();
|
|
291
|
-
}
|
|
292
|
-
if (typeof value === "string" || typeof value === "number") {
|
|
293
|
-
return new Date(value).toLocaleDateString();
|
|
294
|
-
}
|
|
295
|
-
return "";
|
|
296
|
-
}
|
|
297
|
-
case "boolean": {
|
|
298
|
-
const value = row[column.field];
|
|
299
|
-
if (column.format) {
|
|
300
|
-
return column.format(Boolean(value));
|
|
301
|
-
}
|
|
302
|
-
return value ? "Yes" : "No";
|
|
303
|
-
}
|
|
304
|
-
case "badge": {
|
|
305
|
-
const value = row[column.field];
|
|
306
|
-
const valueStr = String(value ?? "");
|
|
307
|
-
const mapped = column.map?.[valueStr];
|
|
308
|
-
const label = mapped?.label ?? valueStr;
|
|
309
|
-
const tone = mapped?.tone ?? "neutral";
|
|
310
|
-
return /* @__PURE__ */ jsxRuntime.jsx("span", { className: `rowakit-badge rowakit-badge-${tone}`, children: label });
|
|
311
|
-
}
|
|
312
|
-
case "number": {
|
|
313
|
-
const value = row[column.field];
|
|
314
|
-
const numValue = Number(value ?? 0);
|
|
315
|
-
if (column.format) {
|
|
316
|
-
if (typeof column.format === "function") {
|
|
317
|
-
return column.format(numValue, row);
|
|
318
|
-
}
|
|
319
|
-
return new Intl.NumberFormat(void 0, column.format).format(numValue);
|
|
320
|
-
}
|
|
321
|
-
return numValue.toLocaleString();
|
|
322
|
-
}
|
|
323
|
-
case "actions": {
|
|
324
|
-
const columnWithActions = column;
|
|
325
|
-
if (!Array.isArray(columnWithActions.actions)) {
|
|
326
|
-
return null;
|
|
327
|
-
}
|
|
328
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rowakit-table-actions", children: columnWithActions.actions.map((action) => {
|
|
329
|
-
const isDisabled = isLoading || action.disabled === true || typeof action.disabled === "function" && action.disabled(row);
|
|
330
|
-
const handleClick = () => {
|
|
331
|
-
if (isDisabled || action.loading) {
|
|
332
|
-
return;
|
|
333
|
-
}
|
|
334
|
-
if (action.confirm) {
|
|
335
|
-
setConfirmState({ action, row });
|
|
336
|
-
} else {
|
|
337
|
-
void action.onClick(row);
|
|
338
|
-
}
|
|
339
|
-
};
|
|
340
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
341
|
-
"button",
|
|
342
|
-
{
|
|
343
|
-
onClick: handleClick,
|
|
344
|
-
disabled: isDisabled || action.loading,
|
|
345
|
-
type: "button",
|
|
346
|
-
className: "rowakit-button rowakit-button-secondary",
|
|
347
|
-
children: [
|
|
348
|
-
action.icon && typeof action.icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx("span", { children: action.icon }) : action.icon,
|
|
349
|
-
action.label
|
|
350
|
-
]
|
|
351
|
-
},
|
|
352
|
-
action.id
|
|
353
|
-
);
|
|
354
|
-
}) });
|
|
355
|
-
}
|
|
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,
|
|
634
|
+
function useUrlSync({
|
|
635
|
+
syncToUrl,
|
|
636
|
+
enableColumnResizing,
|
|
637
|
+
defaultPageSize,
|
|
638
|
+
pageSizeOptions,
|
|
367
639
|
columns,
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
syncToUrl = false,
|
|
375
|
-
enableSavedViews = false
|
|
640
|
+
query,
|
|
641
|
+
setQuery,
|
|
642
|
+
filters,
|
|
643
|
+
setFilters,
|
|
644
|
+
columnWidths,
|
|
645
|
+
setColumnWidths
|
|
376
646
|
}) {
|
|
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
647
|
const didHydrateUrlRef = react.useRef(false);
|
|
395
648
|
const didSkipInitialUrlSyncRef = react.useRef(false);
|
|
396
649
|
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
650
|
react.useEffect(() => {
|
|
410
651
|
if (!syncToUrl) {
|
|
411
652
|
didSkipInitialUrlSyncRef.current = false;
|
|
@@ -422,7 +663,14 @@ function RowaKitTable({
|
|
|
422
663
|
const urlStr = serializeUrlState(query, filters, columnWidths, defaultPageSize, enableColumnResizing);
|
|
423
664
|
const qs = urlStr ? `?${urlStr}` : "";
|
|
424
665
|
window.history.replaceState(null, "", `${window.location.pathname}${qs}${window.location.hash}`);
|
|
425
|
-
}, [
|
|
666
|
+
}, [
|
|
667
|
+
query,
|
|
668
|
+
filters,
|
|
669
|
+
syncToUrl,
|
|
670
|
+
enableColumnResizing,
|
|
671
|
+
defaultPageSize,
|
|
672
|
+
columnWidths
|
|
673
|
+
]);
|
|
426
674
|
react.useEffect(() => {
|
|
427
675
|
if (!syncToUrl || !enableColumnResizing) return;
|
|
428
676
|
if (!didSkipInitialUrlSyncRef.current) return;
|
|
@@ -441,7 +689,14 @@ function RowaKitTable({
|
|
|
441
689
|
urlSyncDebounceRef.current = null;
|
|
442
690
|
}
|
|
443
691
|
};
|
|
444
|
-
}, [
|
|
692
|
+
}, [
|
|
693
|
+
columnWidths,
|
|
694
|
+
syncToUrl,
|
|
695
|
+
enableColumnResizing,
|
|
696
|
+
query,
|
|
697
|
+
filters,
|
|
698
|
+
defaultPageSize
|
|
699
|
+
]);
|
|
445
700
|
react.useEffect(() => {
|
|
446
701
|
if (!syncToUrl) {
|
|
447
702
|
didHydrateUrlRef.current = false;
|
|
@@ -455,6 +710,7 @@ function RowaKitTable({
|
|
|
455
710
|
page: parsed.page,
|
|
456
711
|
pageSize: parsed.pageSize,
|
|
457
712
|
sort: parsed.sort,
|
|
713
|
+
sorts: parsed.sorts,
|
|
458
714
|
filters: parsed.filters
|
|
459
715
|
});
|
|
460
716
|
if (parsed.filters) {
|
|
@@ -477,256 +733,438 @@ function RowaKitTable({
|
|
|
477
733
|
}
|
|
478
734
|
setColumnWidths(clamped);
|
|
479
735
|
}
|
|
480
|
-
}, [
|
|
736
|
+
}, [
|
|
737
|
+
syncToUrl,
|
|
738
|
+
defaultPageSize,
|
|
739
|
+
enableColumnResizing,
|
|
740
|
+
pageSizeOptions,
|
|
741
|
+
columns,
|
|
742
|
+
setQuery,
|
|
743
|
+
setFilters,
|
|
744
|
+
setColumnWidths
|
|
745
|
+
]);
|
|
746
|
+
}
|
|
747
|
+
var FOCUSABLE_SELECTORS = [
|
|
748
|
+
"button",
|
|
749
|
+
"[href]",
|
|
750
|
+
"input",
|
|
751
|
+
"select",
|
|
752
|
+
"textarea",
|
|
753
|
+
'[tabindex]:not([tabindex="-1"])'
|
|
754
|
+
].join(",");
|
|
755
|
+
function useFocusTrap(ref, options = {}) {
|
|
756
|
+
const { onEscape, autoFocus = true } = options;
|
|
757
|
+
const firstFocusableRef = react.useRef(null);
|
|
758
|
+
const lastFocusableRef = react.useRef(null);
|
|
481
759
|
react.useEffect(() => {
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
760
|
+
const modalEl = ref.current;
|
|
761
|
+
if (!modalEl) return;
|
|
762
|
+
const getFocusableElements = () => {
|
|
763
|
+
const elements = Array.from(modalEl.querySelectorAll(FOCUSABLE_SELECTORS));
|
|
764
|
+
return elements.filter((el) => !el.hasAttribute("disabled") && el.offsetParent !== null);
|
|
765
|
+
};
|
|
766
|
+
let focusableElements = getFocusableElements();
|
|
767
|
+
if (focusableElements.length === 0) return;
|
|
768
|
+
firstFocusableRef.current = focusableElements[0] || null;
|
|
769
|
+
lastFocusableRef.current = focusableElements[focusableElements.length - 1] || null;
|
|
770
|
+
if (autoFocus && firstFocusableRef.current) {
|
|
771
|
+
firstFocusableRef.current.focus();
|
|
490
772
|
}
|
|
491
|
-
const
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
const currentRequestId = ++requestIdRef.current;
|
|
501
|
-
setDataState((prev) => ({ ...prev, state: "loading" }));
|
|
502
|
-
fetcher(query).then((result) => {
|
|
503
|
-
if (currentRequestId !== requestIdRef.current) {
|
|
773
|
+
const handleKeyDown = (e) => {
|
|
774
|
+
focusableElements = getFocusableElements();
|
|
775
|
+
if (focusableElements.length === 0) return;
|
|
776
|
+
const activeEl = document.activeElement;
|
|
777
|
+
const firstEl = focusableElements[0] || null;
|
|
778
|
+
const lastEl = focusableElements[focusableElements.length - 1] || null;
|
|
779
|
+
if (e.key === "Escape") {
|
|
780
|
+
e.preventDefault();
|
|
781
|
+
onEscape?.();
|
|
504
782
|
return;
|
|
505
783
|
}
|
|
506
|
-
if (
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
}
|
|
784
|
+
if (e.key === "Tab") {
|
|
785
|
+
if (e.shiftKey) {
|
|
786
|
+
if (activeEl === firstEl && lastEl) {
|
|
787
|
+
e.preventDefault();
|
|
788
|
+
lastEl.focus();
|
|
789
|
+
}
|
|
790
|
+
} else {
|
|
791
|
+
if (activeEl === lastEl && firstEl) {
|
|
792
|
+
e.preventDefault();
|
|
793
|
+
firstEl.focus();
|
|
794
|
+
}
|
|
795
|
+
}
|
|
518
796
|
}
|
|
519
|
-
}
|
|
520
|
-
|
|
797
|
+
};
|
|
798
|
+
modalEl.addEventListener("keydown", handleKeyDown);
|
|
799
|
+
return () => {
|
|
800
|
+
modalEl.removeEventListener("keydown", handleKeyDown);
|
|
801
|
+
};
|
|
802
|
+
}, [ref, onEscape, autoFocus]);
|
|
803
|
+
}
|
|
804
|
+
function RowSelectionHeaderCell(props) {
|
|
805
|
+
const checkboxRef = react.useRef(null);
|
|
806
|
+
react.useEffect(() => {
|
|
807
|
+
if (!checkboxRef.current) return;
|
|
808
|
+
checkboxRef.current.indeterminate = props.indeterminate;
|
|
809
|
+
}, [props.indeterminate]);
|
|
810
|
+
return /* @__PURE__ */ jsxRuntime.jsx("th", { children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
811
|
+
"input",
|
|
812
|
+
{
|
|
813
|
+
ref: checkboxRef,
|
|
814
|
+
type: "checkbox",
|
|
815
|
+
"aria-label": "Select all rows",
|
|
816
|
+
disabled: props.disabled,
|
|
817
|
+
checked: props.checked,
|
|
818
|
+
onChange: (e) => props.onChange(e.target.checked)
|
|
819
|
+
}
|
|
820
|
+
) });
|
|
821
|
+
}
|
|
822
|
+
function RowSelectionCell(props) {
|
|
823
|
+
return /* @__PURE__ */ jsxRuntime.jsx("td", { children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
824
|
+
"input",
|
|
825
|
+
{
|
|
826
|
+
type: "checkbox",
|
|
827
|
+
"aria-label": `Select row ${props.rowKey}`,
|
|
828
|
+
disabled: props.disabled,
|
|
829
|
+
checked: props.checked,
|
|
830
|
+
onChange: (e) => props.onChange(e.target.checked)
|
|
831
|
+
}
|
|
832
|
+
) });
|
|
833
|
+
}
|
|
834
|
+
function BulkActionBar(props) {
|
|
835
|
+
if (props.selectedCount <= 0) return null;
|
|
836
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rowakit-bulk-action-bar", children: [
|
|
837
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
838
|
+
props.selectedCount,
|
|
839
|
+
" selected"
|
|
840
|
+
] }),
|
|
841
|
+
props.actions.map((action) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
842
|
+
"button",
|
|
843
|
+
{
|
|
844
|
+
type: "button",
|
|
845
|
+
className: "rowakit-button rowakit-button-secondary",
|
|
846
|
+
onClick: () => props.onActionClick(action.id),
|
|
847
|
+
children: action.label
|
|
848
|
+
},
|
|
849
|
+
action.id
|
|
850
|
+
))
|
|
851
|
+
] });
|
|
852
|
+
}
|
|
853
|
+
function downloadBlob(blob, filename) {
|
|
854
|
+
if (typeof window === "undefined") return;
|
|
855
|
+
if (typeof URL === "undefined" || typeof URL.createObjectURL !== "function") return;
|
|
856
|
+
const url = URL.createObjectURL(blob);
|
|
857
|
+
try {
|
|
858
|
+
const a = document.createElement("a");
|
|
859
|
+
a.href = url;
|
|
860
|
+
a.download = filename;
|
|
861
|
+
a.rel = "noopener noreferrer";
|
|
862
|
+
a.click();
|
|
863
|
+
} finally {
|
|
864
|
+
URL.revokeObjectURL(url);
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
function openUrl(url) {
|
|
868
|
+
if (typeof window === "undefined") return;
|
|
869
|
+
if (typeof window.open === "function") {
|
|
870
|
+
window.open(url, "_blank", "noopener,noreferrer");
|
|
871
|
+
return;
|
|
872
|
+
}
|
|
873
|
+
try {
|
|
874
|
+
window.location.assign(url);
|
|
875
|
+
} catch {
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
function ExportButton(props) {
|
|
879
|
+
const [isExporting, setIsExporting] = react.useState(false);
|
|
880
|
+
const [error, setError] = react.useState(null);
|
|
881
|
+
const onClick = async () => {
|
|
882
|
+
if (isExporting) return;
|
|
883
|
+
setIsExporting(true);
|
|
884
|
+
setError(null);
|
|
885
|
+
try {
|
|
886
|
+
const snapshot = { ...props.query };
|
|
887
|
+
const result = await props.exporter(snapshot);
|
|
888
|
+
if (result instanceof Blob) {
|
|
889
|
+
downloadBlob(result, "rowakit-export.csv");
|
|
521
890
|
return;
|
|
522
891
|
}
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
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 }));
|
|
892
|
+
openUrl(result.url);
|
|
893
|
+
} catch (e) {
|
|
894
|
+
setError(e instanceof Error ? e.message : "Export failed");
|
|
895
|
+
} finally {
|
|
896
|
+
setIsExporting(false);
|
|
537
897
|
}
|
|
538
898
|
};
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
899
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rowakit-export", children: [
|
|
900
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
901
|
+
"button",
|
|
902
|
+
{
|
|
903
|
+
type: "button",
|
|
904
|
+
className: "rowakit-button rowakit-button-secondary",
|
|
905
|
+
onClick,
|
|
906
|
+
disabled: isExporting,
|
|
907
|
+
children: isExporting ? "Exporting\u2026" : "Export CSV"
|
|
908
|
+
}
|
|
909
|
+
),
|
|
910
|
+
error && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rowakit-export-error", children: error })
|
|
911
|
+
] });
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
// src/state/selection.ts
|
|
915
|
+
function toggleSelectionKey(selected, key) {
|
|
916
|
+
if (selected.includes(key)) {
|
|
917
|
+
return selected.filter((k) => k !== key);
|
|
918
|
+
}
|
|
919
|
+
return [...selected, key];
|
|
920
|
+
}
|
|
921
|
+
function isAllSelected(selected, pageKeys) {
|
|
922
|
+
if (pageKeys.length === 0) return false;
|
|
923
|
+
return pageKeys.every((k) => selected.includes(k));
|
|
924
|
+
}
|
|
925
|
+
function isIndeterminate(selected, pageKeys) {
|
|
926
|
+
if (pageKeys.length === 0) return false;
|
|
927
|
+
const selectedCount = pageKeys.filter((k) => selected.includes(k)).length;
|
|
928
|
+
return selectedCount > 0 && selectedCount < pageKeys.length;
|
|
929
|
+
}
|
|
930
|
+
function selectAll(pageKeys) {
|
|
931
|
+
return [...pageKeys];
|
|
932
|
+
}
|
|
933
|
+
function clearSelection() {
|
|
934
|
+
return [];
|
|
935
|
+
}
|
|
936
|
+
function getRowKey(row, rowKey) {
|
|
937
|
+
if (typeof rowKey === "function") {
|
|
938
|
+
return rowKey(row);
|
|
939
|
+
}
|
|
940
|
+
if (rowKey) {
|
|
941
|
+
return String(row[rowKey]);
|
|
942
|
+
}
|
|
943
|
+
if (row && typeof row === "object" && "id" in row) {
|
|
944
|
+
return String(row.id);
|
|
945
|
+
}
|
|
946
|
+
return String(row);
|
|
947
|
+
}
|
|
948
|
+
function getHeaderLabel(column) {
|
|
949
|
+
return column.header ?? column.id;
|
|
950
|
+
}
|
|
951
|
+
function renderCell(column, row, isLoading, setConfirmState) {
|
|
952
|
+
switch (column.kind) {
|
|
953
|
+
case "text": {
|
|
954
|
+
const value = row[column.field];
|
|
955
|
+
if (column.format) {
|
|
956
|
+
return column.format(value);
|
|
957
|
+
}
|
|
958
|
+
return String(value ?? "");
|
|
543
959
|
}
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
const handleSort = (field) => {
|
|
549
|
-
setQuery((prev) => {
|
|
550
|
-
if (prev.sort?.field !== field) {
|
|
551
|
-
return { ...prev, sort: { field, direction: "asc" }, page: 1 };
|
|
960
|
+
case "date": {
|
|
961
|
+
const value = row[column.field];
|
|
962
|
+
if (column.format) {
|
|
963
|
+
return column.format(value);
|
|
552
964
|
}
|
|
553
|
-
if (
|
|
554
|
-
return
|
|
965
|
+
if (value instanceof Date) {
|
|
966
|
+
return value.toLocaleDateString();
|
|
967
|
+
}
|
|
968
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
969
|
+
return new Date(value).toLocaleDateString();
|
|
555
970
|
}
|
|
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
971
|
return "";
|
|
563
972
|
}
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
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;
|
|
973
|
+
case "boolean": {
|
|
974
|
+
const value = row[column.field];
|
|
975
|
+
if (column.format) {
|
|
976
|
+
return column.format(Boolean(value));
|
|
977
|
+
}
|
|
978
|
+
return value ? "Yes" : "No";
|
|
585
979
|
}
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
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;
|
|
980
|
+
case "badge": {
|
|
981
|
+
const value = row[column.field];
|
|
982
|
+
const valueStr = String(value ?? "");
|
|
983
|
+
const mapped = column.map?.[valueStr];
|
|
984
|
+
const label = mapped?.label ?? valueStr;
|
|
985
|
+
const tone = mapped?.tone ?? "neutral";
|
|
986
|
+
return /* @__PURE__ */ jsxRuntime.jsx("span", { className: `rowakit-badge rowakit-badge-${tone}`, children: label });
|
|
615
987
|
}
|
|
616
|
-
|
|
617
|
-
|
|
988
|
+
case "number": {
|
|
989
|
+
const value = row[column.field];
|
|
990
|
+
const numValue = Number(value ?? 0);
|
|
991
|
+
if (column.format) {
|
|
992
|
+
if (typeof column.format === "function") {
|
|
993
|
+
return column.format(numValue, row);
|
|
994
|
+
}
|
|
995
|
+
return new Intl.NumberFormat(void 0, column.format).format(numValue);
|
|
996
|
+
}
|
|
997
|
+
return numValue.toLocaleString();
|
|
618
998
|
}
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
999
|
+
case "actions": {
|
|
1000
|
+
const columnWithActions = column;
|
|
1001
|
+
if (!Array.isArray(columnWithActions.actions)) {
|
|
1002
|
+
return null;
|
|
1003
|
+
}
|
|
1004
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rowakit-table-actions", children: columnWithActions.actions.map((action) => {
|
|
1005
|
+
const isDisabled = isLoading || action.disabled === true || typeof action.disabled === "function" && action.disabled(row);
|
|
1006
|
+
const handleClick = () => {
|
|
1007
|
+
if (isDisabled || action.loading) {
|
|
1008
|
+
return;
|
|
1009
|
+
}
|
|
1010
|
+
if (action.confirm) {
|
|
1011
|
+
setConfirmState({ action, row });
|
|
1012
|
+
} else {
|
|
1013
|
+
void action.onClick(row);
|
|
1014
|
+
}
|
|
1015
|
+
};
|
|
1016
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1017
|
+
"button",
|
|
1018
|
+
{
|
|
1019
|
+
onClick: handleClick,
|
|
1020
|
+
disabled: isDisabled || action.loading,
|
|
1021
|
+
type: "button",
|
|
1022
|
+
className: "rowakit-button rowakit-button-secondary",
|
|
1023
|
+
children: [
|
|
1024
|
+
action.icon && typeof action.icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx("span", { children: action.icon }) : action.icon,
|
|
1025
|
+
action.label
|
|
1026
|
+
]
|
|
1027
|
+
},
|
|
1028
|
+
action.id
|
|
1029
|
+
);
|
|
1030
|
+
}) });
|
|
624
1031
|
}
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
const startX = e.clientX;
|
|
628
|
-
const th = target.parentElement;
|
|
629
|
-
let startWidth = columnWidths[columnId] ?? th.offsetWidth;
|
|
630
|
-
const MIN_DRAG_WIDTH = 80;
|
|
631
|
-
if (startWidth < MIN_DRAG_WIDTH) {
|
|
632
|
-
const nextTh = th.nextElementSibling;
|
|
633
|
-
if (nextTh && nextTh.offsetWidth >= 50) {
|
|
634
|
-
startWidth = nextTh.offsetWidth;
|
|
635
|
-
} else {
|
|
636
|
-
startWidth = 100;
|
|
637
|
-
}
|
|
1032
|
+
case "custom": {
|
|
1033
|
+
return column.render(row);
|
|
638
1034
|
}
|
|
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
|
-
const
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
1035
|
+
default: {
|
|
1036
|
+
const _exhaustive = column;
|
|
1037
|
+
return _exhaustive;
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
function RowaKitTable({
|
|
1042
|
+
fetcher,
|
|
1043
|
+
columns,
|
|
1044
|
+
defaultPageSize = 20,
|
|
1045
|
+
pageSizeOptions = [10, 20, 50],
|
|
1046
|
+
rowKey,
|
|
1047
|
+
className = "",
|
|
1048
|
+
enableFilters = false,
|
|
1049
|
+
enableColumnResizing = false,
|
|
1050
|
+
syncToUrl = false,
|
|
1051
|
+
enableSavedViews = false,
|
|
1052
|
+
enableRowSelection = false,
|
|
1053
|
+
onSelectionChange,
|
|
1054
|
+
bulkActions,
|
|
1055
|
+
exporter
|
|
1056
|
+
}) {
|
|
1057
|
+
const [query, setQuery] = react.useState({
|
|
1058
|
+
page: 1,
|
|
1059
|
+
pageSize: defaultPageSize
|
|
1060
|
+
});
|
|
1061
|
+
const [filters, setFilters] = react.useState({});
|
|
1062
|
+
const [confirmState, setConfirmState] = react.useState(null);
|
|
1063
|
+
const [selectedKeys, setSelectedKeys] = react.useState([]);
|
|
1064
|
+
const [bulkConfirmState, setBulkConfirmState] = react.useState(null);
|
|
1065
|
+
const confirmModalRef = react.useRef(null);
|
|
1066
|
+
const bulkConfirmModalRef = react.useRef(null);
|
|
1067
|
+
const {
|
|
1068
|
+
tableRef,
|
|
1069
|
+
columnWidths,
|
|
1070
|
+
setColumnWidths,
|
|
1071
|
+
startColumnResize,
|
|
1072
|
+
handleColumnResizeDoubleClick,
|
|
1073
|
+
isResizingRef,
|
|
1074
|
+
lastResizeEndTsRef,
|
|
1075
|
+
resizingColIdRef
|
|
1076
|
+
} = useColumnResizing(columns);
|
|
1077
|
+
useUrlSync({
|
|
1078
|
+
syncToUrl,
|
|
1079
|
+
enableColumnResizing,
|
|
1080
|
+
defaultPageSize,
|
|
1081
|
+
pageSizeOptions,
|
|
1082
|
+
columns,
|
|
1083
|
+
query,
|
|
1084
|
+
setQuery,
|
|
1085
|
+
filters,
|
|
1086
|
+
setFilters,
|
|
1087
|
+
columnWidths,
|
|
1088
|
+
setColumnWidths
|
|
1089
|
+
});
|
|
1090
|
+
const { dataState, handleRetry, isLoading, isError, isEmpty } = useFetcherState(fetcher, query, setQuery);
|
|
1091
|
+
const { handleSort, getSortIndicator } = useSortingState(query, setQuery);
|
|
1092
|
+
const pageRowKeys = dataState.items.map((row) => getRowKey(row, rowKey));
|
|
1093
|
+
const headerChecked = isAllSelected(selectedKeys, pageRowKeys);
|
|
1094
|
+
const headerIndeterminate = isIndeterminate(selectedKeys, pageRowKeys);
|
|
1095
|
+
react.useEffect(() => {
|
|
1096
|
+
if (!enableRowSelection) return;
|
|
1097
|
+
setSelectedKeys(clearSelection());
|
|
1098
|
+
}, [enableRowSelection, query.page, dataState.items]);
|
|
1099
|
+
react.useEffect(() => {
|
|
1100
|
+
if (!enableRowSelection || !onSelectionChange) return;
|
|
1101
|
+
onSelectionChange(selectedKeys);
|
|
1102
|
+
}, [enableRowSelection, onSelectionChange, selectedKeys]);
|
|
1103
|
+
const {
|
|
1104
|
+
savedViews,
|
|
1105
|
+
showSaveViewForm,
|
|
1106
|
+
saveViewInput,
|
|
1107
|
+
saveViewError,
|
|
1108
|
+
overwriteConfirmName,
|
|
1109
|
+
openSaveViewForm,
|
|
1110
|
+
cancelSaveViewForm,
|
|
1111
|
+
onSaveViewInputChange,
|
|
1112
|
+
onSaveViewInputKeyDown,
|
|
1113
|
+
attemptSave,
|
|
1114
|
+
confirmOverwrite,
|
|
1115
|
+
cancelOverwrite,
|
|
1116
|
+
loadSavedView,
|
|
1117
|
+
deleteSavedView,
|
|
1118
|
+
resetTableState
|
|
1119
|
+
} = useSavedViews({
|
|
1120
|
+
enableSavedViews,
|
|
1121
|
+
enableColumnResizing,
|
|
1122
|
+
defaultPageSize,
|
|
1123
|
+
query,
|
|
1124
|
+
setQuery,
|
|
1125
|
+
setFilters,
|
|
1126
|
+
columnWidths,
|
|
1127
|
+
setColumnWidths
|
|
1128
|
+
});
|
|
1129
|
+
useFocusTrap(confirmModalRef, {
|
|
1130
|
+
onEscape: () => setConfirmState(null),
|
|
1131
|
+
autoFocus: true
|
|
1132
|
+
});
|
|
1133
|
+
useFocusTrap(bulkConfirmModalRef, {
|
|
1134
|
+
onEscape: () => setBulkConfirmState(null),
|
|
1135
|
+
autoFocus: true
|
|
1136
|
+
});
|
|
1137
|
+
react.useEffect(() => {
|
|
1138
|
+
if (!enableFilters) return;
|
|
1139
|
+
const activeFilters = {};
|
|
1140
|
+
let hasFilters = false;
|
|
1141
|
+
for (const [field, value] of Object.entries(filters)) {
|
|
1142
|
+
if (value !== void 0) {
|
|
1143
|
+
activeFilters[field] = value;
|
|
1144
|
+
hasFilters = true;
|
|
693
1145
|
}
|
|
694
1146
|
}
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
setFilters(state.filters ?? {});
|
|
707
|
-
if (state.columnWidths && enableColumnResizing) {
|
|
708
|
-
setColumnWidths(state.columnWidths);
|
|
1147
|
+
const filtersToSend = hasFilters ? activeFilters : void 0;
|
|
1148
|
+
setQuery((prev) => ({
|
|
1149
|
+
...prev,
|
|
1150
|
+
filters: filtersToSend,
|
|
1151
|
+
page: 1
|
|
1152
|
+
// Reset page to 1 when filters change
|
|
1153
|
+
}));
|
|
1154
|
+
}, [filters, enableFilters]);
|
|
1155
|
+
const handlePreviousPage = () => {
|
|
1156
|
+
if (query.page > 1) {
|
|
1157
|
+
setQuery((prev) => ({ ...prev, page: prev.page - 1 }));
|
|
709
1158
|
}
|
|
710
1159
|
};
|
|
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
|
-
}
|
|
1160
|
+
const handleNextPage = () => {
|
|
1161
|
+
const totalPages2 = Math.ceil(dataState.total / query.pageSize);
|
|
1162
|
+
if (query.page < totalPages2) {
|
|
1163
|
+
setQuery((prev) => ({ ...prev, page: prev.page + 1 }));
|
|
721
1164
|
}
|
|
722
1165
|
};
|
|
723
|
-
const
|
|
724
|
-
setQuery({
|
|
725
|
-
page: 1,
|
|
726
|
-
pageSize: defaultPageSize
|
|
727
|
-
});
|
|
728
|
-
setFilters({});
|
|
729
|
-
setColumnWidths({});
|
|
1166
|
+
const handlePageSizeChange = (newPageSize) => {
|
|
1167
|
+
setQuery({ ...query, pageSize: newPageSize, page: 1 });
|
|
730
1168
|
};
|
|
731
1169
|
const transformFilterValueForColumn = (column, value) => {
|
|
732
1170
|
if (!value || column?.kind !== "number") {
|
|
@@ -772,12 +1210,10 @@ function RowaKitTable({
|
|
|
772
1210
|
const handleClearAllFilters = () => {
|
|
773
1211
|
setFilters({});
|
|
774
1212
|
};
|
|
775
|
-
const isLoading = dataState.state === "loading";
|
|
776
|
-
const isError = dataState.state === "error";
|
|
777
|
-
const isEmpty = dataState.state === "empty";
|
|
778
1213
|
const totalPages = Math.ceil(dataState.total / query.pageSize);
|
|
779
1214
|
const canGoPrevious = query.page > 1 && !isLoading;
|
|
780
1215
|
const canGoNext = query.page < totalPages && !isLoading;
|
|
1216
|
+
const tableColumnCount = columns.length + (enableRowSelection ? 1 : 0);
|
|
781
1217
|
const hasActiveFilters = enableFilters && Object.values(filters).some((v) => v !== void 0);
|
|
782
1218
|
const containerClass = [
|
|
783
1219
|
"rowakit-table",
|
|
@@ -785,16 +1221,29 @@ function RowaKitTable({
|
|
|
785
1221
|
className
|
|
786
1222
|
].filter(Boolean).join(" ");
|
|
787
1223
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: containerClass, children: [
|
|
1224
|
+
enableRowSelection && bulkActions && bulkActions.length > 0 && selectedKeys.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1225
|
+
BulkActionBar,
|
|
1226
|
+
{
|
|
1227
|
+
selectedCount: selectedKeys.length,
|
|
1228
|
+
actions: bulkActions,
|
|
1229
|
+
onActionClick: (actionId) => {
|
|
1230
|
+
const action = bulkActions.find((a) => a.id === actionId);
|
|
1231
|
+
if (!action) return;
|
|
1232
|
+
const snapshot = [...selectedKeys];
|
|
1233
|
+
if (action.confirm) {
|
|
1234
|
+
setBulkConfirmState({ action, selectedKeys: snapshot });
|
|
1235
|
+
return;
|
|
1236
|
+
}
|
|
1237
|
+
action.onClick(snapshot);
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
),
|
|
1241
|
+
exporter && /* @__PURE__ */ jsxRuntime.jsx(ExportButton, { exporter, query }),
|
|
788
1242
|
enableSavedViews && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rowakit-saved-views-group", children: [
|
|
789
1243
|
!showSaveViewForm ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
790
1244
|
"button",
|
|
791
1245
|
{
|
|
792
|
-
onClick:
|
|
793
|
-
setShowSaveViewForm(true);
|
|
794
|
-
setSaveViewInput("");
|
|
795
|
-
setSaveViewError("");
|
|
796
|
-
setOverwriteConfirmName(null);
|
|
797
|
-
},
|
|
1246
|
+
onClick: openSaveViewForm,
|
|
798
1247
|
className: "rowakit-saved-view-button",
|
|
799
1248
|
type: "button",
|
|
800
1249
|
children: "Save View"
|
|
@@ -808,13 +1257,7 @@ function RowaKitTable({
|
|
|
808
1257
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
809
1258
|
"button",
|
|
810
1259
|
{
|
|
811
|
-
onClick:
|
|
812
|
-
saveCurrentView(overwriteConfirmName);
|
|
813
|
-
setShowSaveViewForm(false);
|
|
814
|
-
setSaveViewInput("");
|
|
815
|
-
setSaveViewError("");
|
|
816
|
-
setOverwriteConfirmName(null);
|
|
817
|
-
},
|
|
1260
|
+
onClick: confirmOverwrite,
|
|
818
1261
|
className: "rowakit-saved-view-button",
|
|
819
1262
|
type: "button",
|
|
820
1263
|
children: "Overwrite"
|
|
@@ -823,9 +1266,7 @@ function RowaKitTable({
|
|
|
823
1266
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
824
1267
|
"button",
|
|
825
1268
|
{
|
|
826
|
-
onClick:
|
|
827
|
-
setOverwriteConfirmName(null);
|
|
828
|
-
},
|
|
1269
|
+
onClick: cancelOverwrite,
|
|
829
1270
|
className: "rowakit-saved-view-button",
|
|
830
1271
|
type: "button",
|
|
831
1272
|
children: "Cancel"
|
|
@@ -837,27 +1278,8 @@ function RowaKitTable({
|
|
|
837
1278
|
{
|
|
838
1279
|
type: "text",
|
|
839
1280
|
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
|
-
},
|
|
1281
|
+
onChange: onSaveViewInputChange,
|
|
1282
|
+
onKeyDown: onSaveViewInputKeyDown,
|
|
861
1283
|
placeholder: "Enter view name...",
|
|
862
1284
|
className: "rowakit-save-view-input"
|
|
863
1285
|
}
|
|
@@ -866,21 +1288,7 @@ function RowaKitTable({
|
|
|
866
1288
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
867
1289
|
"button",
|
|
868
1290
|
{
|
|
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
|
-
},
|
|
1291
|
+
onClick: attemptSave,
|
|
884
1292
|
className: "rowakit-saved-view-button",
|
|
885
1293
|
type: "button",
|
|
886
1294
|
children: "Save"
|
|
@@ -889,11 +1297,7 @@ function RowaKitTable({
|
|
|
889
1297
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
890
1298
|
"button",
|
|
891
1299
|
{
|
|
892
|
-
onClick:
|
|
893
|
-
setShowSaveViewForm(false);
|
|
894
|
-
setSaveViewInput("");
|
|
895
|
-
setSaveViewError("");
|
|
896
|
-
},
|
|
1300
|
+
onClick: cancelSaveViewForm,
|
|
897
1301
|
className: "rowakit-saved-view-button",
|
|
898
1302
|
type: "button",
|
|
899
1303
|
children: "Cancel"
|
|
@@ -942,167 +1346,143 @@ function RowaKitTable({
|
|
|
942
1346
|
) }),
|
|
943
1347
|
/* @__PURE__ */ jsxRuntime.jsxs("table", { ref: tableRef, children: [
|
|
944
1348
|
/* @__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",
|
|
1349
|
+
/* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
|
|
1350
|
+
enableRowSelection && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1351
|
+
RowSelectionHeaderCell,
|
|
952
1352
|
{
|
|
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));
|
|
1353
|
+
checked: headerChecked,
|
|
1354
|
+
indeterminate: headerIndeterminate,
|
|
1355
|
+
disabled: isLoading || pageRowKeys.length === 0,
|
|
1356
|
+
onChange: (checked) => {
|
|
1357
|
+
if (checked) {
|
|
1358
|
+
setSelectedKeys(selectAll(pageRowKeys));
|
|
1359
|
+
} else {
|
|
1360
|
+
setSelectedKeys(clearSelection());
|
|
965
1361
|
}
|
|
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
1362
|
}
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1363
|
+
}
|
|
1364
|
+
),
|
|
1365
|
+
columns.map((column) => {
|
|
1366
|
+
const isSortable = column.kind !== "actions" && (column.kind === "custom" ? false : column.sortable === true);
|
|
1367
|
+
const field = column.kind === "actions" ? "" : column.kind === "custom" ? column.field : column.field;
|
|
1368
|
+
const isResizable = enableColumnResizing && column.kind !== "actions";
|
|
1369
|
+
const actualWidth = columnWidths[column.id] ?? column.width;
|
|
1370
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1371
|
+
"th",
|
|
1027
1372
|
{
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1373
|
+
"data-col-id": column.id,
|
|
1374
|
+
onClick: isSortable ? (e) => {
|
|
1375
|
+
if (isResizingRef.current) return;
|
|
1376
|
+
if (Date.now() - lastResizeEndTsRef.current < 150) return;
|
|
1377
|
+
const isMultiSort = e.ctrlKey || e.metaKey;
|
|
1378
|
+
handleSort(String(field), isMultiSort);
|
|
1379
|
+
} : void 0,
|
|
1380
|
+
role: isSortable ? "button" : void 0,
|
|
1381
|
+
tabIndex: isSortable ? 0 : void 0,
|
|
1382
|
+
onKeyDown: isSortable ? (e) => {
|
|
1383
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
1384
|
+
e.preventDefault();
|
|
1385
|
+
const isMultiSort = e.shiftKey;
|
|
1386
|
+
handleSort(String(field), isMultiSort);
|
|
1036
1387
|
}
|
|
1388
|
+
} : void 0,
|
|
1389
|
+
"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,
|
|
1390
|
+
style: {
|
|
1391
|
+
width: actualWidth != null ? `${actualWidth}px` : void 0,
|
|
1392
|
+
textAlign: column.align,
|
|
1393
|
+
position: isResizable ? "relative" : void 0
|
|
1037
1394
|
},
|
|
1395
|
+
className: [
|
|
1396
|
+
column.truncate ? "rowakit-cell-truncate" : "",
|
|
1397
|
+
resizingColIdRef.current === column.id ? "resizing" : ""
|
|
1398
|
+
// PRD-01
|
|
1399
|
+
].filter(Boolean).join(" ") || void 0,
|
|
1038
1400
|
children: [
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1401
|
+
getHeaderLabel(column),
|
|
1402
|
+
isSortable && getSortIndicator(String(field)),
|
|
1403
|
+
isResizable && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1404
|
+
"div",
|
|
1405
|
+
{
|
|
1406
|
+
className: "rowakit-column-resize-handle",
|
|
1407
|
+
onPointerDown: (e) => startColumnResize(e, column.id),
|
|
1408
|
+
onDoubleClick: (e) => handleColumnResizeDoubleClick(e, column.id),
|
|
1409
|
+
title: "Drag to resize | Double-click to auto-fit content"
|
|
1410
|
+
}
|
|
1411
|
+
)
|
|
1042
1412
|
]
|
|
1043
|
-
}
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1413
|
+
},
|
|
1414
|
+
column.id
|
|
1415
|
+
);
|
|
1416
|
+
})
|
|
1417
|
+
] }),
|
|
1418
|
+
enableFilters && /* @__PURE__ */ jsxRuntime.jsxs("tr", { className: "rowakit-table-filter-row", children: [
|
|
1419
|
+
enableRowSelection && /* @__PURE__ */ jsxRuntime.jsx("th", {}),
|
|
1420
|
+
columns.map((column) => {
|
|
1421
|
+
const field = column.kind === "actions" || column.kind === "custom" ? "" : String(column.field);
|
|
1422
|
+
const canFilter = field && column.kind !== "actions";
|
|
1423
|
+
if (!canFilter) {
|
|
1424
|
+
return /* @__PURE__ */ jsxRuntime.jsx("th", {}, column.id);
|
|
1425
|
+
}
|
|
1426
|
+
const filterValue = filters[field];
|
|
1427
|
+
if (column.kind === "badge") {
|
|
1428
|
+
const options = column.map ? Object.keys(column.map) : [];
|
|
1429
|
+
return /* @__PURE__ */ jsxRuntime.jsx("th", { children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1430
|
+
"select",
|
|
1052
1431
|
{
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
placeholder: "From",
|
|
1056
|
-
value: fromValue,
|
|
1432
|
+
className: "rowakit-filter-select",
|
|
1433
|
+
value: filterValue?.op === "equals" ? String(filterValue.value ?? "") : "",
|
|
1057
1434
|
onChange: (e) => {
|
|
1058
|
-
const
|
|
1059
|
-
|
|
1060
|
-
if (!from && !to) {
|
|
1435
|
+
const value = e.target.value;
|
|
1436
|
+
if (value === "") {
|
|
1061
1437
|
handleClearFilter(field);
|
|
1062
1438
|
} else {
|
|
1063
|
-
handleFilterChange(field, { op: "
|
|
1439
|
+
handleFilterChange(field, { op: "equals", value });
|
|
1064
1440
|
}
|
|
1065
|
-
}
|
|
1441
|
+
},
|
|
1442
|
+
children: [
|
|
1443
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "All" }),
|
|
1444
|
+
options.map((opt) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: opt, children: opt }, opt))
|
|
1445
|
+
]
|
|
1066
1446
|
}
|
|
1067
|
-
),
|
|
1068
|
-
|
|
1069
|
-
|
|
1447
|
+
) }, column.id);
|
|
1448
|
+
}
|
|
1449
|
+
if (column.kind === "boolean") {
|
|
1450
|
+
return /* @__PURE__ */ jsxRuntime.jsx("th", { children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1451
|
+
"select",
|
|
1070
1452
|
{
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
placeholder: "To",
|
|
1074
|
-
value: toValue,
|
|
1453
|
+
className: "rowakit-filter-select",
|
|
1454
|
+
value: filterValue?.op === "equals" && typeof filterValue.value === "boolean" ? String(filterValue.value) : "",
|
|
1075
1455
|
onChange: (e) => {
|
|
1076
|
-
const
|
|
1077
|
-
|
|
1078
|
-
if (!from && !to) {
|
|
1456
|
+
const value = e.target.value;
|
|
1457
|
+
if (value === "") {
|
|
1079
1458
|
handleClearFilter(field);
|
|
1080
1459
|
} else {
|
|
1081
|
-
handleFilterChange(field, { op: "
|
|
1460
|
+
handleFilterChange(field, { op: "equals", value: value === "true" });
|
|
1082
1461
|
}
|
|
1083
|
-
}
|
|
1462
|
+
},
|
|
1463
|
+
children: [
|
|
1464
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "All" }),
|
|
1465
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "true", children: "True" }),
|
|
1466
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "false", children: "False" })
|
|
1467
|
+
]
|
|
1084
1468
|
}
|
|
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: [
|
|
1469
|
+
) }, column.id);
|
|
1470
|
+
}
|
|
1471
|
+
if (column.kind === "date") {
|
|
1472
|
+
const fromValue = filterValue?.op === "range" ? filterValue.value.from ?? "" : "";
|
|
1473
|
+
const toValue = filterValue?.op === "range" ? filterValue.value.to ?? "" : "";
|
|
1474
|
+
return /* @__PURE__ */ jsxRuntime.jsx("th", { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rowakit-filter-date-range", children: [
|
|
1095
1475
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1096
1476
|
"input",
|
|
1097
1477
|
{
|
|
1098
|
-
type: "
|
|
1478
|
+
type: "date",
|
|
1099
1479
|
className: "rowakit-filter-input",
|
|
1100
|
-
placeholder: "
|
|
1480
|
+
placeholder: "From",
|
|
1101
1481
|
value: fromValue,
|
|
1102
1482
|
onChange: (e) => {
|
|
1103
|
-
const from = e.target.value
|
|
1104
|
-
const to = toValue
|
|
1105
|
-
if (from
|
|
1483
|
+
const from = e.target.value || void 0;
|
|
1484
|
+
const to = toValue || void 0;
|
|
1485
|
+
if (!from && !to) {
|
|
1106
1486
|
handleClearFilter(field);
|
|
1107
1487
|
} else {
|
|
1108
1488
|
handleFilterChange(field, { op: "range", value: { from, to } });
|
|
@@ -1113,14 +1493,14 @@ function RowaKitTable({
|
|
|
1113
1493
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1114
1494
|
"input",
|
|
1115
1495
|
{
|
|
1116
|
-
type: "
|
|
1496
|
+
type: "date",
|
|
1117
1497
|
className: "rowakit-filter-input",
|
|
1118
|
-
placeholder: "
|
|
1498
|
+
placeholder: "To",
|
|
1119
1499
|
value: toValue,
|
|
1120
1500
|
onChange: (e) => {
|
|
1121
|
-
const to = e.target.value
|
|
1122
|
-
const from = fromValue
|
|
1123
|
-
if (from
|
|
1501
|
+
const to = e.target.value || void 0;
|
|
1502
|
+
const from = fromValue || void 0;
|
|
1503
|
+
if (!from && !to) {
|
|
1124
1504
|
handleClearFilter(field);
|
|
1125
1505
|
} else {
|
|
1126
1506
|
handleFilterChange(field, { op: "range", value: { from, to } });
|
|
@@ -1130,39 +1510,85 @@ function RowaKitTable({
|
|
|
1130
1510
|
)
|
|
1131
1511
|
] }) }, column.id);
|
|
1132
1512
|
}
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1513
|
+
const isNumberColumn = column.kind === "number";
|
|
1514
|
+
if (isNumberColumn) {
|
|
1515
|
+
const fromValue = filterValue?.op === "range" ? String(filterValue.value.from ?? "") : filterValue?.op === "equals" && typeof filterValue.value === "number" ? String(filterValue.value) : "";
|
|
1516
|
+
const toValue = filterValue?.op === "range" ? String(filterValue.value.to ?? "") : "";
|
|
1517
|
+
const showRangeUI = !filterValue || filterValue.op === "range";
|
|
1518
|
+
if (showRangeUI) {
|
|
1519
|
+
return /* @__PURE__ */ jsxRuntime.jsx("th", { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rowakit-filter-number-range", children: [
|
|
1520
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1521
|
+
"input",
|
|
1522
|
+
{
|
|
1523
|
+
type: "number",
|
|
1524
|
+
className: "rowakit-filter-input",
|
|
1525
|
+
placeholder: "Min",
|
|
1526
|
+
value: fromValue,
|
|
1527
|
+
onChange: (e) => {
|
|
1528
|
+
const from = e.target.value ? Number(e.target.value) : void 0;
|
|
1529
|
+
const to = toValue ? Number(toValue) : void 0;
|
|
1530
|
+
if (from === void 0 && to === void 0) {
|
|
1531
|
+
handleClearFilter(field);
|
|
1532
|
+
} else {
|
|
1533
|
+
handleFilterChange(field, { op: "range", value: { from, to } });
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
),
|
|
1538
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1539
|
+
"input",
|
|
1540
|
+
{
|
|
1541
|
+
type: "number",
|
|
1542
|
+
className: "rowakit-filter-input",
|
|
1543
|
+
placeholder: "Max",
|
|
1544
|
+
value: toValue,
|
|
1545
|
+
onChange: (e) => {
|
|
1546
|
+
const to = e.target.value ? Number(e.target.value) : void 0;
|
|
1547
|
+
const from = fromValue ? Number(fromValue) : void 0;
|
|
1548
|
+
if (from === void 0 && to === void 0) {
|
|
1549
|
+
handleClearFilter(field);
|
|
1550
|
+
} else {
|
|
1551
|
+
handleFilterChange(field, { op: "range", value: { from, to } });
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
)
|
|
1556
|
+
] }) }, column.id);
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
return /* @__PURE__ */ jsxRuntime.jsx("th", { children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1560
|
+
"input",
|
|
1561
|
+
{
|
|
1562
|
+
type: isNumberColumn ? "number" : "text",
|
|
1563
|
+
className: "rowakit-filter-input",
|
|
1564
|
+
placeholder: `Filter ${getHeaderLabel(column)}...`,
|
|
1565
|
+
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) : "",
|
|
1566
|
+
onChange: (e) => {
|
|
1567
|
+
const rawValue = e.target.value;
|
|
1568
|
+
if (rawValue === "") {
|
|
1150
1569
|
handleClearFilter(field);
|
|
1570
|
+
} else if (isNumberColumn) {
|
|
1571
|
+
const numValue = Number(rawValue);
|
|
1572
|
+
if (!isNaN(numValue)) {
|
|
1573
|
+
handleFilterChange(field, { op: "equals", value: numValue });
|
|
1574
|
+
} else {
|
|
1575
|
+
handleClearFilter(field);
|
|
1576
|
+
}
|
|
1577
|
+
} else {
|
|
1578
|
+
handleFilterChange(field, { op: "contains", value: rawValue });
|
|
1151
1579
|
}
|
|
1152
|
-
} else {
|
|
1153
|
-
handleFilterChange(field, { op: "contains", value: rawValue });
|
|
1154
1580
|
}
|
|
1155
1581
|
}
|
|
1156
|
-
}
|
|
1157
|
-
|
|
1158
|
-
|
|
1582
|
+
) }, column.id);
|
|
1583
|
+
})
|
|
1584
|
+
] })
|
|
1159
1585
|
] }),
|
|
1160
1586
|
/* @__PURE__ */ jsxRuntime.jsxs("tbody", { children: [
|
|
1161
|
-
isLoading && /* @__PURE__ */ jsxRuntime.jsx("tr", { children: /* @__PURE__ */ jsxRuntime.jsxs("td", { colSpan:
|
|
1587
|
+
isLoading && /* @__PURE__ */ jsxRuntime.jsx("tr", { children: /* @__PURE__ */ jsxRuntime.jsxs("td", { colSpan: tableColumnCount, className: "rowakit-table-loading", children: [
|
|
1162
1588
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "rowakit-table-loading-spinner" }),
|
|
1163
1589
|
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Loading..." })
|
|
1164
1590
|
] }) }),
|
|
1165
|
-
isError && /* @__PURE__ */ jsxRuntime.jsx("tr", { children: /* @__PURE__ */ jsxRuntime.jsxs("td", { colSpan:
|
|
1591
|
+
isError && /* @__PURE__ */ jsxRuntime.jsx("tr", { children: /* @__PURE__ */ jsxRuntime.jsxs("td", { colSpan: tableColumnCount, className: "rowakit-table-error", children: [
|
|
1166
1592
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "rowakit-table-error-message", children: dataState.error ?? "An error occurred" }),
|
|
1167
1593
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1168
1594
|
"button",
|
|
@@ -1174,29 +1600,42 @@ function RowaKitTable({
|
|
|
1174
1600
|
}
|
|
1175
1601
|
)
|
|
1176
1602
|
] }) }),
|
|
1177
|
-
isEmpty && /* @__PURE__ */ jsxRuntime.jsx("tr", { children: /* @__PURE__ */ jsxRuntime.jsx("td", { colSpan:
|
|
1603
|
+
isEmpty && /* @__PURE__ */ jsxRuntime.jsx("tr", { children: /* @__PURE__ */ jsxRuntime.jsx("td", { colSpan: tableColumnCount, className: "rowakit-table-empty", children: "No data" }) }),
|
|
1178
1604
|
dataState.state === "success" && dataState.items.map((row) => {
|
|
1179
1605
|
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",
|
|
1606
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
|
|
1607
|
+
enableRowSelection && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1608
|
+
RowSelectionCell,
|
|
1188
1609
|
{
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1610
|
+
rowKey: key,
|
|
1611
|
+
disabled: isLoading,
|
|
1612
|
+
checked: selectedKeys.includes(key),
|
|
1613
|
+
onChange: () => {
|
|
1614
|
+
setSelectedKeys((prev) => toggleSelectionKey(prev, key));
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
),
|
|
1618
|
+
columns.map((column) => {
|
|
1619
|
+
const cellClass = [
|
|
1620
|
+
column.kind === "number" ? "rowakit-cell-number" : "",
|
|
1621
|
+
column.truncate ? "rowakit-cell-truncate" : ""
|
|
1622
|
+
].filter(Boolean).join(" ") || void 0;
|
|
1623
|
+
const actualWidth = columnWidths[column.id] ?? column.width;
|
|
1624
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1625
|
+
"td",
|
|
1626
|
+
{
|
|
1627
|
+
"data-col-id": column.id,
|
|
1628
|
+
className: cellClass,
|
|
1629
|
+
style: {
|
|
1630
|
+
width: actualWidth != null ? `${actualWidth}px` : void 0,
|
|
1631
|
+
textAlign: column.align || (column.kind === "number" ? "right" : void 0)
|
|
1632
|
+
},
|
|
1633
|
+
children: renderCell(column, row, isLoading, setConfirmState)
|
|
1194
1634
|
},
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
}) }, key);
|
|
1635
|
+
column.id
|
|
1636
|
+
);
|
|
1637
|
+
})
|
|
1638
|
+
] }, key);
|
|
1200
1639
|
})
|
|
1201
1640
|
] })
|
|
1202
1641
|
] }),
|
|
@@ -1251,6 +1690,7 @@ function RowaKitTable({
|
|
|
1251
1690
|
confirmState && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1252
1691
|
"div",
|
|
1253
1692
|
{
|
|
1693
|
+
ref: confirmModalRef,
|
|
1254
1694
|
className: "rowakit-modal-backdrop",
|
|
1255
1695
|
onClick: () => setConfirmState(null),
|
|
1256
1696
|
role: "dialog",
|
|
@@ -1288,13 +1728,51 @@ function RowaKitTable({
|
|
|
1288
1728
|
] })
|
|
1289
1729
|
] })
|
|
1290
1730
|
}
|
|
1731
|
+
),
|
|
1732
|
+
bulkConfirmState && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1733
|
+
"div",
|
|
1734
|
+
{
|
|
1735
|
+
ref: bulkConfirmModalRef,
|
|
1736
|
+
className: "rowakit-modal-backdrop",
|
|
1737
|
+
onClick: () => setBulkConfirmState(null),
|
|
1738
|
+
role: "dialog",
|
|
1739
|
+
"aria-modal": "true",
|
|
1740
|
+
"aria-labelledby": "bulk-confirm-dialog-title",
|
|
1741
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rowakit-modal", onClick: (e) => e.stopPropagation(), children: [
|
|
1742
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { id: "bulk-confirm-dialog-title", className: "rowakit-modal-title", children: bulkConfirmState.action.confirm?.title ?? "Confirm Action" }),
|
|
1743
|
+
/* @__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." }),
|
|
1744
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rowakit-modal-actions", children: [
|
|
1745
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1746
|
+
"button",
|
|
1747
|
+
{
|
|
1748
|
+
onClick: () => setBulkConfirmState(null),
|
|
1749
|
+
className: "rowakit-button rowakit-button-secondary",
|
|
1750
|
+
type: "button",
|
|
1751
|
+
children: "Cancel"
|
|
1752
|
+
}
|
|
1753
|
+
),
|
|
1754
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1755
|
+
"button",
|
|
1756
|
+
{
|
|
1757
|
+
onClick: () => {
|
|
1758
|
+
bulkConfirmState.action.onClick(bulkConfirmState.selectedKeys);
|
|
1759
|
+
setBulkConfirmState(null);
|
|
1760
|
+
},
|
|
1761
|
+
className: "rowakit-button rowakit-button-danger",
|
|
1762
|
+
type: "button",
|
|
1763
|
+
children: "Confirm"
|
|
1764
|
+
}
|
|
1765
|
+
)
|
|
1766
|
+
] })
|
|
1767
|
+
] })
|
|
1768
|
+
}
|
|
1291
1769
|
)
|
|
1292
1770
|
] });
|
|
1293
1771
|
}
|
|
1294
1772
|
var SmartTable = RowaKitTable;
|
|
1295
1773
|
|
|
1296
1774
|
// src/index.ts
|
|
1297
|
-
var VERSION = "0.
|
|
1775
|
+
var VERSION = "0.5.0" ;
|
|
1298
1776
|
|
|
1299
1777
|
exports.RowaKitTable = RowaKitTable;
|
|
1300
1778
|
exports.SmartTable = SmartTable;
|