@rowakit/table 0.3.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/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { useState, useRef, useEffect } from 'react';
2
- import { jsxs, jsx } from 'react/jsx-runtime';
1
+ import { useState, useRef, useEffect, useMemo } from 'react';
2
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
3
3
 
4
4
  // src/column-helpers.ts
5
5
  function text(field, options) {
@@ -103,6 +103,834 @@ var col = {
103
103
  actions,
104
104
  custom
105
105
  };
106
+ function useColumnResizing(columns) {
107
+ const [columnWidths, setColumnWidths] = useState({});
108
+ const resizeRafRef = useRef(null);
109
+ const resizePendingRef = useRef(null);
110
+ const tableRef = useRef(null);
111
+ const isResizingRef = useRef(false);
112
+ const lastResizeEndTsRef = useRef(0);
113
+ const resizingColIdRef = useRef(null);
114
+ const scheduleColumnWidthUpdate = (colId, width) => {
115
+ resizePendingRef.current = { colId, width };
116
+ if (resizeRafRef.current != null) return;
117
+ resizeRafRef.current = requestAnimationFrame(() => {
118
+ resizeRafRef.current = null;
119
+ const pending = resizePendingRef.current;
120
+ if (!pending) return;
121
+ handleColumnResize(pending.colId, pending.width);
122
+ });
123
+ };
124
+ const handleColumnResize = (columnId, newWidth) => {
125
+ const minWidth = columns.find((c) => c.id === columnId)?.minWidth ?? 80;
126
+ const maxWidth = columns.find((c) => c.id === columnId)?.maxWidth;
127
+ let finalWidth = Math.max(minWidth, newWidth);
128
+ if (maxWidth) {
129
+ finalWidth = Math.min(finalWidth, maxWidth);
130
+ }
131
+ if (columnWidths[columnId] === finalWidth) {
132
+ return;
133
+ }
134
+ setColumnWidths((prev) => ({
135
+ ...prev,
136
+ [columnId]: finalWidth
137
+ }));
138
+ };
139
+ const autoFitColumnWidth = (columnId) => {
140
+ const tableEl = tableRef.current;
141
+ if (!tableEl) return;
142
+ const th = tableEl.querySelector(`th[data-col-id="${columnId}"]`);
143
+ if (!th) return;
144
+ const tds = Array.from(
145
+ tableEl.querySelectorAll(`td[data-col-id="${columnId}"]`)
146
+ );
147
+ const headerW = th.scrollWidth;
148
+ const cellsMaxW = tds.reduce((max, td) => Math.max(max, td.scrollWidth), 0);
149
+ const padding = 24;
150
+ const raw = Math.max(headerW, cellsMaxW) + padding;
151
+ const colDef = columns.find((c) => c.id === columnId);
152
+ const minW = colDef?.minWidth ?? 80;
153
+ const maxW = colDef?.maxWidth ?? 600;
154
+ const finalW = Math.max(minW, Math.min(raw, maxW));
155
+ setColumnWidths((prev) => ({ ...prev, [columnId]: finalW }));
156
+ };
157
+ const startColumnResize = (e, columnId) => {
158
+ e.preventDefault();
159
+ e.stopPropagation();
160
+ if (e.detail === 2) {
161
+ autoFitColumnWidth(columnId);
162
+ return;
163
+ }
164
+ if (e.pointerType === "mouse" && e.buttons !== 1) {
165
+ return;
166
+ }
167
+ const target = e.currentTarget;
168
+ const pointerId = e.pointerId;
169
+ try {
170
+ target.setPointerCapture(pointerId);
171
+ } catch {
172
+ }
173
+ isResizingRef.current = true;
174
+ resizingColIdRef.current = columnId;
175
+ const startX = e.clientX;
176
+ const th = target.parentElement;
177
+ let startWidth = columnWidths[columnId] ?? th.offsetWidth;
178
+ const MIN_DRAG_WIDTH = 80;
179
+ if (startWidth < MIN_DRAG_WIDTH) {
180
+ const nextTh = th.nextElementSibling;
181
+ if (nextTh && nextTh.offsetWidth >= 50) {
182
+ startWidth = nextTh.offsetWidth;
183
+ } else {
184
+ startWidth = 100;
185
+ }
186
+ }
187
+ document.body.classList.add("rowakit-resizing");
188
+ const handlePointerMove = (moveEvent) => {
189
+ const delta = moveEvent.clientX - startX;
190
+ const newWidth = startWidth + delta;
191
+ scheduleColumnWidthUpdate(columnId, newWidth);
192
+ };
193
+ const cleanupResize = () => {
194
+ target.removeEventListener("pointermove", handlePointerMove);
195
+ target.removeEventListener("pointerup", handlePointerUp);
196
+ target.removeEventListener("pointercancel", handlePointerCancel);
197
+ document.body.classList.remove("rowakit-resizing");
198
+ isResizingRef.current = false;
199
+ resizingColIdRef.current = null;
200
+ lastResizeEndTsRef.current = Date.now();
201
+ try {
202
+ target.releasePointerCapture(pointerId);
203
+ } catch {
204
+ }
205
+ };
206
+ const handlePointerUp = () => {
207
+ cleanupResize();
208
+ };
209
+ const handlePointerCancel = () => {
210
+ cleanupResize();
211
+ };
212
+ target.addEventListener("pointermove", handlePointerMove);
213
+ target.addEventListener("pointerup", handlePointerUp);
214
+ target.addEventListener("pointercancel", handlePointerCancel);
215
+ };
216
+ const handleColumnResizeDoubleClick = (e, columnId) => {
217
+ e.preventDefault();
218
+ e.stopPropagation();
219
+ autoFitColumnWidth(columnId);
220
+ };
221
+ return {
222
+ tableRef,
223
+ columnWidths,
224
+ setColumnWidths,
225
+ startColumnResize,
226
+ handleColumnResizeDoubleClick,
227
+ isResizingRef,
228
+ lastResizeEndTsRef,
229
+ resizingColIdRef
230
+ };
231
+ }
232
+ function useFetcherState(fetcher, query, setQuery) {
233
+ const [dataState, setDataState] = useState({
234
+ state: "idle",
235
+ items: [],
236
+ total: 0
237
+ });
238
+ const requestIdRef = useRef(0);
239
+ useEffect(() => {
240
+ const currentRequestId = ++requestIdRef.current;
241
+ setDataState((prev) => ({ ...prev, state: "loading" }));
242
+ fetcher(query).then((result) => {
243
+ if (currentRequestId !== requestIdRef.current) return;
244
+ if (result.items.length === 0) {
245
+ setDataState({
246
+ state: "empty",
247
+ items: [],
248
+ total: result.total
249
+ });
250
+ return;
251
+ }
252
+ setDataState({
253
+ state: "success",
254
+ items: result.items,
255
+ total: result.total
256
+ });
257
+ }).catch((error) => {
258
+ if (currentRequestId !== requestIdRef.current) return;
259
+ setDataState({
260
+ state: "error",
261
+ items: [],
262
+ total: 0,
263
+ error: error instanceof Error ? error.message : "Failed to load data"
264
+ });
265
+ });
266
+ }, [fetcher, query]);
267
+ const handleRetry = () => {
268
+ setQuery({ ...query });
269
+ };
270
+ return {
271
+ dataState,
272
+ setDataState,
273
+ handleRetry,
274
+ isLoading: dataState.state === "loading",
275
+ isError: dataState.state === "error",
276
+ isEmpty: dataState.state === "empty"
277
+ };
278
+ }
279
+ function validateViewName(name) {
280
+ const trimmed = name.trim();
281
+ if (trimmed.length === 0) {
282
+ return { valid: false, error: "Name cannot be empty" };
283
+ }
284
+ if (trimmed.length > 40) {
285
+ return { valid: false, error: "Name cannot exceed 40 characters" };
286
+ }
287
+ const invalidChars = /[/\\?%*:|"<>\x00-\x1f\x7f]/;
288
+ if (invalidChars.test(trimmed)) {
289
+ return { valid: false, error: "Name contains invalid characters" };
290
+ }
291
+ return { valid: true };
292
+ }
293
+ function getSavedViewsIndex() {
294
+ if (typeof window === "undefined" || !window.localStorage) {
295
+ return [];
296
+ }
297
+ try {
298
+ const indexStr = localStorage.getItem("rowakit-views-index");
299
+ if (indexStr) {
300
+ const index = JSON.parse(indexStr);
301
+ if (Array.isArray(index)) {
302
+ return index;
303
+ }
304
+ }
305
+ } catch {
306
+ }
307
+ const rebuilt = [];
308
+ try {
309
+ for (let i = 0; i < localStorage.length; i++) {
310
+ const key = localStorage.key(i);
311
+ if (key?.startsWith("rowakit-view-")) {
312
+ const name = key.substring("rowakit-view-".length);
313
+ rebuilt.push({ name, updatedAt: Date.now() });
314
+ }
315
+ }
316
+ } catch {
317
+ }
318
+ return rebuilt;
319
+ }
320
+ function setSavedViewsIndex(index) {
321
+ if (typeof window === "undefined" || !window.localStorage) {
322
+ return;
323
+ }
324
+ try {
325
+ localStorage.setItem("rowakit-views-index", JSON.stringify(index));
326
+ } catch {
327
+ }
328
+ }
329
+ function loadSavedViewsFromStorage() {
330
+ if (typeof window === "undefined" || !window.localStorage) {
331
+ return [];
332
+ }
333
+ const index = getSavedViewsIndex();
334
+ const views = [];
335
+ for (const entry of index) {
336
+ try {
337
+ const viewStr = localStorage.getItem(`rowakit-view-${entry.name}`);
338
+ if (viewStr) {
339
+ const state = JSON.parse(viewStr);
340
+ views.push({ name: entry.name, state });
341
+ }
342
+ } catch {
343
+ }
344
+ }
345
+ return views;
346
+ }
347
+ function useSavedViews(options) {
348
+ const [savedViews, setSavedViews] = useState([]);
349
+ const [showSaveViewForm, setShowSaveViewForm] = useState(false);
350
+ const [saveViewInput, setSaveViewInput] = useState("");
351
+ const [saveViewError, setSaveViewError] = useState("");
352
+ const [overwriteConfirmName, setOverwriteConfirmName] = useState(null);
353
+ useEffect(() => {
354
+ if (!options.enableSavedViews) return;
355
+ setSavedViews(loadSavedViewsFromStorage());
356
+ }, [options.enableSavedViews]);
357
+ const saveCurrentView = (name) => {
358
+ const viewState = {
359
+ page: options.query.page,
360
+ pageSize: options.query.pageSize,
361
+ sort: options.query.sort,
362
+ filters: options.query.filters,
363
+ columnWidths: options.enableColumnResizing ? options.columnWidths : void 0
364
+ };
365
+ setSavedViews((prev) => {
366
+ const filtered = prev.filter((v) => v.name !== name);
367
+ return [...filtered, { name, state: viewState }];
368
+ });
369
+ if (typeof window !== "undefined" && window.localStorage) {
370
+ try {
371
+ localStorage.setItem(`rowakit-view-${name}`, JSON.stringify(viewState));
372
+ const index = getSavedViewsIndex();
373
+ const filtered = index.filter((v) => v.name !== name);
374
+ filtered.push({ name, updatedAt: Date.now() });
375
+ setSavedViewsIndex(filtered);
376
+ } catch {
377
+ }
378
+ }
379
+ };
380
+ const loadSavedView = (name) => {
381
+ const view = savedViews.find((v) => v.name === name);
382
+ if (!view) return;
383
+ const { state } = view;
384
+ options.setQuery({
385
+ page: state.page,
386
+ pageSize: state.pageSize,
387
+ sort: state.sort,
388
+ filters: state.filters
389
+ });
390
+ options.setFilters(state.filters ?? {});
391
+ if (state.columnWidths && options.enableColumnResizing) {
392
+ options.setColumnWidths(state.columnWidths);
393
+ }
394
+ };
395
+ const deleteSavedView = (name) => {
396
+ setSavedViews((prev) => prev.filter((v) => v.name !== name));
397
+ if (typeof window !== "undefined" && window.localStorage) {
398
+ try {
399
+ localStorage.removeItem(`rowakit-view-${name}`);
400
+ const index = getSavedViewsIndex();
401
+ const filtered = index.filter((v) => v.name !== name);
402
+ setSavedViewsIndex(filtered);
403
+ } catch {
404
+ }
405
+ }
406
+ };
407
+ const resetTableState = () => {
408
+ options.setQuery({
409
+ page: 1,
410
+ pageSize: options.defaultPageSize
411
+ });
412
+ options.setFilters({});
413
+ options.setColumnWidths({});
414
+ };
415
+ const shouldShowReset = useMemo(() => {
416
+ if (!options.enableSavedViews) return false;
417
+ return Boolean(options.query.page > 1 || options.query.sort || options.query.filters && Object.keys(options.query.filters).length > 0);
418
+ }, [options.enableSavedViews, options.query.page, options.query.sort, options.query.filters]);
419
+ const openSaveViewForm = () => {
420
+ setShowSaveViewForm(true);
421
+ setSaveViewInput("");
422
+ setSaveViewError("");
423
+ setOverwriteConfirmName(null);
424
+ };
425
+ const cancelSaveViewForm = () => {
426
+ setShowSaveViewForm(false);
427
+ setSaveViewInput("");
428
+ setSaveViewError("");
429
+ setOverwriteConfirmName(null);
430
+ };
431
+ const onSaveViewInputChange = (e) => {
432
+ setSaveViewInput(e.target.value);
433
+ setSaveViewError("");
434
+ };
435
+ const attemptSave = () => {
436
+ const validation = validateViewName(saveViewInput);
437
+ if (!validation.valid) {
438
+ setSaveViewError(validation.error || "Invalid name");
439
+ return;
440
+ }
441
+ const trimmed = saveViewInput.trim();
442
+ if (savedViews.some((v) => v.name === trimmed)) {
443
+ setOverwriteConfirmName(trimmed);
444
+ return;
445
+ }
446
+ saveCurrentView(trimmed);
447
+ cancelSaveViewForm();
448
+ };
449
+ const onSaveViewInputKeyDown = (e) => {
450
+ if (e.key !== "Enter") return;
451
+ attemptSave();
452
+ };
453
+ const confirmOverwrite = () => {
454
+ if (!overwriteConfirmName) return;
455
+ saveCurrentView(overwriteConfirmName);
456
+ cancelSaveViewForm();
457
+ };
458
+ const cancelOverwrite = () => {
459
+ setOverwriteConfirmName(null);
460
+ };
461
+ return {
462
+ savedViews,
463
+ showSaveViewForm,
464
+ saveViewInput,
465
+ saveViewError,
466
+ overwriteConfirmName,
467
+ openSaveViewForm,
468
+ cancelSaveViewForm,
469
+ onSaveViewInputChange,
470
+ onSaveViewInputKeyDown,
471
+ attemptSave,
472
+ confirmOverwrite,
473
+ cancelOverwrite,
474
+ loadSavedView,
475
+ deleteSavedView,
476
+ resetTableState,
477
+ shouldShowReset
478
+ };
479
+ }
480
+
481
+ // src/hooks/useSortingState.ts
482
+ function useSortingState(query, setQuery) {
483
+ const handleSort = (field, isMultiSort = false) => {
484
+ setQuery((prev) => {
485
+ const currentSorts = prev.sorts || [];
486
+ const existingSort = currentSorts.find((s) => s.field === field);
487
+ if (!isMultiSort) {
488
+ if (existingSort?.priority === 0) {
489
+ if (existingSort.direction === "asc") {
490
+ return {
491
+ ...prev,
492
+ sorts: [{ field, direction: "desc", priority: 0 }],
493
+ page: 1
494
+ };
495
+ }
496
+ const { sorts: _removed, ...rest } = prev;
497
+ return {
498
+ ...rest,
499
+ page: 1
500
+ };
501
+ }
502
+ return {
503
+ ...prev,
504
+ sorts: [{ field, direction: "asc", priority: 0 }],
505
+ page: 1
506
+ };
507
+ }
508
+ if (existingSort) {
509
+ const newSorts2 = currentSorts.map((s) => {
510
+ if (s.field === field) {
511
+ const newDirection = s.direction === "asc" ? "desc" : "asc";
512
+ return { ...s, direction: newDirection };
513
+ }
514
+ return s;
515
+ });
516
+ return { ...prev, sorts: newSorts2, page: 1 };
517
+ }
518
+ const nextPriority = currentSorts.length;
519
+ const newSorts = [...currentSorts, { field, direction: "asc", priority: nextPriority }];
520
+ return { ...prev, sorts: newSorts, page: 1 };
521
+ });
522
+ };
523
+ const getSortIndicator = (field) => {
524
+ const sorts = query.sorts || [];
525
+ const sort = sorts.find((s) => s.field === field);
526
+ if (!sort) {
527
+ return "";
528
+ }
529
+ const directionIcon = sort.direction === "asc" ? " \u2191" : " \u2193";
530
+ const priorityLabel = sort.priority === 0 ? "" : ` [${sort.priority + 1}]`;
531
+ return directionIcon + priorityLabel;
532
+ };
533
+ const getSortPriority = (field) => {
534
+ const sorts = query.sorts || [];
535
+ const sort = sorts.find((s) => s.field === field);
536
+ return sort ? sort.priority : null;
537
+ };
538
+ return { handleSort, getSortIndicator, getSortPriority };
539
+ }
540
+ function parseUrlState(params, defaultPageSize, pageSizeOptions) {
541
+ const pageStr = params.get("page");
542
+ let page = 1;
543
+ if (pageStr) {
544
+ const parsed = parseInt(pageStr, 10);
545
+ page = !isNaN(parsed) && parsed >= 1 ? parsed : 1;
546
+ }
547
+ const pageSizeStr = params.get("pageSize");
548
+ let pageSize = defaultPageSize;
549
+ if (pageSizeStr) {
550
+ const parsed = parseInt(pageSizeStr, 10);
551
+ if (!isNaN(parsed) && parsed >= 1) {
552
+ if (pageSizeOptions && pageSizeOptions.length > 0) {
553
+ pageSize = pageSizeOptions.includes(parsed) ? parsed : defaultPageSize;
554
+ } else {
555
+ pageSize = parsed;
556
+ }
557
+ }
558
+ }
559
+ const result = { page, pageSize };
560
+ const sortsStr = params.get("sorts");
561
+ if (sortsStr) {
562
+ try {
563
+ const parsed = JSON.parse(sortsStr);
564
+ if (Array.isArray(parsed) && parsed.every((s) => typeof s.field === "string" && (s.direction === "asc" || s.direction === "desc") && typeof s.priority === "number")) {
565
+ result.sorts = parsed;
566
+ }
567
+ } catch {
568
+ }
569
+ }
570
+ if (!result.sorts) {
571
+ const sortField = params.get("sortField");
572
+ const sortDir = params.get("sortDirection");
573
+ if (sortField && (sortDir === "asc" || sortDir === "desc")) {
574
+ result.sort = { field: sortField, direction: sortDir };
575
+ }
576
+ }
577
+ const filtersStr = params.get("filters");
578
+ if (filtersStr) {
579
+ try {
580
+ const parsed = JSON.parse(filtersStr);
581
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
582
+ result.filters = parsed;
583
+ }
584
+ } catch {
585
+ }
586
+ }
587
+ const widthsStr = params.get("columnWidths");
588
+ if (widthsStr) {
589
+ try {
590
+ const parsed = JSON.parse(widthsStr);
591
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
592
+ const widths = {};
593
+ for (const [key, value] of Object.entries(parsed)) {
594
+ if (typeof value === "number" && value > 0) {
595
+ widths[key] = value;
596
+ }
597
+ }
598
+ if (Object.keys(widths).length > 0) {
599
+ result.columnWidths = widths;
600
+ }
601
+ }
602
+ } catch {
603
+ }
604
+ }
605
+ return result;
606
+ }
607
+ function serializeUrlState(query, filters, columnWidths, defaultPageSize, enableColumnResizing) {
608
+ const params = new URLSearchParams();
609
+ params.set("page", String(query.page));
610
+ if (query.pageSize !== defaultPageSize) {
611
+ params.set("pageSize", String(query.pageSize));
612
+ }
613
+ if (query.sorts && query.sorts.length > 0) {
614
+ params.set("sorts", JSON.stringify(query.sorts));
615
+ } else if (query.sort) {
616
+ params.set("sortField", query.sort.field);
617
+ params.set("sortDirection", query.sort.direction);
618
+ }
619
+ if (filters && Object.keys(filters).length > 0) {
620
+ const nonEmptyFilters = Object.fromEntries(
621
+ Object.entries(filters).filter(([, v]) => v !== void 0)
622
+ );
623
+ if (Object.keys(nonEmptyFilters).length > 0) {
624
+ params.set("filters", JSON.stringify(nonEmptyFilters));
625
+ }
626
+ }
627
+ if (enableColumnResizing && Object.keys(columnWidths).length > 0) {
628
+ params.set("columnWidths", JSON.stringify(columnWidths));
629
+ }
630
+ return params.toString();
631
+ }
632
+ function useUrlSync({
633
+ syncToUrl,
634
+ enableColumnResizing,
635
+ defaultPageSize,
636
+ pageSizeOptions,
637
+ columns,
638
+ query,
639
+ setQuery,
640
+ filters,
641
+ setFilters,
642
+ columnWidths,
643
+ setColumnWidths
644
+ }) {
645
+ const didHydrateUrlRef = useRef(false);
646
+ const didSkipInitialUrlSyncRef = useRef(false);
647
+ const urlSyncDebounceRef = useRef(null);
648
+ useEffect(() => {
649
+ if (!syncToUrl) {
650
+ didSkipInitialUrlSyncRef.current = false;
651
+ return;
652
+ }
653
+ if (!didSkipInitialUrlSyncRef.current) {
654
+ didSkipInitialUrlSyncRef.current = true;
655
+ return;
656
+ }
657
+ if (urlSyncDebounceRef.current) {
658
+ clearTimeout(urlSyncDebounceRef.current);
659
+ urlSyncDebounceRef.current = null;
660
+ }
661
+ const urlStr = serializeUrlState(query, filters, columnWidths, defaultPageSize, enableColumnResizing);
662
+ const qs = urlStr ? `?${urlStr}` : "";
663
+ window.history.replaceState(null, "", `${window.location.pathname}${qs}${window.location.hash}`);
664
+ }, [
665
+ query,
666
+ filters,
667
+ syncToUrl,
668
+ enableColumnResizing,
669
+ defaultPageSize,
670
+ columnWidths
671
+ ]);
672
+ useEffect(() => {
673
+ if (!syncToUrl || !enableColumnResizing) return;
674
+ if (!didSkipInitialUrlSyncRef.current) return;
675
+ if (urlSyncDebounceRef.current) {
676
+ clearTimeout(urlSyncDebounceRef.current);
677
+ }
678
+ urlSyncDebounceRef.current = setTimeout(() => {
679
+ const urlStr = serializeUrlState(query, filters, columnWidths, defaultPageSize, enableColumnResizing);
680
+ const qs = urlStr ? `?${urlStr}` : "";
681
+ window.history.replaceState(null, "", `${window.location.pathname}${qs}${window.location.hash}`);
682
+ urlSyncDebounceRef.current = null;
683
+ }, 150);
684
+ return () => {
685
+ if (urlSyncDebounceRef.current) {
686
+ clearTimeout(urlSyncDebounceRef.current);
687
+ urlSyncDebounceRef.current = null;
688
+ }
689
+ };
690
+ }, [
691
+ columnWidths,
692
+ syncToUrl,
693
+ enableColumnResizing,
694
+ query,
695
+ filters,
696
+ defaultPageSize
697
+ ]);
698
+ useEffect(() => {
699
+ if (!syncToUrl) {
700
+ didHydrateUrlRef.current = false;
701
+ return;
702
+ }
703
+ if (didHydrateUrlRef.current) return;
704
+ didHydrateUrlRef.current = true;
705
+ const params = new URLSearchParams(window.location.search);
706
+ const parsed = parseUrlState(params, defaultPageSize, pageSizeOptions);
707
+ setQuery({
708
+ page: parsed.page,
709
+ pageSize: parsed.pageSize,
710
+ sort: parsed.sort,
711
+ sorts: parsed.sorts,
712
+ filters: parsed.filters
713
+ });
714
+ if (parsed.filters) {
715
+ setFilters(parsed.filters);
716
+ }
717
+ if (parsed.columnWidths && enableColumnResizing) {
718
+ const clamped = {};
719
+ for (const [colId, rawWidth] of Object.entries(parsed.columnWidths)) {
720
+ const widthNum = typeof rawWidth === "number" ? rawWidth : Number(rawWidth);
721
+ if (!Number.isFinite(widthNum)) continue;
722
+ const colDef = columns.find((c) => c.id === colId);
723
+ if (!colDef) continue;
724
+ const minW = colDef.minWidth ?? 80;
725
+ const maxW = colDef.maxWidth;
726
+ let finalW = Math.max(minW, widthNum);
727
+ if (maxW != null) {
728
+ finalW = Math.min(finalW, maxW);
729
+ }
730
+ clamped[colId] = finalW;
731
+ }
732
+ setColumnWidths(clamped);
733
+ }
734
+ }, [
735
+ syncToUrl,
736
+ defaultPageSize,
737
+ enableColumnResizing,
738
+ pageSizeOptions,
739
+ columns,
740
+ setQuery,
741
+ setFilters,
742
+ setColumnWidths
743
+ ]);
744
+ }
745
+ var FOCUSABLE_SELECTORS = [
746
+ "button",
747
+ "[href]",
748
+ "input",
749
+ "select",
750
+ "textarea",
751
+ '[tabindex]:not([tabindex="-1"])'
752
+ ].join(",");
753
+ function useFocusTrap(ref, options = {}) {
754
+ const { onEscape, autoFocus = true } = options;
755
+ const firstFocusableRef = useRef(null);
756
+ const lastFocusableRef = useRef(null);
757
+ useEffect(() => {
758
+ const modalEl = ref.current;
759
+ if (!modalEl) return;
760
+ const getFocusableElements = () => {
761
+ const elements = Array.from(modalEl.querySelectorAll(FOCUSABLE_SELECTORS));
762
+ return elements.filter((el) => !el.hasAttribute("disabled") && el.offsetParent !== null);
763
+ };
764
+ let focusableElements = getFocusableElements();
765
+ if (focusableElements.length === 0) return;
766
+ firstFocusableRef.current = focusableElements[0] || null;
767
+ lastFocusableRef.current = focusableElements[focusableElements.length - 1] || null;
768
+ if (autoFocus && firstFocusableRef.current) {
769
+ firstFocusableRef.current.focus();
770
+ }
771
+ const handleKeyDown = (e) => {
772
+ focusableElements = getFocusableElements();
773
+ if (focusableElements.length === 0) return;
774
+ const activeEl = document.activeElement;
775
+ const firstEl = focusableElements[0] || null;
776
+ const lastEl = focusableElements[focusableElements.length - 1] || null;
777
+ if (e.key === "Escape") {
778
+ e.preventDefault();
779
+ onEscape?.();
780
+ return;
781
+ }
782
+ if (e.key === "Tab") {
783
+ if (e.shiftKey) {
784
+ if (activeEl === firstEl && lastEl) {
785
+ e.preventDefault();
786
+ lastEl.focus();
787
+ }
788
+ } else {
789
+ if (activeEl === lastEl && firstEl) {
790
+ e.preventDefault();
791
+ firstEl.focus();
792
+ }
793
+ }
794
+ }
795
+ };
796
+ modalEl.addEventListener("keydown", handleKeyDown);
797
+ return () => {
798
+ modalEl.removeEventListener("keydown", handleKeyDown);
799
+ };
800
+ }, [ref, onEscape, autoFocus]);
801
+ }
802
+ function RowSelectionHeaderCell(props) {
803
+ const checkboxRef = useRef(null);
804
+ useEffect(() => {
805
+ if (!checkboxRef.current) return;
806
+ checkboxRef.current.indeterminate = props.indeterminate;
807
+ }, [props.indeterminate]);
808
+ return /* @__PURE__ */ jsx("th", { children: /* @__PURE__ */ jsx(
809
+ "input",
810
+ {
811
+ ref: checkboxRef,
812
+ type: "checkbox",
813
+ "aria-label": "Select all rows",
814
+ disabled: props.disabled,
815
+ checked: props.checked,
816
+ onChange: (e) => props.onChange(e.target.checked)
817
+ }
818
+ ) });
819
+ }
820
+ function RowSelectionCell(props) {
821
+ return /* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx(
822
+ "input",
823
+ {
824
+ type: "checkbox",
825
+ "aria-label": `Select row ${props.rowKey}`,
826
+ disabled: props.disabled,
827
+ checked: props.checked,
828
+ onChange: (e) => props.onChange(e.target.checked)
829
+ }
830
+ ) });
831
+ }
832
+ function BulkActionBar(props) {
833
+ if (props.selectedCount <= 0) return null;
834
+ return /* @__PURE__ */ jsxs("div", { className: "rowakit-bulk-action-bar", children: [
835
+ /* @__PURE__ */ jsxs("span", { children: [
836
+ props.selectedCount,
837
+ " selected"
838
+ ] }),
839
+ props.actions.map((action) => /* @__PURE__ */ jsx(
840
+ "button",
841
+ {
842
+ type: "button",
843
+ className: "rowakit-button rowakit-button-secondary",
844
+ onClick: () => props.onActionClick(action.id),
845
+ children: action.label
846
+ },
847
+ action.id
848
+ ))
849
+ ] });
850
+ }
851
+ function downloadBlob(blob, filename) {
852
+ if (typeof window === "undefined") return;
853
+ if (typeof URL === "undefined" || typeof URL.createObjectURL !== "function") return;
854
+ const url = URL.createObjectURL(blob);
855
+ try {
856
+ const a = document.createElement("a");
857
+ a.href = url;
858
+ a.download = filename;
859
+ a.rel = "noopener noreferrer";
860
+ a.click();
861
+ } finally {
862
+ URL.revokeObjectURL(url);
863
+ }
864
+ }
865
+ function openUrl(url) {
866
+ if (typeof window === "undefined") return;
867
+ if (typeof window.open === "function") {
868
+ window.open(url, "_blank", "noopener,noreferrer");
869
+ return;
870
+ }
871
+ try {
872
+ window.location.assign(url);
873
+ } catch {
874
+ }
875
+ }
876
+ function ExportButton(props) {
877
+ const [isExporting, setIsExporting] = useState(false);
878
+ const [error, setError] = useState(null);
879
+ const onClick = async () => {
880
+ if (isExporting) return;
881
+ setIsExporting(true);
882
+ setError(null);
883
+ try {
884
+ const snapshot = { ...props.query };
885
+ const result = await props.exporter(snapshot);
886
+ if (result instanceof Blob) {
887
+ downloadBlob(result, "rowakit-export.csv");
888
+ return;
889
+ }
890
+ openUrl(result.url);
891
+ } catch (e) {
892
+ setError(e instanceof Error ? e.message : "Export failed");
893
+ } finally {
894
+ setIsExporting(false);
895
+ }
896
+ };
897
+ return /* @__PURE__ */ jsxs("div", { className: "rowakit-export", children: [
898
+ /* @__PURE__ */ jsx(
899
+ "button",
900
+ {
901
+ type: "button",
902
+ className: "rowakit-button rowakit-button-secondary",
903
+ onClick,
904
+ disabled: isExporting,
905
+ children: isExporting ? "Exporting\u2026" : "Export CSV"
906
+ }
907
+ ),
908
+ error && /* @__PURE__ */ jsx("div", { className: "rowakit-export-error", children: error })
909
+ ] });
910
+ }
911
+
912
+ // src/state/selection.ts
913
+ function toggleSelectionKey(selected, key) {
914
+ if (selected.includes(key)) {
915
+ return selected.filter((k) => k !== key);
916
+ }
917
+ return [...selected, key];
918
+ }
919
+ function isAllSelected(selected, pageKeys) {
920
+ if (pageKeys.length === 0) return false;
921
+ return pageKeys.every((k) => selected.includes(k));
922
+ }
923
+ function isIndeterminate(selected, pageKeys) {
924
+ if (pageKeys.length === 0) return false;
925
+ const selectedCount = pageKeys.filter((k) => selected.includes(k)).length;
926
+ return selectedCount > 0 && selectedCount < pageKeys.length;
927
+ }
928
+ function selectAll(pageKeys) {
929
+ return [...pageKeys];
930
+ }
931
+ function clearSelection() {
932
+ return [];
933
+ }
106
934
  function getRowKey(row, rowKey) {
107
935
  if (typeof rowKey === "function") {
108
936
  return rowKey(row);
@@ -218,76 +1046,92 @@ function RowaKitTable({
218
1046
  enableFilters = false,
219
1047
  enableColumnResizing = false,
220
1048
  syncToUrl = false,
221
- enableSavedViews = false
1049
+ enableSavedViews = false,
1050
+ enableRowSelection = false,
1051
+ onSelectionChange,
1052
+ bulkActions,
1053
+ exporter
222
1054
  }) {
223
- const [dataState, setDataState] = useState({
224
- state: "idle",
225
- items: [],
226
- total: 0
227
- });
228
1055
  const [query, setQuery] = useState({
229
1056
  page: 1,
230
1057
  pageSize: defaultPageSize
231
1058
  });
232
1059
  const [filters, setFilters] = useState({});
233
- const [columnWidths, setColumnWidths] = useState({});
234
- const resizeRafRef = useRef(null);
235
- const resizePendingRef = useRef(null);
236
- const tableRef = useRef(null);
237
- const [savedViews, setSavedViews] = useState([]);
238
1060
  const [confirmState, setConfirmState] = useState(null);
239
- const requestIdRef = useRef(0);
1061
+ const [selectedKeys, setSelectedKeys] = useState([]);
1062
+ const [bulkConfirmState, setBulkConfirmState] = useState(null);
1063
+ const confirmModalRef = useRef(null);
1064
+ const bulkConfirmModalRef = useRef(null);
1065
+ const {
1066
+ tableRef,
1067
+ columnWidths,
1068
+ setColumnWidths,
1069
+ startColumnResize,
1070
+ handleColumnResizeDoubleClick,
1071
+ isResizingRef,
1072
+ lastResizeEndTsRef,
1073
+ resizingColIdRef
1074
+ } = useColumnResizing(columns);
1075
+ useUrlSync({
1076
+ syncToUrl,
1077
+ enableColumnResizing,
1078
+ defaultPageSize,
1079
+ pageSizeOptions,
1080
+ columns,
1081
+ query,
1082
+ setQuery,
1083
+ filters,
1084
+ setFilters,
1085
+ columnWidths,
1086
+ setColumnWidths
1087
+ });
1088
+ const { dataState, handleRetry, isLoading, isError, isEmpty } = useFetcherState(fetcher, query, setQuery);
1089
+ const { handleSort, getSortIndicator } = useSortingState(query, setQuery);
1090
+ const pageRowKeys = dataState.items.map((row) => getRowKey(row, rowKey));
1091
+ const headerChecked = isAllSelected(selectedKeys, pageRowKeys);
1092
+ const headerIndeterminate = isIndeterminate(selectedKeys, pageRowKeys);
240
1093
  useEffect(() => {
241
- if (!syncToUrl) return;
242
- const params = new URLSearchParams();
243
- params.set("page", String(query.page));
244
- params.set("pageSize", String(query.pageSize));
245
- if (query.sort) {
246
- params.set("sortField", query.sort.field);
247
- params.set("sortDirection", query.sort.direction);
248
- }
249
- if (query.filters && Object.keys(query.filters).length > 0) {
250
- params.set("filters", JSON.stringify(query.filters));
251
- }
252
- if (enableColumnResizing && Object.keys(columnWidths).length > 0) {
253
- params.set("columnWidths", JSON.stringify(columnWidths));
254
- }
255
- window.history.replaceState(null, "", `?${params.toString()}`);
256
- }, [query, columnWidths, syncToUrl, enableColumnResizing]);
1094
+ if (!enableRowSelection) return;
1095
+ setSelectedKeys(clearSelection());
1096
+ }, [enableRowSelection, query.page, dataState.items]);
257
1097
  useEffect(() => {
258
- if (!syncToUrl) return;
259
- const params = new URLSearchParams(window.location.search);
260
- const page = parseInt(params.get("page") ?? "1", 10);
261
- const pageSize = parseInt(params.get("pageSize") ?? String(defaultPageSize), 10);
262
- const sortField = params.get("sortField");
263
- const sortDirection = params.get("sortDirection");
264
- const filtersStr = params.get("filters");
265
- const columnWidthsStr = params.get("columnWidths");
266
- const newQuery = {
267
- page: Math.max(1, page),
268
- pageSize: Math.max(1, pageSize)
269
- };
270
- if (sortField && sortDirection) {
271
- newQuery.sort = { field: sortField, direction: sortDirection };
272
- }
273
- if (filtersStr) {
274
- try {
275
- const parsedFilters = JSON.parse(filtersStr);
276
- if (parsedFilters && typeof parsedFilters === "object") {
277
- setFilters(parsedFilters);
278
- newQuery.filters = parsedFilters;
279
- }
280
- } catch {
281
- }
282
- }
283
- if (enableColumnResizing && columnWidthsStr) {
284
- try {
285
- setColumnWidths(JSON.parse(columnWidthsStr));
286
- } catch {
287
- }
288
- }
289
- setQuery(newQuery);
290
- }, [syncToUrl, defaultPageSize, enableColumnResizing]);
1098
+ if (!enableRowSelection || !onSelectionChange) return;
1099
+ onSelectionChange(selectedKeys);
1100
+ }, [enableRowSelection, onSelectionChange, selectedKeys]);
1101
+ const {
1102
+ savedViews,
1103
+ showSaveViewForm,
1104
+ saveViewInput,
1105
+ saveViewError,
1106
+ overwriteConfirmName,
1107
+ openSaveViewForm,
1108
+ cancelSaveViewForm,
1109
+ onSaveViewInputChange,
1110
+ onSaveViewInputKeyDown,
1111
+ attemptSave,
1112
+ confirmOverwrite,
1113
+ cancelOverwrite,
1114
+ loadSavedView,
1115
+ deleteSavedView,
1116
+ resetTableState
1117
+ } = useSavedViews({
1118
+ enableSavedViews,
1119
+ enableColumnResizing,
1120
+ defaultPageSize,
1121
+ query,
1122
+ setQuery,
1123
+ setFilters,
1124
+ columnWidths,
1125
+ setColumnWidths
1126
+ });
1127
+ useFocusTrap(confirmModalRef, {
1128
+ onEscape: () => setConfirmState(null),
1129
+ autoFocus: true
1130
+ });
1131
+ useFocusTrap(bulkConfirmModalRef, {
1132
+ onEscape: () => setBulkConfirmState(null),
1133
+ autoFocus: true
1134
+ });
291
1135
  useEffect(() => {
292
1136
  if (!enableFilters) return;
293
1137
  const activeFilters = {};
@@ -295,52 +1139,17 @@ function RowaKitTable({
295
1139
  for (const [field, value] of Object.entries(filters)) {
296
1140
  if (value !== void 0) {
297
1141
  activeFilters[field] = value;
298
- hasFilters = true;
299
- }
300
- }
301
- const filtersToSend = hasFilters ? activeFilters : void 0;
302
- setQuery((prev) => ({
303
- ...prev,
304
- filters: filtersToSend,
305
- page: 1
306
- // Reset page to 1 when filters change
307
- }));
308
- }, [filters, enableFilters]);
309
- useEffect(() => {
310
- const currentRequestId = ++requestIdRef.current;
311
- setDataState((prev) => ({ ...prev, state: "loading" }));
312
- fetcher(query).then((result) => {
313
- if (currentRequestId !== requestIdRef.current) {
314
- return;
315
- }
316
- if (result.items.length === 0) {
317
- setDataState({
318
- state: "empty",
319
- items: [],
320
- total: result.total
321
- });
322
- } else {
323
- setDataState({
324
- state: "success",
325
- items: result.items,
326
- total: result.total
327
- });
328
- }
329
- }).catch((error) => {
330
- if (currentRequestId !== requestIdRef.current) {
331
- return;
1142
+ hasFilters = true;
332
1143
  }
333
- setDataState({
334
- state: "error",
335
- items: [],
336
- total: 0,
337
- error: error instanceof Error ? error.message : "Failed to load data"
338
- });
339
- });
340
- }, [fetcher, query]);
341
- const handleRetry = () => {
342
- setQuery({ ...query });
343
- };
1144
+ }
1145
+ const filtersToSend = hasFilters ? activeFilters : void 0;
1146
+ setQuery((prev) => ({
1147
+ ...prev,
1148
+ filters: filtersToSend,
1149
+ page: 1
1150
+ // Reset page to 1 when filters change
1151
+ }));
1152
+ }, [filters, enableFilters]);
344
1153
  const handlePreviousPage = () => {
345
1154
  if (query.page > 1) {
346
1155
  setQuery((prev) => ({ ...prev, page: prev.page - 1 }));
@@ -355,140 +1164,6 @@ function RowaKitTable({
355
1164
  const handlePageSizeChange = (newPageSize) => {
356
1165
  setQuery({ ...query, pageSize: newPageSize, page: 1 });
357
1166
  };
358
- const handleSort = (field) => {
359
- setQuery((prev) => {
360
- if (prev.sort?.field !== field) {
361
- return { ...prev, sort: { field, direction: "asc" }, page: 1 };
362
- }
363
- if (prev.sort.direction === "asc") {
364
- return { ...prev, sort: { field, direction: "desc" }, page: 1 };
365
- }
366
- const { sort: _sort, ...rest } = prev;
367
- return { ...rest, page: 1 };
368
- });
369
- };
370
- const getSortIndicator = (field) => {
371
- if (!query.sort || query.sort.field !== field) {
372
- return "";
373
- }
374
- return query.sort.direction === "asc" ? " \u2191" : " \u2193";
375
- };
376
- const scheduleColumnWidthUpdate = (colId, width) => {
377
- resizePendingRef.current = { colId, width };
378
- if (resizeRafRef.current != null) return;
379
- resizeRafRef.current = requestAnimationFrame(() => {
380
- resizeRafRef.current = null;
381
- const pending = resizePendingRef.current;
382
- if (!pending) return;
383
- handleColumnResize(pending.colId, pending.width);
384
- });
385
- };
386
- const handleColumnResize = (columnId, newWidth) => {
387
- const minWidth = columns.find((c) => c.id === columnId)?.minWidth ?? 80;
388
- const maxWidth = columns.find((c) => c.id === columnId)?.maxWidth;
389
- let finalWidth = Math.max(minWidth, newWidth);
390
- if (maxWidth) {
391
- finalWidth = Math.min(finalWidth, maxWidth);
392
- }
393
- setColumnWidths((prev) => ({
394
- ...prev,
395
- [columnId]: finalWidth
396
- }));
397
- };
398
- const startColumnResize = (e, columnId) => {
399
- e.preventDefault();
400
- const startX = e.clientX;
401
- const th = e.currentTarget.parentElement;
402
- let startWidth = columnWidths[columnId] ?? th.offsetWidth;
403
- const MIN_DRAG_WIDTH = 80;
404
- if (startWidth < MIN_DRAG_WIDTH) {
405
- const nextTh = th.nextElementSibling;
406
- if (nextTh && nextTh.offsetWidth >= 50) {
407
- startWidth = nextTh.offsetWidth;
408
- } else {
409
- startWidth = 100;
410
- }
411
- }
412
- document.body.classList.add("rowakit-resizing");
413
- const handleMouseMove = (moveEvent) => {
414
- const delta = moveEvent.clientX - startX;
415
- const newWidth = startWidth + delta;
416
- scheduleColumnWidthUpdate(columnId, newWidth);
417
- };
418
- const handleMouseUp = () => {
419
- document.removeEventListener("mousemove", handleMouseMove);
420
- document.removeEventListener("mouseup", handleMouseUp);
421
- document.body.classList.remove("rowakit-resizing");
422
- };
423
- document.addEventListener("mousemove", handleMouseMove);
424
- document.addEventListener("mouseup", handleMouseUp);
425
- };
426
- const handleColumnResizeDoubleClick = (columnId) => {
427
- const tableEl = tableRef.current;
428
- if (!tableEl) return;
429
- const th = tableEl.querySelector(`th[data-col-id="${columnId}"]`);
430
- if (!th) return;
431
- const tds = Array.from(tableEl.querySelectorAll(`td[data-col-id="${columnId}"]`));
432
- const headerW = th.scrollWidth;
433
- const cellsMaxW = tds.reduce((max, td) => Math.max(max, td.scrollWidth), 0);
434
- const padding = 24;
435
- const raw = Math.max(headerW, cellsMaxW) + padding;
436
- const minW = columns.find((c) => c.id === columnId)?.minWidth ?? 80;
437
- const maxW = columns.find((c) => c.id === columnId)?.maxWidth ?? 600;
438
- const finalW = Math.max(minW, Math.min(raw, maxW));
439
- setColumnWidths((prev) => ({ ...prev, [columnId]: finalW }));
440
- };
441
- const saveCurrentView = (name) => {
442
- const viewState = {
443
- page: query.page,
444
- pageSize: query.pageSize,
445
- sort: query.sort,
446
- filters: query.filters,
447
- columnWidths: enableColumnResizing ? columnWidths : void 0
448
- };
449
- setSavedViews((prev) => {
450
- const filtered = prev.filter((v) => v.name !== name);
451
- return [...filtered, { name, state: viewState }];
452
- });
453
- if (typeof window !== "undefined" && window.localStorage) {
454
- try {
455
- localStorage.setItem(`rowakit-view-${name}`, JSON.stringify(viewState));
456
- } catch {
457
- }
458
- }
459
- };
460
- const loadSavedView = (name) => {
461
- const view = savedViews.find((v) => v.name === name);
462
- if (!view) return;
463
- const { state } = view;
464
- setQuery({
465
- page: state.page,
466
- pageSize: state.pageSize,
467
- sort: state.sort,
468
- filters: state.filters
469
- });
470
- setFilters(state.filters ?? {});
471
- if (state.columnWidths && enableColumnResizing) {
472
- setColumnWidths(state.columnWidths);
473
- }
474
- };
475
- const deleteSavedView = (name) => {
476
- setSavedViews((prev) => prev.filter((v) => v.name !== name));
477
- if (typeof window !== "undefined" && window.localStorage) {
478
- try {
479
- localStorage.removeItem(`rowakit-view-${name}`);
480
- } catch {
481
- }
482
- }
483
- };
484
- const resetTableState = () => {
485
- setQuery({
486
- page: 1,
487
- pageSize: defaultPageSize
488
- });
489
- setFilters({});
490
- setColumnWidths({});
491
- };
492
1167
  const transformFilterValueForColumn = (column, value) => {
493
1168
  if (!value || column?.kind !== "number") {
494
1169
  return value;
@@ -533,29 +1208,100 @@ function RowaKitTable({
533
1208
  const handleClearAllFilters = () => {
534
1209
  setFilters({});
535
1210
  };
536
- const isLoading = dataState.state === "loading";
537
- const isError = dataState.state === "error";
538
- const isEmpty = dataState.state === "empty";
539
1211
  const totalPages = Math.ceil(dataState.total / query.pageSize);
540
1212
  const canGoPrevious = query.page > 1 && !isLoading;
541
1213
  const canGoNext = query.page < totalPages && !isLoading;
1214
+ const tableColumnCount = columns.length + (enableRowSelection ? 1 : 0);
542
1215
  const hasActiveFilters = enableFilters && Object.values(filters).some((v) => v !== void 0);
543
- return /* @__PURE__ */ jsxs("div", { className: `rowakit-table${className ? ` ${className}` : ""}`, children: [
1216
+ const containerClass = [
1217
+ "rowakit-table",
1218
+ enableColumnResizing ? "rowakit-layout-fixed" : "",
1219
+ className
1220
+ ].filter(Boolean).join(" ");
1221
+ return /* @__PURE__ */ jsxs("div", { className: containerClass, children: [
1222
+ enableRowSelection && bulkActions && bulkActions.length > 0 && selectedKeys.length > 0 && /* @__PURE__ */ jsx(
1223
+ BulkActionBar,
1224
+ {
1225
+ selectedCount: selectedKeys.length,
1226
+ actions: bulkActions,
1227
+ onActionClick: (actionId) => {
1228
+ const action = bulkActions.find((a) => a.id === actionId);
1229
+ if (!action) return;
1230
+ const snapshot = [...selectedKeys];
1231
+ if (action.confirm) {
1232
+ setBulkConfirmState({ action, selectedKeys: snapshot });
1233
+ return;
1234
+ }
1235
+ action.onClick(snapshot);
1236
+ }
1237
+ }
1238
+ ),
1239
+ exporter && /* @__PURE__ */ jsx(ExportButton, { exporter, query }),
544
1240
  enableSavedViews && /* @__PURE__ */ jsxs("div", { className: "rowakit-saved-views-group", children: [
545
- /* @__PURE__ */ jsx(
1241
+ !showSaveViewForm ? /* @__PURE__ */ jsx(
546
1242
  "button",
547
1243
  {
548
- onClick: () => {
549
- const name = typeof window !== "undefined" ? window.prompt("Enter view name:") : null;
550
- if (name) {
551
- saveCurrentView(name);
552
- }
553
- },
1244
+ onClick: openSaveViewForm,
554
1245
  className: "rowakit-saved-view-button",
555
1246
  type: "button",
556
1247
  children: "Save View"
557
1248
  }
558
- ),
1249
+ ) : /* @__PURE__ */ jsx("div", { className: "rowakit-save-view-form", children: overwriteConfirmName ? /* @__PURE__ */ jsxs("div", { className: "rowakit-save-view-confirm", children: [
1250
+ /* @__PURE__ */ jsxs("p", { children: [
1251
+ 'View "',
1252
+ overwriteConfirmName,
1253
+ '" already exists. Overwrite?'
1254
+ ] }),
1255
+ /* @__PURE__ */ jsx(
1256
+ "button",
1257
+ {
1258
+ onClick: confirmOverwrite,
1259
+ className: "rowakit-saved-view-button",
1260
+ type: "button",
1261
+ children: "Overwrite"
1262
+ }
1263
+ ),
1264
+ /* @__PURE__ */ jsx(
1265
+ "button",
1266
+ {
1267
+ onClick: cancelOverwrite,
1268
+ className: "rowakit-saved-view-button",
1269
+ type: "button",
1270
+ children: "Cancel"
1271
+ }
1272
+ )
1273
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1274
+ /* @__PURE__ */ jsx(
1275
+ "input",
1276
+ {
1277
+ type: "text",
1278
+ value: saveViewInput,
1279
+ onChange: onSaveViewInputChange,
1280
+ onKeyDown: onSaveViewInputKeyDown,
1281
+ placeholder: "Enter view name...",
1282
+ className: "rowakit-save-view-input"
1283
+ }
1284
+ ),
1285
+ saveViewError && /* @__PURE__ */ jsx("div", { className: "rowakit-save-view-error", children: saveViewError }),
1286
+ /* @__PURE__ */ jsx(
1287
+ "button",
1288
+ {
1289
+ onClick: attemptSave,
1290
+ className: "rowakit-saved-view-button",
1291
+ type: "button",
1292
+ children: "Save"
1293
+ }
1294
+ ),
1295
+ /* @__PURE__ */ jsx(
1296
+ "button",
1297
+ {
1298
+ onClick: cancelSaveViewForm,
1299
+ className: "rowakit-saved-view-button",
1300
+ type: "button",
1301
+ children: "Cancel"
1302
+ }
1303
+ )
1304
+ ] }) }),
559
1305
  savedViews.map((view) => /* @__PURE__ */ jsxs("div", { className: "rowakit-saved-view-item", children: [
560
1306
  /* @__PURE__ */ jsx(
561
1307
  "button",
@@ -598,159 +1344,143 @@ function RowaKitTable({
598
1344
  ) }),
599
1345
  /* @__PURE__ */ jsxs("table", { ref: tableRef, children: [
600
1346
  /* @__PURE__ */ jsxs("thead", { children: [
601
- /* @__PURE__ */ jsx("tr", { children: columns.map((column) => {
602
- const isSortable = column.kind !== "actions" && (column.kind === "custom" ? false : column.sortable === true);
603
- const field = column.kind === "actions" ? "" : column.kind === "custom" ? column.field : column.field;
604
- const isResizable = enableColumnResizing && column.kind !== "actions";
605
- const actualWidth = columnWidths[column.id] ?? column.width;
606
- return /* @__PURE__ */ jsxs(
607
- "th",
1347
+ /* @__PURE__ */ jsxs("tr", { children: [
1348
+ enableRowSelection && /* @__PURE__ */ jsx(
1349
+ RowSelectionHeaderCell,
608
1350
  {
609
- "data-col-id": column.id,
610
- onClick: isSortable ? () => handleSort(String(field)) : void 0,
611
- role: isSortable ? "button" : void 0,
612
- tabIndex: isSortable ? 0 : void 0,
613
- onKeyDown: isSortable ? (e) => {
614
- if (e.key === "Enter" || e.key === " ") {
615
- e.preventDefault();
616
- handleSort(String(field));
1351
+ checked: headerChecked,
1352
+ indeterminate: headerIndeterminate,
1353
+ disabled: isLoading || pageRowKeys.length === 0,
1354
+ onChange: (checked) => {
1355
+ if (checked) {
1356
+ setSelectedKeys(selectAll(pageRowKeys));
1357
+ } else {
1358
+ setSelectedKeys(clearSelection());
617
1359
  }
618
- } : void 0,
619
- "aria-sort": isSortable && query.sort?.field === String(field) ? query.sort.direction === "asc" ? "ascending" : "descending" : void 0,
620
- style: {
621
- width: actualWidth ? `${actualWidth}px` : void 0,
622
- textAlign: column.align,
623
- position: isResizable ? "relative" : void 0
624
- },
625
- className: column.truncate && !isResizable ? "rowakit-cell-truncate" : void 0,
626
- children: [
627
- getHeaderLabel(column),
628
- isSortable && getSortIndicator(String(field)),
629
- isResizable && /* @__PURE__ */ jsx(
630
- "div",
631
- {
632
- className: "rowakit-column-resize-handle",
633
- onMouseDown: (e) => startColumnResize(e, column.id),
634
- onDoubleClick: () => handleColumnResizeDoubleClick(column.id),
635
- title: "Drag to resize | Double-click to auto-fit content"
636
- }
637
- )
638
- ]
639
- },
640
- column.id
641
- );
642
- }) }),
643
- enableFilters && /* @__PURE__ */ jsx("tr", { className: "rowakit-table-filter-row", children: columns.map((column) => {
644
- const field = column.kind === "actions" || column.kind === "custom" ? "" : String(column.field);
645
- const canFilter = field && column.kind !== "actions";
646
- if (!canFilter) {
647
- return /* @__PURE__ */ jsx("th", {}, column.id);
648
- }
649
- const filterValue = filters[field];
650
- if (column.kind === "badge") {
651
- const options = column.map ? Object.keys(column.map) : [];
652
- return /* @__PURE__ */ jsx("th", { children: /* @__PURE__ */ jsxs(
653
- "select",
654
- {
655
- className: "rowakit-filter-select",
656
- value: filterValue?.op === "equals" ? String(filterValue.value ?? "") : "",
657
- onChange: (e) => {
658
- const value = e.target.value;
659
- if (value === "") {
660
- handleClearFilter(field);
661
- } else {
662
- handleFilterChange(field, { op: "equals", value });
663
- }
664
- },
665
- children: [
666
- /* @__PURE__ */ jsx("option", { value: "", children: "All" }),
667
- options.map((opt) => /* @__PURE__ */ jsx("option", { value: opt, children: opt }, opt))
668
- ]
669
1360
  }
670
- ) }, column.id);
671
- }
672
- if (column.kind === "boolean") {
673
- return /* @__PURE__ */ jsx("th", { children: /* @__PURE__ */ jsxs(
674
- "select",
1361
+ }
1362
+ ),
1363
+ columns.map((column) => {
1364
+ const isSortable = column.kind !== "actions" && (column.kind === "custom" ? false : column.sortable === true);
1365
+ const field = column.kind === "actions" ? "" : column.kind === "custom" ? column.field : column.field;
1366
+ const isResizable = enableColumnResizing && column.kind !== "actions";
1367
+ const actualWidth = columnWidths[column.id] ?? column.width;
1368
+ return /* @__PURE__ */ jsxs(
1369
+ "th",
675
1370
  {
676
- className: "rowakit-filter-select",
677
- value: filterValue?.op === "equals" && typeof filterValue.value === "boolean" ? String(filterValue.value) : "",
678
- onChange: (e) => {
679
- const value = e.target.value;
680
- if (value === "") {
681
- handleClearFilter(field);
682
- } else {
683
- handleFilterChange(field, { op: "equals", value: value === "true" });
1371
+ "data-col-id": column.id,
1372
+ onClick: isSortable ? (e) => {
1373
+ if (isResizingRef.current) return;
1374
+ if (Date.now() - lastResizeEndTsRef.current < 150) return;
1375
+ const isMultiSort = e.ctrlKey || e.metaKey;
1376
+ handleSort(String(field), isMultiSort);
1377
+ } : void 0,
1378
+ role: isSortable ? "button" : void 0,
1379
+ tabIndex: isSortable ? 0 : void 0,
1380
+ onKeyDown: isSortable ? (e) => {
1381
+ if (e.key === "Enter" || e.key === " ") {
1382
+ e.preventDefault();
1383
+ const isMultiSort = e.shiftKey;
1384
+ handleSort(String(field), isMultiSort);
684
1385
  }
1386
+ } : void 0,
1387
+ "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,
1388
+ style: {
1389
+ width: actualWidth != null ? `${actualWidth}px` : void 0,
1390
+ textAlign: column.align,
1391
+ position: isResizable ? "relative" : void 0
685
1392
  },
1393
+ className: [
1394
+ column.truncate ? "rowakit-cell-truncate" : "",
1395
+ resizingColIdRef.current === column.id ? "resizing" : ""
1396
+ // PRD-01
1397
+ ].filter(Boolean).join(" ") || void 0,
686
1398
  children: [
687
- /* @__PURE__ */ jsx("option", { value: "", children: "All" }),
688
- /* @__PURE__ */ jsx("option", { value: "true", children: "True" }),
689
- /* @__PURE__ */ jsx("option", { value: "false", children: "False" })
1399
+ getHeaderLabel(column),
1400
+ isSortable && getSortIndicator(String(field)),
1401
+ isResizable && /* @__PURE__ */ jsx(
1402
+ "div",
1403
+ {
1404
+ className: "rowakit-column-resize-handle",
1405
+ onPointerDown: (e) => startColumnResize(e, column.id),
1406
+ onDoubleClick: (e) => handleColumnResizeDoubleClick(e, column.id),
1407
+ title: "Drag to resize | Double-click to auto-fit content"
1408
+ }
1409
+ )
690
1410
  ]
691
- }
692
- ) }, column.id);
693
- }
694
- if (column.kind === "date") {
695
- const fromValue = filterValue?.op === "range" ? filterValue.value.from ?? "" : "";
696
- const toValue = filterValue?.op === "range" ? filterValue.value.to ?? "" : "";
697
- return /* @__PURE__ */ jsx("th", { children: /* @__PURE__ */ jsxs("div", { className: "rowakit-filter-date-range", children: [
698
- /* @__PURE__ */ jsx(
699
- "input",
1411
+ },
1412
+ column.id
1413
+ );
1414
+ })
1415
+ ] }),
1416
+ enableFilters && /* @__PURE__ */ jsxs("tr", { className: "rowakit-table-filter-row", children: [
1417
+ enableRowSelection && /* @__PURE__ */ jsx("th", {}),
1418
+ columns.map((column) => {
1419
+ const field = column.kind === "actions" || column.kind === "custom" ? "" : String(column.field);
1420
+ const canFilter = field && column.kind !== "actions";
1421
+ if (!canFilter) {
1422
+ return /* @__PURE__ */ jsx("th", {}, column.id);
1423
+ }
1424
+ const filterValue = filters[field];
1425
+ if (column.kind === "badge") {
1426
+ const options = column.map ? Object.keys(column.map) : [];
1427
+ return /* @__PURE__ */ jsx("th", { children: /* @__PURE__ */ jsxs(
1428
+ "select",
700
1429
  {
701
- type: "date",
702
- className: "rowakit-filter-input",
703
- placeholder: "From",
704
- value: fromValue,
1430
+ className: "rowakit-filter-select",
1431
+ value: filterValue?.op === "equals" ? String(filterValue.value ?? "") : "",
705
1432
  onChange: (e) => {
706
- const from = e.target.value || void 0;
707
- const to = toValue || void 0;
708
- if (!from && !to) {
1433
+ const value = e.target.value;
1434
+ if (value === "") {
709
1435
  handleClearFilter(field);
710
1436
  } else {
711
- handleFilterChange(field, { op: "range", value: { from, to } });
1437
+ handleFilterChange(field, { op: "equals", value });
712
1438
  }
713
- }
1439
+ },
1440
+ children: [
1441
+ /* @__PURE__ */ jsx("option", { value: "", children: "All" }),
1442
+ options.map((opt) => /* @__PURE__ */ jsx("option", { value: opt, children: opt }, opt))
1443
+ ]
714
1444
  }
715
- ),
716
- /* @__PURE__ */ jsx(
717
- "input",
1445
+ ) }, column.id);
1446
+ }
1447
+ if (column.kind === "boolean") {
1448
+ return /* @__PURE__ */ jsx("th", { children: /* @__PURE__ */ jsxs(
1449
+ "select",
718
1450
  {
719
- type: "date",
720
- className: "rowakit-filter-input",
721
- placeholder: "To",
722
- value: toValue,
1451
+ className: "rowakit-filter-select",
1452
+ value: filterValue?.op === "equals" && typeof filterValue.value === "boolean" ? String(filterValue.value) : "",
723
1453
  onChange: (e) => {
724
- const to = e.target.value || void 0;
725
- const from = fromValue || void 0;
726
- if (!from && !to) {
1454
+ const value = e.target.value;
1455
+ if (value === "") {
727
1456
  handleClearFilter(field);
728
1457
  } else {
729
- handleFilterChange(field, { op: "range", value: { from, to } });
1458
+ handleFilterChange(field, { op: "equals", value: value === "true" });
730
1459
  }
731
- }
1460
+ },
1461
+ children: [
1462
+ /* @__PURE__ */ jsx("option", { value: "", children: "All" }),
1463
+ /* @__PURE__ */ jsx("option", { value: "true", children: "True" }),
1464
+ /* @__PURE__ */ jsx("option", { value: "false", children: "False" })
1465
+ ]
732
1466
  }
733
- )
734
- ] }) }, column.id);
735
- }
736
- const isNumberColumn = column.kind === "number";
737
- if (isNumberColumn) {
738
- const fromValue = filterValue?.op === "range" ? String(filterValue.value.from ?? "") : filterValue?.op === "equals" && typeof filterValue.value === "number" ? String(filterValue.value) : "";
739
- const toValue = filterValue?.op === "range" ? String(filterValue.value.to ?? "") : "";
740
- const showRangeUI = !filterValue || filterValue.op === "range";
741
- if (showRangeUI) {
742
- return /* @__PURE__ */ jsx("th", { children: /* @__PURE__ */ jsxs("div", { className: "rowakit-filter-number-range", children: [
1467
+ ) }, column.id);
1468
+ }
1469
+ if (column.kind === "date") {
1470
+ const fromValue = filterValue?.op === "range" ? filterValue.value.from ?? "" : "";
1471
+ const toValue = filterValue?.op === "range" ? filterValue.value.to ?? "" : "";
1472
+ return /* @__PURE__ */ jsx("th", { children: /* @__PURE__ */ jsxs("div", { className: "rowakit-filter-date-range", children: [
743
1473
  /* @__PURE__ */ jsx(
744
1474
  "input",
745
1475
  {
746
- type: "number",
1476
+ type: "date",
747
1477
  className: "rowakit-filter-input",
748
- placeholder: "Min",
1478
+ placeholder: "From",
749
1479
  value: fromValue,
750
1480
  onChange: (e) => {
751
- const from = e.target.value ? Number(e.target.value) : void 0;
752
- const to = toValue ? Number(toValue) : void 0;
753
- if (from === void 0 && to === void 0) {
1481
+ const from = e.target.value || void 0;
1482
+ const to = toValue || void 0;
1483
+ if (!from && !to) {
754
1484
  handleClearFilter(field);
755
1485
  } else {
756
1486
  handleFilterChange(field, { op: "range", value: { from, to } });
@@ -761,14 +1491,14 @@ function RowaKitTable({
761
1491
  /* @__PURE__ */ jsx(
762
1492
  "input",
763
1493
  {
764
- type: "number",
1494
+ type: "date",
765
1495
  className: "rowakit-filter-input",
766
- placeholder: "Max",
1496
+ placeholder: "To",
767
1497
  value: toValue,
768
1498
  onChange: (e) => {
769
- const to = e.target.value ? Number(e.target.value) : void 0;
770
- const from = fromValue ? Number(fromValue) : void 0;
771
- if (from === void 0 && to === void 0) {
1499
+ const to = e.target.value || void 0;
1500
+ const from = fromValue || void 0;
1501
+ if (!from && !to) {
772
1502
  handleClearFilter(field);
773
1503
  } else {
774
1504
  handleFilterChange(field, { op: "range", value: { from, to } });
@@ -778,39 +1508,85 @@ function RowaKitTable({
778
1508
  )
779
1509
  ] }) }, column.id);
780
1510
  }
781
- }
782
- return /* @__PURE__ */ jsx("th", { children: /* @__PURE__ */ jsx(
783
- "input",
784
- {
785
- type: isNumberColumn ? "number" : "text",
786
- className: "rowakit-filter-input",
787
- placeholder: `Filter ${getHeaderLabel(column)}...`,
788
- 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) : "",
789
- onChange: (e) => {
790
- const rawValue = e.target.value;
791
- if (rawValue === "") {
792
- handleClearFilter(field);
793
- } else if (isNumberColumn) {
794
- const numValue = Number(rawValue);
795
- if (!isNaN(numValue)) {
796
- handleFilterChange(field, { op: "equals", value: numValue });
797
- } else {
1511
+ const isNumberColumn = column.kind === "number";
1512
+ if (isNumberColumn) {
1513
+ const fromValue = filterValue?.op === "range" ? String(filterValue.value.from ?? "") : filterValue?.op === "equals" && typeof filterValue.value === "number" ? String(filterValue.value) : "";
1514
+ const toValue = filterValue?.op === "range" ? String(filterValue.value.to ?? "") : "";
1515
+ const showRangeUI = !filterValue || filterValue.op === "range";
1516
+ if (showRangeUI) {
1517
+ return /* @__PURE__ */ jsx("th", { children: /* @__PURE__ */ jsxs("div", { className: "rowakit-filter-number-range", children: [
1518
+ /* @__PURE__ */ jsx(
1519
+ "input",
1520
+ {
1521
+ type: "number",
1522
+ className: "rowakit-filter-input",
1523
+ placeholder: "Min",
1524
+ value: fromValue,
1525
+ onChange: (e) => {
1526
+ const from = e.target.value ? Number(e.target.value) : void 0;
1527
+ const to = toValue ? Number(toValue) : void 0;
1528
+ if (from === void 0 && to === void 0) {
1529
+ handleClearFilter(field);
1530
+ } else {
1531
+ handleFilterChange(field, { op: "range", value: { from, to } });
1532
+ }
1533
+ }
1534
+ }
1535
+ ),
1536
+ /* @__PURE__ */ jsx(
1537
+ "input",
1538
+ {
1539
+ type: "number",
1540
+ className: "rowakit-filter-input",
1541
+ placeholder: "Max",
1542
+ value: toValue,
1543
+ onChange: (e) => {
1544
+ const to = e.target.value ? Number(e.target.value) : void 0;
1545
+ const from = fromValue ? Number(fromValue) : void 0;
1546
+ if (from === void 0 && to === void 0) {
1547
+ handleClearFilter(field);
1548
+ } else {
1549
+ handleFilterChange(field, { op: "range", value: { from, to } });
1550
+ }
1551
+ }
1552
+ }
1553
+ )
1554
+ ] }) }, column.id);
1555
+ }
1556
+ }
1557
+ return /* @__PURE__ */ jsx("th", { children: /* @__PURE__ */ jsx(
1558
+ "input",
1559
+ {
1560
+ type: isNumberColumn ? "number" : "text",
1561
+ className: "rowakit-filter-input",
1562
+ placeholder: `Filter ${getHeaderLabel(column)}...`,
1563
+ 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) : "",
1564
+ onChange: (e) => {
1565
+ const rawValue = e.target.value;
1566
+ if (rawValue === "") {
798
1567
  handleClearFilter(field);
1568
+ } else if (isNumberColumn) {
1569
+ const numValue = Number(rawValue);
1570
+ if (!isNaN(numValue)) {
1571
+ handleFilterChange(field, { op: "equals", value: numValue });
1572
+ } else {
1573
+ handleClearFilter(field);
1574
+ }
1575
+ } else {
1576
+ handleFilterChange(field, { op: "contains", value: rawValue });
799
1577
  }
800
- } else {
801
- handleFilterChange(field, { op: "contains", value: rawValue });
802
1578
  }
803
1579
  }
804
- }
805
- ) }, column.id);
806
- }) })
1580
+ ) }, column.id);
1581
+ })
1582
+ ] })
807
1583
  ] }),
808
1584
  /* @__PURE__ */ jsxs("tbody", { children: [
809
- isLoading && /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsxs("td", { colSpan: columns.length, className: "rowakit-table-loading", children: [
1585
+ isLoading && /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsxs("td", { colSpan: tableColumnCount, className: "rowakit-table-loading", children: [
810
1586
  /* @__PURE__ */ jsx("div", { className: "rowakit-table-loading-spinner" }),
811
1587
  /* @__PURE__ */ jsx("span", { children: "Loading..." })
812
1588
  ] }) }),
813
- isError && /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsxs("td", { colSpan: columns.length, className: "rowakit-table-error", children: [
1589
+ isError && /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsxs("td", { colSpan: tableColumnCount, className: "rowakit-table-error", children: [
814
1590
  /* @__PURE__ */ jsx("div", { className: "rowakit-table-error-message", children: dataState.error ?? "An error occurred" }),
815
1591
  /* @__PURE__ */ jsx(
816
1592
  "button",
@@ -822,29 +1598,42 @@ function RowaKitTable({
822
1598
  }
823
1599
  )
824
1600
  ] }) }),
825
- isEmpty && /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", { colSpan: columns.length, className: "rowakit-table-empty", children: "No data" }) }),
1601
+ isEmpty && /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", { colSpan: tableColumnCount, className: "rowakit-table-empty", children: "No data" }) }),
826
1602
  dataState.state === "success" && dataState.items.map((row) => {
827
1603
  const key = getRowKey(row, rowKey);
828
- return /* @__PURE__ */ jsx("tr", { children: columns.map((column) => {
829
- const cellClass = [
830
- column.kind === "number" ? "rowakit-cell-number" : "",
831
- column.truncate ? "rowakit-cell-truncate" : ""
832
- ].filter(Boolean).join(" ") || void 0;
833
- const actualWidth = columnWidths[column.id];
834
- return /* @__PURE__ */ jsx(
835
- "td",
1604
+ return /* @__PURE__ */ jsxs("tr", { children: [
1605
+ enableRowSelection && /* @__PURE__ */ jsx(
1606
+ RowSelectionCell,
836
1607
  {
837
- "data-col-id": column.id,
838
- className: cellClass,
839
- style: {
840
- width: actualWidth ? `${actualWidth}px` : void 0,
841
- textAlign: column.align || (column.kind === "number" ? "right" : void 0)
1608
+ rowKey: key,
1609
+ disabled: isLoading,
1610
+ checked: selectedKeys.includes(key),
1611
+ onChange: () => {
1612
+ setSelectedKeys((prev) => toggleSelectionKey(prev, key));
1613
+ }
1614
+ }
1615
+ ),
1616
+ columns.map((column) => {
1617
+ const cellClass = [
1618
+ column.kind === "number" ? "rowakit-cell-number" : "",
1619
+ column.truncate ? "rowakit-cell-truncate" : ""
1620
+ ].filter(Boolean).join(" ") || void 0;
1621
+ const actualWidth = columnWidths[column.id] ?? column.width;
1622
+ return /* @__PURE__ */ jsx(
1623
+ "td",
1624
+ {
1625
+ "data-col-id": column.id,
1626
+ className: cellClass,
1627
+ style: {
1628
+ width: actualWidth != null ? `${actualWidth}px` : void 0,
1629
+ textAlign: column.align || (column.kind === "number" ? "right" : void 0)
1630
+ },
1631
+ children: renderCell(column, row, isLoading, setConfirmState)
842
1632
  },
843
- children: renderCell(column, row, isLoading, setConfirmState)
844
- },
845
- column.id
846
- );
847
- }) }, key);
1633
+ column.id
1634
+ );
1635
+ })
1636
+ ] }, key);
848
1637
  })
849
1638
  ] })
850
1639
  ] }),
@@ -899,6 +1688,7 @@ function RowaKitTable({
899
1688
  confirmState && /* @__PURE__ */ jsx(
900
1689
  "div",
901
1690
  {
1691
+ ref: confirmModalRef,
902
1692
  className: "rowakit-modal-backdrop",
903
1693
  onClick: () => setConfirmState(null),
904
1694
  role: "dialog",
@@ -936,13 +1726,51 @@ function RowaKitTable({
936
1726
  ] })
937
1727
  ] })
938
1728
  }
1729
+ ),
1730
+ bulkConfirmState && /* @__PURE__ */ jsx(
1731
+ "div",
1732
+ {
1733
+ ref: bulkConfirmModalRef,
1734
+ className: "rowakit-modal-backdrop",
1735
+ onClick: () => setBulkConfirmState(null),
1736
+ role: "dialog",
1737
+ "aria-modal": "true",
1738
+ "aria-labelledby": "bulk-confirm-dialog-title",
1739
+ children: /* @__PURE__ */ jsxs("div", { className: "rowakit-modal", onClick: (e) => e.stopPropagation(), children: [
1740
+ /* @__PURE__ */ jsx("h2", { id: "bulk-confirm-dialog-title", className: "rowakit-modal-title", children: bulkConfirmState.action.confirm?.title ?? "Confirm Action" }),
1741
+ /* @__PURE__ */ jsx("p", { className: "rowakit-modal-content", children: bulkConfirmState.action.confirm?.description ?? "Are you sure you want to perform this action? This action cannot be undone." }),
1742
+ /* @__PURE__ */ jsxs("div", { className: "rowakit-modal-actions", children: [
1743
+ /* @__PURE__ */ jsx(
1744
+ "button",
1745
+ {
1746
+ onClick: () => setBulkConfirmState(null),
1747
+ className: "rowakit-button rowakit-button-secondary",
1748
+ type: "button",
1749
+ children: "Cancel"
1750
+ }
1751
+ ),
1752
+ /* @__PURE__ */ jsx(
1753
+ "button",
1754
+ {
1755
+ onClick: () => {
1756
+ bulkConfirmState.action.onClick(bulkConfirmState.selectedKeys);
1757
+ setBulkConfirmState(null);
1758
+ },
1759
+ className: "rowakit-button rowakit-button-danger",
1760
+ type: "button",
1761
+ children: "Confirm"
1762
+ }
1763
+ )
1764
+ ] })
1765
+ ] })
1766
+ }
939
1767
  )
940
1768
  ] });
941
1769
  }
942
1770
  var SmartTable = RowaKitTable;
943
1771
 
944
1772
  // src/index.ts
945
- var VERSION = "0.1.0";
1773
+ var VERSION = "0.5.0" ;
946
1774
 
947
1775
  export { RowaKitTable, SmartTable, VERSION, col };
948
1776
  //# sourceMappingURL=index.js.map