@rowakit/table 0.2.2 → 0.4.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 +187 -15
- package/dist/index.cjs +675 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +22 -5
- package/dist/index.d.ts +22 -5
- package/dist/index.js +676 -13
- package/dist/index.js.map +1 -1
- package/package.json +5 -1
- package/src/styles/table.css +154 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useState, useRef, useEffect } from 'react';
|
|
2
|
-
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
3
3
|
|
|
4
4
|
// src/column-helpers.ts
|
|
5
5
|
function text(field, options) {
|
|
@@ -118,6 +118,158 @@ function getRowKey(row, rowKey) {
|
|
|
118
118
|
function getHeaderLabel(column) {
|
|
119
119
|
return column.header ?? column.id;
|
|
120
120
|
}
|
|
121
|
+
function validateViewName(name) {
|
|
122
|
+
const trimmed = name.trim();
|
|
123
|
+
if (trimmed.length === 0) {
|
|
124
|
+
return { valid: false, error: "Name cannot be empty" };
|
|
125
|
+
}
|
|
126
|
+
if (trimmed.length > 40) {
|
|
127
|
+
return { valid: false, error: "Name cannot exceed 40 characters" };
|
|
128
|
+
}
|
|
129
|
+
const invalidChars = /[/\\?%*:|"<>\x00-\x1f\x7f]/;
|
|
130
|
+
if (invalidChars.test(trimmed)) {
|
|
131
|
+
return { valid: false, error: "Name contains invalid characters" };
|
|
132
|
+
}
|
|
133
|
+
return { valid: true };
|
|
134
|
+
}
|
|
135
|
+
function getSavedViewsIndex() {
|
|
136
|
+
if (typeof window === "undefined" || !window.localStorage) {
|
|
137
|
+
return [];
|
|
138
|
+
}
|
|
139
|
+
try {
|
|
140
|
+
const indexStr = localStorage.getItem("rowakit-views-index");
|
|
141
|
+
if (indexStr) {
|
|
142
|
+
const index = JSON.parse(indexStr);
|
|
143
|
+
if (Array.isArray(index)) {
|
|
144
|
+
return index;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
} catch {
|
|
148
|
+
}
|
|
149
|
+
const rebuilt = [];
|
|
150
|
+
try {
|
|
151
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
152
|
+
const key = localStorage.key(i);
|
|
153
|
+
if (key?.startsWith("rowakit-view-")) {
|
|
154
|
+
const name = key.substring("rowakit-view-".length);
|
|
155
|
+
rebuilt.push({
|
|
156
|
+
name,
|
|
157
|
+
updatedAt: Date.now()
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
} catch {
|
|
162
|
+
}
|
|
163
|
+
return rebuilt;
|
|
164
|
+
}
|
|
165
|
+
function setSavedViewsIndex(index) {
|
|
166
|
+
if (typeof window === "undefined" || !window.localStorage) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
try {
|
|
170
|
+
localStorage.setItem("rowakit-views-index", JSON.stringify(index));
|
|
171
|
+
} catch {
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function loadSavedViewsFromStorage() {
|
|
175
|
+
if (typeof window === "undefined" || !window.localStorage) {
|
|
176
|
+
return [];
|
|
177
|
+
}
|
|
178
|
+
const index = getSavedViewsIndex();
|
|
179
|
+
const views = [];
|
|
180
|
+
for (const entry of index) {
|
|
181
|
+
try {
|
|
182
|
+
const viewStr = localStorage.getItem(`rowakit-view-${entry.name}`);
|
|
183
|
+
if (viewStr) {
|
|
184
|
+
const state = JSON.parse(viewStr);
|
|
185
|
+
views.push({
|
|
186
|
+
name: entry.name,
|
|
187
|
+
state
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
} catch {
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return views;
|
|
194
|
+
}
|
|
195
|
+
function parseUrlState(params, defaultPageSize, pageSizeOptions) {
|
|
196
|
+
const pageStr = params.get("page");
|
|
197
|
+
let page = 1;
|
|
198
|
+
if (pageStr) {
|
|
199
|
+
const parsed = parseInt(pageStr, 10);
|
|
200
|
+
page = !isNaN(parsed) && parsed >= 1 ? parsed : 1;
|
|
201
|
+
}
|
|
202
|
+
const pageSizeStr = params.get("pageSize");
|
|
203
|
+
let pageSize = defaultPageSize;
|
|
204
|
+
if (pageSizeStr) {
|
|
205
|
+
const parsed = parseInt(pageSizeStr, 10);
|
|
206
|
+
if (!isNaN(parsed) && parsed >= 1) {
|
|
207
|
+
if (pageSizeOptions && pageSizeOptions.length > 0) {
|
|
208
|
+
pageSize = pageSizeOptions.includes(parsed) ? parsed : defaultPageSize;
|
|
209
|
+
} else {
|
|
210
|
+
pageSize = parsed;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
const result = { page, pageSize };
|
|
215
|
+
const sortField = params.get("sortField");
|
|
216
|
+
const sortDir = params.get("sortDirection");
|
|
217
|
+
if (sortField && (sortDir === "asc" || sortDir === "desc")) {
|
|
218
|
+
result.sort = { field: sortField, direction: sortDir };
|
|
219
|
+
}
|
|
220
|
+
const filtersStr = params.get("filters");
|
|
221
|
+
if (filtersStr) {
|
|
222
|
+
try {
|
|
223
|
+
const parsed = JSON.parse(filtersStr);
|
|
224
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
225
|
+
result.filters = parsed;
|
|
226
|
+
}
|
|
227
|
+
} catch {
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
const widthsStr = params.get("columnWidths");
|
|
231
|
+
if (widthsStr) {
|
|
232
|
+
try {
|
|
233
|
+
const parsed = JSON.parse(widthsStr);
|
|
234
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
235
|
+
const widths = {};
|
|
236
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
237
|
+
if (typeof value === "number" && value > 0) {
|
|
238
|
+
widths[key] = value;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
if (Object.keys(widths).length > 0) {
|
|
242
|
+
result.columnWidths = widths;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
} catch {
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return result;
|
|
249
|
+
}
|
|
250
|
+
function serializeUrlState(query, filters, columnWidths, defaultPageSize, enableColumnResizing) {
|
|
251
|
+
const params = new URLSearchParams();
|
|
252
|
+
params.set("page", String(query.page));
|
|
253
|
+
if (query.pageSize !== defaultPageSize) {
|
|
254
|
+
params.set("pageSize", String(query.pageSize));
|
|
255
|
+
}
|
|
256
|
+
if (query.sort) {
|
|
257
|
+
params.set("sortField", query.sort.field);
|
|
258
|
+
params.set("sortDirection", query.sort.direction);
|
|
259
|
+
}
|
|
260
|
+
if (filters && Object.keys(filters).length > 0) {
|
|
261
|
+
const nonEmptyFilters = Object.fromEntries(
|
|
262
|
+
Object.entries(filters).filter(([, v]) => v !== void 0)
|
|
263
|
+
);
|
|
264
|
+
if (Object.keys(nonEmptyFilters).length > 0) {
|
|
265
|
+
params.set("filters", JSON.stringify(nonEmptyFilters));
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
if (enableColumnResizing && Object.keys(columnWidths).length > 0) {
|
|
269
|
+
params.set("columnWidths", JSON.stringify(columnWidths));
|
|
270
|
+
}
|
|
271
|
+
return params.toString();
|
|
272
|
+
}
|
|
121
273
|
function renderCell(column, row, isLoading, setConfirmState) {
|
|
122
274
|
switch (column.kind) {
|
|
123
275
|
case "text": {
|
|
@@ -167,7 +319,11 @@ function renderCell(column, row, isLoading, setConfirmState) {
|
|
|
167
319
|
return numValue.toLocaleString();
|
|
168
320
|
}
|
|
169
321
|
case "actions": {
|
|
170
|
-
|
|
322
|
+
const columnWithActions = column;
|
|
323
|
+
if (!Array.isArray(columnWithActions.actions)) {
|
|
324
|
+
return null;
|
|
325
|
+
}
|
|
326
|
+
return /* @__PURE__ */ jsx("div", { className: "rowakit-table-actions", children: columnWithActions.actions.map((action) => {
|
|
171
327
|
const isDisabled = isLoading || action.disabled === true || typeof action.disabled === "function" && action.disabled(row);
|
|
172
328
|
const handleClick = () => {
|
|
173
329
|
if (isDisabled || action.loading) {
|
|
@@ -211,7 +367,10 @@ function RowaKitTable({
|
|
|
211
367
|
pageSizeOptions = [10, 20, 50],
|
|
212
368
|
rowKey,
|
|
213
369
|
className = "",
|
|
214
|
-
enableFilters = false
|
|
370
|
+
enableFilters = false,
|
|
371
|
+
enableColumnResizing = false,
|
|
372
|
+
syncToUrl = false,
|
|
373
|
+
enableSavedViews = false
|
|
215
374
|
}) {
|
|
216
375
|
const [dataState, setDataState] = useState({
|
|
217
376
|
state: "idle",
|
|
@@ -223,8 +382,100 @@ function RowaKitTable({
|
|
|
223
382
|
pageSize: defaultPageSize
|
|
224
383
|
});
|
|
225
384
|
const [filters, setFilters] = useState({});
|
|
385
|
+
const [columnWidths, setColumnWidths] = useState({});
|
|
386
|
+
const resizeRafRef = useRef(null);
|
|
387
|
+
const resizePendingRef = useRef(null);
|
|
388
|
+
const tableRef = useRef(null);
|
|
389
|
+
const isResizingRef = useRef(false);
|
|
390
|
+
const lastResizeEndTsRef = useRef(0);
|
|
391
|
+
const resizingColIdRef = useRef(null);
|
|
392
|
+
const didHydrateUrlRef = useRef(false);
|
|
393
|
+
const didSkipInitialUrlSyncRef = useRef(false);
|
|
394
|
+
const urlSyncDebounceRef = useRef(null);
|
|
395
|
+
const [savedViews, setSavedViews] = useState([]);
|
|
396
|
+
const [showSaveViewForm, setShowSaveViewForm] = useState(false);
|
|
397
|
+
const [saveViewInput, setSaveViewInput] = useState("");
|
|
398
|
+
const [saveViewError, setSaveViewError] = useState("");
|
|
399
|
+
const [overwriteConfirmName, setOverwriteConfirmName] = useState(null);
|
|
400
|
+
useEffect(() => {
|
|
401
|
+
if (!enableSavedViews) return;
|
|
402
|
+
const views = loadSavedViewsFromStorage();
|
|
403
|
+
setSavedViews(views);
|
|
404
|
+
}, [enableSavedViews]);
|
|
226
405
|
const [confirmState, setConfirmState] = useState(null);
|
|
227
406
|
const requestIdRef = useRef(0);
|
|
407
|
+
useEffect(() => {
|
|
408
|
+
if (!syncToUrl) {
|
|
409
|
+
didSkipInitialUrlSyncRef.current = false;
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
if (!didSkipInitialUrlSyncRef.current) {
|
|
413
|
+
didSkipInitialUrlSyncRef.current = true;
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
if (urlSyncDebounceRef.current) {
|
|
417
|
+
clearTimeout(urlSyncDebounceRef.current);
|
|
418
|
+
urlSyncDebounceRef.current = null;
|
|
419
|
+
}
|
|
420
|
+
const urlStr = serializeUrlState(query, filters, columnWidths, defaultPageSize, enableColumnResizing);
|
|
421
|
+
const qs = urlStr ? `?${urlStr}` : "";
|
|
422
|
+
window.history.replaceState(null, "", `${window.location.pathname}${qs}${window.location.hash}`);
|
|
423
|
+
}, [query, filters, syncToUrl, enableColumnResizing, defaultPageSize, columnWidths]);
|
|
424
|
+
useEffect(() => {
|
|
425
|
+
if (!syncToUrl || !enableColumnResizing) return;
|
|
426
|
+
if (!didSkipInitialUrlSyncRef.current) return;
|
|
427
|
+
if (urlSyncDebounceRef.current) {
|
|
428
|
+
clearTimeout(urlSyncDebounceRef.current);
|
|
429
|
+
}
|
|
430
|
+
urlSyncDebounceRef.current = setTimeout(() => {
|
|
431
|
+
const urlStr = serializeUrlState(query, filters, columnWidths, defaultPageSize, enableColumnResizing);
|
|
432
|
+
const qs = urlStr ? `?${urlStr}` : "";
|
|
433
|
+
window.history.replaceState(null, "", `${window.location.pathname}${qs}${window.location.hash}`);
|
|
434
|
+
urlSyncDebounceRef.current = null;
|
|
435
|
+
}, 150);
|
|
436
|
+
return () => {
|
|
437
|
+
if (urlSyncDebounceRef.current) {
|
|
438
|
+
clearTimeout(urlSyncDebounceRef.current);
|
|
439
|
+
urlSyncDebounceRef.current = null;
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
}, [columnWidths, syncToUrl, enableColumnResizing, query, filters, defaultPageSize]);
|
|
443
|
+
useEffect(() => {
|
|
444
|
+
if (!syncToUrl) {
|
|
445
|
+
didHydrateUrlRef.current = false;
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
if (didHydrateUrlRef.current) return;
|
|
449
|
+
didHydrateUrlRef.current = true;
|
|
450
|
+
const params = new URLSearchParams(window.location.search);
|
|
451
|
+
const parsed = parseUrlState(params, defaultPageSize, pageSizeOptions);
|
|
452
|
+
setQuery({
|
|
453
|
+
page: parsed.page,
|
|
454
|
+
pageSize: parsed.pageSize,
|
|
455
|
+
sort: parsed.sort,
|
|
456
|
+
filters: parsed.filters
|
|
457
|
+
});
|
|
458
|
+
if (parsed.filters) {
|
|
459
|
+
setFilters(parsed.filters);
|
|
460
|
+
}
|
|
461
|
+
if (parsed.columnWidths && enableColumnResizing) {
|
|
462
|
+
const clamped = {};
|
|
463
|
+
for (const [colId, rawWidth] of Object.entries(parsed.columnWidths)) {
|
|
464
|
+
const widthNum = typeof rawWidth === "number" ? rawWidth : Number(rawWidth);
|
|
465
|
+
if (!Number.isFinite(widthNum)) continue;
|
|
466
|
+
const colDef = columns.find((c) => c.id === colId);
|
|
467
|
+
if (!colDef) continue;
|
|
468
|
+
const minW = colDef.minWidth ?? 80;
|
|
469
|
+
const maxW = colDef.maxWidth;
|
|
470
|
+
let finalW = Math.max(minW, widthNum);
|
|
471
|
+
if (maxW != null) {
|
|
472
|
+
finalW = Math.min(finalW, maxW);
|
|
473
|
+
}
|
|
474
|
+
clamped[colId] = finalW;
|
|
475
|
+
}
|
|
476
|
+
setColumnWidths(clamped);
|
|
477
|
+
}
|
|
478
|
+
}, [syncToUrl, defaultPageSize, enableColumnResizing, pageSizeOptions, columns]);
|
|
228
479
|
useEffect(() => {
|
|
229
480
|
if (!enableFilters) return;
|
|
230
481
|
const activeFilters = {};
|
|
@@ -310,10 +561,203 @@ function RowaKitTable({
|
|
|
310
561
|
}
|
|
311
562
|
return query.sort.direction === "asc" ? " \u2191" : " \u2193";
|
|
312
563
|
};
|
|
564
|
+
const scheduleColumnWidthUpdate = (colId, width) => {
|
|
565
|
+
resizePendingRef.current = { colId, width };
|
|
566
|
+
if (resizeRafRef.current != null) return;
|
|
567
|
+
resizeRafRef.current = requestAnimationFrame(() => {
|
|
568
|
+
resizeRafRef.current = null;
|
|
569
|
+
const pending = resizePendingRef.current;
|
|
570
|
+
if (!pending) return;
|
|
571
|
+
handleColumnResize(pending.colId, pending.width);
|
|
572
|
+
});
|
|
573
|
+
};
|
|
574
|
+
const handleColumnResize = (columnId, newWidth) => {
|
|
575
|
+
const minWidth = columns.find((c) => c.id === columnId)?.minWidth ?? 80;
|
|
576
|
+
const maxWidth = columns.find((c) => c.id === columnId)?.maxWidth;
|
|
577
|
+
let finalWidth = Math.max(minWidth, newWidth);
|
|
578
|
+
if (maxWidth) {
|
|
579
|
+
finalWidth = Math.min(finalWidth, maxWidth);
|
|
580
|
+
}
|
|
581
|
+
if (columnWidths[columnId] === finalWidth) {
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
setColumnWidths((prev) => ({
|
|
585
|
+
...prev,
|
|
586
|
+
[columnId]: finalWidth
|
|
587
|
+
}));
|
|
588
|
+
};
|
|
589
|
+
const autoFitColumnWidth = (columnId) => {
|
|
590
|
+
const tableEl = tableRef.current;
|
|
591
|
+
if (!tableEl) return;
|
|
592
|
+
const th = tableEl.querySelector(`th[data-col-id="${columnId}"]`);
|
|
593
|
+
if (!th) return;
|
|
594
|
+
const tds = Array.from(
|
|
595
|
+
tableEl.querySelectorAll(`td[data-col-id="${columnId}"]`)
|
|
596
|
+
);
|
|
597
|
+
const headerW = th.scrollWidth;
|
|
598
|
+
const cellsMaxW = tds.reduce((max, td) => Math.max(max, td.scrollWidth), 0);
|
|
599
|
+
const padding = 24;
|
|
600
|
+
const raw = Math.max(headerW, cellsMaxW) + padding;
|
|
601
|
+
const colDef = columns.find((c) => c.id === columnId);
|
|
602
|
+
const minW = colDef?.minWidth ?? 80;
|
|
603
|
+
const maxW = colDef?.maxWidth ?? 600;
|
|
604
|
+
const finalW = Math.max(minW, Math.min(raw, maxW));
|
|
605
|
+
setColumnWidths((prev) => ({ ...prev, [columnId]: finalW }));
|
|
606
|
+
};
|
|
607
|
+
const startColumnResize = (e, columnId) => {
|
|
608
|
+
e.preventDefault();
|
|
609
|
+
e.stopPropagation();
|
|
610
|
+
if (e.detail === 2) {
|
|
611
|
+
autoFitColumnWidth(columnId);
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
if (e.pointerType === "mouse" && e.buttons !== 1) {
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
const target = e.currentTarget;
|
|
618
|
+
const pointerId = e.pointerId;
|
|
619
|
+
try {
|
|
620
|
+
target.setPointerCapture(pointerId);
|
|
621
|
+
} catch {
|
|
622
|
+
}
|
|
623
|
+
isResizingRef.current = true;
|
|
624
|
+
resizingColIdRef.current = columnId;
|
|
625
|
+
const startX = e.clientX;
|
|
626
|
+
const th = target.parentElement;
|
|
627
|
+
let startWidth = columnWidths[columnId] ?? th.offsetWidth;
|
|
628
|
+
const MIN_DRAG_WIDTH = 80;
|
|
629
|
+
if (startWidth < MIN_DRAG_WIDTH) {
|
|
630
|
+
const nextTh = th.nextElementSibling;
|
|
631
|
+
if (nextTh && nextTh.offsetWidth >= 50) {
|
|
632
|
+
startWidth = nextTh.offsetWidth;
|
|
633
|
+
} else {
|
|
634
|
+
startWidth = 100;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
document.body.classList.add("rowakit-resizing");
|
|
638
|
+
const handlePointerMove = (moveEvent) => {
|
|
639
|
+
const delta = moveEvent.clientX - startX;
|
|
640
|
+
const newWidth = startWidth + delta;
|
|
641
|
+
scheduleColumnWidthUpdate(columnId, newWidth);
|
|
642
|
+
};
|
|
643
|
+
const cleanupResize = () => {
|
|
644
|
+
target.removeEventListener("pointermove", handlePointerMove);
|
|
645
|
+
target.removeEventListener("pointerup", handlePointerUp);
|
|
646
|
+
target.removeEventListener("pointercancel", handlePointerCancel);
|
|
647
|
+
document.body.classList.remove("rowakit-resizing");
|
|
648
|
+
isResizingRef.current = false;
|
|
649
|
+
resizingColIdRef.current = null;
|
|
650
|
+
lastResizeEndTsRef.current = Date.now();
|
|
651
|
+
try {
|
|
652
|
+
target.releasePointerCapture(pointerId);
|
|
653
|
+
} catch {
|
|
654
|
+
}
|
|
655
|
+
};
|
|
656
|
+
const handlePointerUp = () => {
|
|
657
|
+
cleanupResize();
|
|
658
|
+
};
|
|
659
|
+
const handlePointerCancel = () => {
|
|
660
|
+
cleanupResize();
|
|
661
|
+
};
|
|
662
|
+
target.addEventListener("pointermove", handlePointerMove);
|
|
663
|
+
target.addEventListener("pointerup", handlePointerUp);
|
|
664
|
+
target.addEventListener("pointercancel", handlePointerCancel);
|
|
665
|
+
};
|
|
666
|
+
const handleColumnResizeDoubleClick = (e, columnId) => {
|
|
667
|
+
e.preventDefault();
|
|
668
|
+
e.stopPropagation();
|
|
669
|
+
autoFitColumnWidth(columnId);
|
|
670
|
+
};
|
|
671
|
+
const saveCurrentView = (name) => {
|
|
672
|
+
const viewState = {
|
|
673
|
+
page: query.page,
|
|
674
|
+
pageSize: query.pageSize,
|
|
675
|
+
sort: query.sort,
|
|
676
|
+
filters: query.filters,
|
|
677
|
+
columnWidths: enableColumnResizing ? columnWidths : void 0
|
|
678
|
+
};
|
|
679
|
+
setSavedViews((prev) => {
|
|
680
|
+
const filtered = prev.filter((v) => v.name !== name);
|
|
681
|
+
return [...filtered, { name, state: viewState }];
|
|
682
|
+
});
|
|
683
|
+
if (typeof window !== "undefined" && window.localStorage) {
|
|
684
|
+
try {
|
|
685
|
+
localStorage.setItem(`rowakit-view-${name}`, JSON.stringify(viewState));
|
|
686
|
+
const index = getSavedViewsIndex();
|
|
687
|
+
const filtered = index.filter((v) => v.name !== name);
|
|
688
|
+
filtered.push({ name, updatedAt: Date.now() });
|
|
689
|
+
setSavedViewsIndex(filtered);
|
|
690
|
+
} catch {
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
};
|
|
694
|
+
const loadSavedView = (name) => {
|
|
695
|
+
const view = savedViews.find((v) => v.name === name);
|
|
696
|
+
if (!view) return;
|
|
697
|
+
const { state } = view;
|
|
698
|
+
setQuery({
|
|
699
|
+
page: state.page,
|
|
700
|
+
pageSize: state.pageSize,
|
|
701
|
+
sort: state.sort,
|
|
702
|
+
filters: state.filters
|
|
703
|
+
});
|
|
704
|
+
setFilters(state.filters ?? {});
|
|
705
|
+
if (state.columnWidths && enableColumnResizing) {
|
|
706
|
+
setColumnWidths(state.columnWidths);
|
|
707
|
+
}
|
|
708
|
+
};
|
|
709
|
+
const deleteSavedView = (name) => {
|
|
710
|
+
setSavedViews((prev) => prev.filter((v) => v.name !== name));
|
|
711
|
+
if (typeof window !== "undefined" && window.localStorage) {
|
|
712
|
+
try {
|
|
713
|
+
localStorage.removeItem(`rowakit-view-${name}`);
|
|
714
|
+
const index = getSavedViewsIndex();
|
|
715
|
+
const filtered = index.filter((v) => v.name !== name);
|
|
716
|
+
setSavedViewsIndex(filtered);
|
|
717
|
+
} catch {
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
};
|
|
721
|
+
const resetTableState = () => {
|
|
722
|
+
setQuery({
|
|
723
|
+
page: 1,
|
|
724
|
+
pageSize: defaultPageSize
|
|
725
|
+
});
|
|
726
|
+
setFilters({});
|
|
727
|
+
setColumnWidths({});
|
|
728
|
+
};
|
|
729
|
+
const transformFilterValueForColumn = (column, value) => {
|
|
730
|
+
if (!value || column?.kind !== "number") {
|
|
731
|
+
return value;
|
|
732
|
+
}
|
|
733
|
+
const numberColumn = column;
|
|
734
|
+
if (!numberColumn.filterTransform) {
|
|
735
|
+
return value;
|
|
736
|
+
}
|
|
737
|
+
if (value.op === "equals" && typeof value.value === "number") {
|
|
738
|
+
return {
|
|
739
|
+
...value,
|
|
740
|
+
value: numberColumn.filterTransform(value.value)
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
if (value.op === "range" && typeof value.value === "object") {
|
|
744
|
+
const { from, to } = value.value;
|
|
745
|
+
return {
|
|
746
|
+
op: "range",
|
|
747
|
+
value: {
|
|
748
|
+
from: from !== void 0 && typeof from === "number" ? numberColumn.filterTransform(from) : from,
|
|
749
|
+
to: to !== void 0 && typeof to === "number" ? numberColumn.filterTransform(to) : to
|
|
750
|
+
}
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
return value;
|
|
754
|
+
};
|
|
313
755
|
const handleFilterChange = (field, value) => {
|
|
756
|
+
const column = columns.find((c) => c.id === field);
|
|
757
|
+
const transformedValue = transformFilterValueForColumn(column, value);
|
|
314
758
|
setFilters((prev) => ({
|
|
315
759
|
...prev,
|
|
316
|
-
[field]:
|
|
760
|
+
[field]: transformedValue
|
|
317
761
|
}));
|
|
318
762
|
};
|
|
319
763
|
const handleClearFilter = (field) => {
|
|
@@ -333,7 +777,158 @@ function RowaKitTable({
|
|
|
333
777
|
const canGoPrevious = query.page > 1 && !isLoading;
|
|
334
778
|
const canGoNext = query.page < totalPages && !isLoading;
|
|
335
779
|
const hasActiveFilters = enableFilters && Object.values(filters).some((v) => v !== void 0);
|
|
336
|
-
|
|
780
|
+
const containerClass = [
|
|
781
|
+
"rowakit-table",
|
|
782
|
+
enableColumnResizing ? "rowakit-layout-fixed" : "",
|
|
783
|
+
className
|
|
784
|
+
].filter(Boolean).join(" ");
|
|
785
|
+
return /* @__PURE__ */ jsxs("div", { className: containerClass, children: [
|
|
786
|
+
enableSavedViews && /* @__PURE__ */ jsxs("div", { className: "rowakit-saved-views-group", children: [
|
|
787
|
+
!showSaveViewForm ? /* @__PURE__ */ jsx(
|
|
788
|
+
"button",
|
|
789
|
+
{
|
|
790
|
+
onClick: () => {
|
|
791
|
+
setShowSaveViewForm(true);
|
|
792
|
+
setSaveViewInput("");
|
|
793
|
+
setSaveViewError("");
|
|
794
|
+
setOverwriteConfirmName(null);
|
|
795
|
+
},
|
|
796
|
+
className: "rowakit-saved-view-button",
|
|
797
|
+
type: "button",
|
|
798
|
+
children: "Save View"
|
|
799
|
+
}
|
|
800
|
+
) : /* @__PURE__ */ jsx("div", { className: "rowakit-save-view-form", children: overwriteConfirmName ? /* @__PURE__ */ jsxs("div", { className: "rowakit-save-view-confirm", children: [
|
|
801
|
+
/* @__PURE__ */ jsxs("p", { children: [
|
|
802
|
+
'View "',
|
|
803
|
+
overwriteConfirmName,
|
|
804
|
+
'" already exists. Overwrite?'
|
|
805
|
+
] }),
|
|
806
|
+
/* @__PURE__ */ jsx(
|
|
807
|
+
"button",
|
|
808
|
+
{
|
|
809
|
+
onClick: () => {
|
|
810
|
+
saveCurrentView(overwriteConfirmName);
|
|
811
|
+
setShowSaveViewForm(false);
|
|
812
|
+
setSaveViewInput("");
|
|
813
|
+
setSaveViewError("");
|
|
814
|
+
setOverwriteConfirmName(null);
|
|
815
|
+
},
|
|
816
|
+
className: "rowakit-saved-view-button",
|
|
817
|
+
type: "button",
|
|
818
|
+
children: "Overwrite"
|
|
819
|
+
}
|
|
820
|
+
),
|
|
821
|
+
/* @__PURE__ */ jsx(
|
|
822
|
+
"button",
|
|
823
|
+
{
|
|
824
|
+
onClick: () => {
|
|
825
|
+
setOverwriteConfirmName(null);
|
|
826
|
+
},
|
|
827
|
+
className: "rowakit-saved-view-button",
|
|
828
|
+
type: "button",
|
|
829
|
+
children: "Cancel"
|
|
830
|
+
}
|
|
831
|
+
)
|
|
832
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
833
|
+
/* @__PURE__ */ jsx(
|
|
834
|
+
"input",
|
|
835
|
+
{
|
|
836
|
+
type: "text",
|
|
837
|
+
value: saveViewInput,
|
|
838
|
+
onChange: (e) => {
|
|
839
|
+
setSaveViewInput(e.target.value);
|
|
840
|
+
setSaveViewError("");
|
|
841
|
+
},
|
|
842
|
+
onKeyDown: (e) => {
|
|
843
|
+
if (e.key === "Enter") {
|
|
844
|
+
const validation = validateViewName(saveViewInput);
|
|
845
|
+
if (!validation.valid) {
|
|
846
|
+
setSaveViewError(validation.error || "Invalid name");
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
if (savedViews.some((v) => v.name === saveViewInput.trim())) {
|
|
850
|
+
setOverwriteConfirmName(saveViewInput.trim());
|
|
851
|
+
} else {
|
|
852
|
+
saveCurrentView(saveViewInput.trim());
|
|
853
|
+
setShowSaveViewForm(false);
|
|
854
|
+
setSaveViewInput("");
|
|
855
|
+
setSaveViewError("");
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
},
|
|
859
|
+
placeholder: "Enter view name...",
|
|
860
|
+
className: "rowakit-save-view-input"
|
|
861
|
+
}
|
|
862
|
+
),
|
|
863
|
+
saveViewError && /* @__PURE__ */ jsx("div", { className: "rowakit-save-view-error", children: saveViewError }),
|
|
864
|
+
/* @__PURE__ */ jsx(
|
|
865
|
+
"button",
|
|
866
|
+
{
|
|
867
|
+
onClick: () => {
|
|
868
|
+
const validation = validateViewName(saveViewInput);
|
|
869
|
+
if (!validation.valid) {
|
|
870
|
+
setSaveViewError(validation.error || "Invalid name");
|
|
871
|
+
return;
|
|
872
|
+
}
|
|
873
|
+
if (savedViews.some((v) => v.name === saveViewInput.trim())) {
|
|
874
|
+
setOverwriteConfirmName(saveViewInput.trim());
|
|
875
|
+
} else {
|
|
876
|
+
saveCurrentView(saveViewInput.trim());
|
|
877
|
+
setShowSaveViewForm(false);
|
|
878
|
+
setSaveViewInput("");
|
|
879
|
+
setSaveViewError("");
|
|
880
|
+
}
|
|
881
|
+
},
|
|
882
|
+
className: "rowakit-saved-view-button",
|
|
883
|
+
type: "button",
|
|
884
|
+
children: "Save"
|
|
885
|
+
}
|
|
886
|
+
),
|
|
887
|
+
/* @__PURE__ */ jsx(
|
|
888
|
+
"button",
|
|
889
|
+
{
|
|
890
|
+
onClick: () => {
|
|
891
|
+
setShowSaveViewForm(false);
|
|
892
|
+
setSaveViewInput("");
|
|
893
|
+
setSaveViewError("");
|
|
894
|
+
},
|
|
895
|
+
className: "rowakit-saved-view-button",
|
|
896
|
+
type: "button",
|
|
897
|
+
children: "Cancel"
|
|
898
|
+
}
|
|
899
|
+
)
|
|
900
|
+
] }) }),
|
|
901
|
+
savedViews.map((view) => /* @__PURE__ */ jsxs("div", { className: "rowakit-saved-view-item", children: [
|
|
902
|
+
/* @__PURE__ */ jsx(
|
|
903
|
+
"button",
|
|
904
|
+
{
|
|
905
|
+
onClick: () => loadSavedView(view.name),
|
|
906
|
+
className: "rowakit-saved-view-button",
|
|
907
|
+
type: "button",
|
|
908
|
+
children: view.name
|
|
909
|
+
}
|
|
910
|
+
),
|
|
911
|
+
/* @__PURE__ */ jsx(
|
|
912
|
+
"button",
|
|
913
|
+
{
|
|
914
|
+
onClick: () => deleteSavedView(view.name),
|
|
915
|
+
className: "rowakit-saved-view-button rowakit-saved-view-button-delete",
|
|
916
|
+
type: "button",
|
|
917
|
+
title: "Delete this view",
|
|
918
|
+
children: "\xD7"
|
|
919
|
+
}
|
|
920
|
+
)
|
|
921
|
+
] }, view.name)),
|
|
922
|
+
(hasActiveFilters || query.page > 1 || query.sort) && /* @__PURE__ */ jsx(
|
|
923
|
+
"button",
|
|
924
|
+
{
|
|
925
|
+
onClick: resetTableState,
|
|
926
|
+
className: "rowakit-saved-view-button",
|
|
927
|
+
type: "button",
|
|
928
|
+
children: "Reset"
|
|
929
|
+
}
|
|
930
|
+
)
|
|
931
|
+
] }),
|
|
337
932
|
hasActiveFilters && /* @__PURE__ */ jsx("div", { className: "rowakit-table-filter-controls", children: /* @__PURE__ */ jsx(
|
|
338
933
|
"button",
|
|
339
934
|
{
|
|
@@ -343,15 +938,22 @@ function RowaKitTable({
|
|
|
343
938
|
children: "Clear all filters"
|
|
344
939
|
}
|
|
345
940
|
) }),
|
|
346
|
-
/* @__PURE__ */ jsxs("table", { children: [
|
|
941
|
+
/* @__PURE__ */ jsxs("table", { ref: tableRef, children: [
|
|
347
942
|
/* @__PURE__ */ jsxs("thead", { children: [
|
|
348
943
|
/* @__PURE__ */ jsx("tr", { children: columns.map((column) => {
|
|
349
944
|
const isSortable = column.kind !== "actions" && (column.kind === "custom" ? false : column.sortable === true);
|
|
350
945
|
const field = column.kind === "actions" ? "" : column.kind === "custom" ? column.field : column.field;
|
|
946
|
+
const isResizable = enableColumnResizing && column.kind !== "actions";
|
|
947
|
+
const actualWidth = columnWidths[column.id] ?? column.width;
|
|
351
948
|
return /* @__PURE__ */ jsxs(
|
|
352
949
|
"th",
|
|
353
950
|
{
|
|
354
|
-
|
|
951
|
+
"data-col-id": column.id,
|
|
952
|
+
onClick: isSortable ? () => {
|
|
953
|
+
if (isResizingRef.current) return;
|
|
954
|
+
if (Date.now() - lastResizeEndTsRef.current < 150) return;
|
|
955
|
+
handleSort(String(field));
|
|
956
|
+
} : void 0,
|
|
355
957
|
role: isSortable ? "button" : void 0,
|
|
356
958
|
tabIndex: isSortable ? 0 : void 0,
|
|
357
959
|
onKeyDown: isSortable ? (e) => {
|
|
@@ -362,13 +964,27 @@ function RowaKitTable({
|
|
|
362
964
|
} : void 0,
|
|
363
965
|
"aria-sort": isSortable && query.sort?.field === String(field) ? query.sort.direction === "asc" ? "ascending" : "descending" : void 0,
|
|
364
966
|
style: {
|
|
365
|
-
width:
|
|
366
|
-
textAlign: column.align
|
|
967
|
+
width: actualWidth != null ? `${actualWidth}px` : void 0,
|
|
968
|
+
textAlign: column.align,
|
|
969
|
+
position: isResizable ? "relative" : void 0
|
|
367
970
|
},
|
|
368
|
-
className:
|
|
971
|
+
className: [
|
|
972
|
+
column.truncate ? "rowakit-cell-truncate" : "",
|
|
973
|
+
resizingColIdRef.current === column.id ? "resizing" : ""
|
|
974
|
+
// PRD-01
|
|
975
|
+
].filter(Boolean).join(" ") || void 0,
|
|
369
976
|
children: [
|
|
370
977
|
getHeaderLabel(column),
|
|
371
|
-
isSortable && getSortIndicator(String(field))
|
|
978
|
+
isSortable && getSortIndicator(String(field)),
|
|
979
|
+
isResizable && /* @__PURE__ */ jsx(
|
|
980
|
+
"div",
|
|
981
|
+
{
|
|
982
|
+
className: "rowakit-column-resize-handle",
|
|
983
|
+
onPointerDown: (e) => startColumnResize(e, column.id),
|
|
984
|
+
onDoubleClick: (e) => handleColumnResizeDoubleClick(e, column.id),
|
|
985
|
+
title: "Drag to resize | Double-click to auto-fit content"
|
|
986
|
+
}
|
|
987
|
+
)
|
|
372
988
|
]
|
|
373
989
|
},
|
|
374
990
|
column.id
|
|
@@ -468,6 +1084,51 @@ function RowaKitTable({
|
|
|
468
1084
|
] }) }, column.id);
|
|
469
1085
|
}
|
|
470
1086
|
const isNumberColumn = column.kind === "number";
|
|
1087
|
+
if (isNumberColumn) {
|
|
1088
|
+
const fromValue = filterValue?.op === "range" ? String(filterValue.value.from ?? "") : filterValue?.op === "equals" && typeof filterValue.value === "number" ? String(filterValue.value) : "";
|
|
1089
|
+
const toValue = filterValue?.op === "range" ? String(filterValue.value.to ?? "") : "";
|
|
1090
|
+
const showRangeUI = !filterValue || filterValue.op === "range";
|
|
1091
|
+
if (showRangeUI) {
|
|
1092
|
+
return /* @__PURE__ */ jsx("th", { children: /* @__PURE__ */ jsxs("div", { className: "rowakit-filter-number-range", children: [
|
|
1093
|
+
/* @__PURE__ */ jsx(
|
|
1094
|
+
"input",
|
|
1095
|
+
{
|
|
1096
|
+
type: "number",
|
|
1097
|
+
className: "rowakit-filter-input",
|
|
1098
|
+
placeholder: "Min",
|
|
1099
|
+
value: fromValue,
|
|
1100
|
+
onChange: (e) => {
|
|
1101
|
+
const from = e.target.value ? Number(e.target.value) : void 0;
|
|
1102
|
+
const to = toValue ? Number(toValue) : void 0;
|
|
1103
|
+
if (from === void 0 && to === void 0) {
|
|
1104
|
+
handleClearFilter(field);
|
|
1105
|
+
} else {
|
|
1106
|
+
handleFilterChange(field, { op: "range", value: { from, to } });
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
),
|
|
1111
|
+
/* @__PURE__ */ jsx(
|
|
1112
|
+
"input",
|
|
1113
|
+
{
|
|
1114
|
+
type: "number",
|
|
1115
|
+
className: "rowakit-filter-input",
|
|
1116
|
+
placeholder: "Max",
|
|
1117
|
+
value: toValue,
|
|
1118
|
+
onChange: (e) => {
|
|
1119
|
+
const to = e.target.value ? Number(e.target.value) : void 0;
|
|
1120
|
+
const from = fromValue ? Number(fromValue) : void 0;
|
|
1121
|
+
if (from === void 0 && to === void 0) {
|
|
1122
|
+
handleClearFilter(field);
|
|
1123
|
+
} else {
|
|
1124
|
+
handleFilterChange(field, { op: "range", value: { from, to } });
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
)
|
|
1129
|
+
] }) }, column.id);
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
471
1132
|
return /* @__PURE__ */ jsx("th", { children: /* @__PURE__ */ jsx(
|
|
472
1133
|
"input",
|
|
473
1134
|
{
|
|
@@ -519,12 +1180,14 @@ function RowaKitTable({
|
|
|
519
1180
|
column.kind === "number" ? "rowakit-cell-number" : "",
|
|
520
1181
|
column.truncate ? "rowakit-cell-truncate" : ""
|
|
521
1182
|
].filter(Boolean).join(" ") || void 0;
|
|
1183
|
+
const actualWidth = columnWidths[column.id] ?? column.width;
|
|
522
1184
|
return /* @__PURE__ */ jsx(
|
|
523
1185
|
"td",
|
|
524
1186
|
{
|
|
1187
|
+
"data-col-id": column.id,
|
|
525
1188
|
className: cellClass,
|
|
526
1189
|
style: {
|
|
527
|
-
width:
|
|
1190
|
+
width: actualWidth != null ? `${actualWidth}px` : void 0,
|
|
528
1191
|
textAlign: column.align || (column.kind === "number" ? "right" : void 0)
|
|
529
1192
|
},
|
|
530
1193
|
children: renderCell(column, row, isLoading, setConfirmState)
|
|
@@ -629,7 +1292,7 @@ function RowaKitTable({
|
|
|
629
1292
|
var SmartTable = RowaKitTable;
|
|
630
1293
|
|
|
631
1294
|
// src/index.ts
|
|
632
|
-
var VERSION = "0.
|
|
1295
|
+
var VERSION = "0.4.0";
|
|
633
1296
|
|
|
634
1297
|
export { RowaKitTable, SmartTable, VERSION, col };
|
|
635
1298
|
//# sourceMappingURL=index.js.map
|