@roy-ui/ui 0.0.7 → 0.0.8

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.
Files changed (68) hide show
  1. package/dist/{Button-XBBWB5ZT.css → Button-OZLAH5NO.css} +60 -13
  2. package/dist/Table-qVdGZkB4.d.ts +42 -0
  3. package/dist/TimePicker-BhRta4MK.d.ts +39 -0
  4. package/dist/chunk-4SGMAZBG.js +161 -0
  5. package/dist/chunk-4SGMAZBG.js.map +1 -0
  6. package/dist/chunk-5CIBIH7R.js +98 -0
  7. package/dist/chunk-5CIBIH7R.js.map +1 -0
  8. package/dist/chunk-75IGGPXL.js +518 -0
  9. package/dist/chunk-75IGGPXL.js.map +1 -0
  10. package/dist/chunk-C5X3TE5U.js +87 -0
  11. package/dist/chunk-C5X3TE5U.js.map +1 -0
  12. package/dist/chunk-HUCK7AM7.js +840 -0
  13. package/dist/chunk-HUCK7AM7.js.map +1 -0
  14. package/dist/chunk-KSHKVSNK.js +82 -0
  15. package/dist/chunk-KSHKVSNK.js.map +1 -0
  16. package/dist/chunk-M6HB6BMA.js +101 -0
  17. package/dist/chunk-M6HB6BMA.js.map +1 -0
  18. package/dist/chunk-MDPMEW4K.js +58 -0
  19. package/dist/chunk-MDPMEW4K.js.map +1 -0
  20. package/dist/chunk-PGV55XSZ.js +107 -0
  21. package/dist/chunk-PGV55XSZ.js.map +1 -0
  22. package/dist/chunk-RLBVY3DG.js +64 -0
  23. package/dist/chunk-RLBVY3DG.js.map +1 -0
  24. package/dist/chunk-SFENGB5N.js +410 -0
  25. package/dist/chunk-SFENGB5N.js.map +1 -0
  26. package/dist/chunk-XERZVDIT.js +194 -0
  27. package/dist/chunk-XERZVDIT.js.map +1 -0
  28. package/dist/components/button/index.d.ts +37 -0
  29. package/dist/components/button/index.js +4 -0
  30. package/dist/components/button/index.js.map +1 -0
  31. package/dist/components/data-table/index.d.ts +145 -0
  32. package/dist/components/data-table/index.js +9 -0
  33. package/dist/components/data-table/index.js.map +1 -0
  34. package/dist/components/date-range-picker/index.d.ts +30 -0
  35. package/dist/components/date-range-picker/index.js +4 -0
  36. package/dist/components/date-range-picker/index.js.map +1 -0
  37. package/dist/components/gradient-button/index.d.ts +12 -0
  38. package/dist/components/gradient-button/index.js +4 -0
  39. package/dist/components/gradient-button/index.js.map +1 -0
  40. package/dist/components/made-by/index.d.ts +23 -0
  41. package/dist/components/made-by/index.js +4 -0
  42. package/dist/components/made-by/index.js.map +1 -0
  43. package/dist/components/pagination/index.d.ts +23 -0
  44. package/dist/components/pagination/index.js +4 -0
  45. package/dist/components/pagination/index.js.map +1 -0
  46. package/dist/components/popover/index.d.ts +18 -0
  47. package/dist/components/popover/index.js +4 -0
  48. package/dist/components/popover/index.js.map +1 -0
  49. package/dist/components/table/index.d.ts +28 -0
  50. package/dist/components/table/index.js +4 -0
  51. package/dist/components/table/index.js.map +1 -0
  52. package/dist/components/table-search/index.d.ts +19 -0
  53. package/dist/components/table-search/index.js +4 -0
  54. package/dist/components/table-search/index.js.map +1 -0
  55. package/dist/components/text-morph/index.d.ts +28 -0
  56. package/dist/components/text-morph/index.js +4 -0
  57. package/dist/components/text-morph/index.js.map +1 -0
  58. package/dist/components/time-picker/index.d.ts +14 -0
  59. package/dist/components/time-picker/index.js +4 -0
  60. package/dist/components/time-picker/index.js.map +1 -0
  61. package/dist/components/tree-nav/index.d.ts +30 -0
  62. package/dist/components/tree-nav/index.js +4 -0
  63. package/dist/components/tree-nav/index.js.map +1 -0
  64. package/dist/dateUtils-B_m_EICl.d.ts +14 -0
  65. package/dist/index.d.ts +17 -422
  66. package/dist/index.js +12 -2519
  67. package/dist/index.js.map +1 -1
  68. package/package.json +51 -2
@@ -0,0 +1,840 @@
1
+ "use client";
2
+ import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from './chunk-XERZVDIT.js';
3
+ import { TableSearch } from './chunk-KSHKVSNK.js';
4
+ import { TimePicker } from './chunk-75IGGPXL.js';
5
+ import { DateRangePicker, startOfDay, isBetween } from './chunk-SFENGB5N.js';
6
+ import { Pagination } from './chunk-5CIBIH7R.js';
7
+ import { useState, useRef, useEffect, useMemo, useCallback } from 'react';
8
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
9
+ import './DataTable-TQ5OBNZF.css';
10
+
11
+ function ColumnMenu({
12
+ columns,
13
+ layout,
14
+ onToggle,
15
+ onReset
16
+ }) {
17
+ const [open, setOpen] = useState(false);
18
+ const wrap = useRef(null);
19
+ useEffect(() => {
20
+ if (!open) return;
21
+ function onDown(e) {
22
+ if (wrap.current && !wrap.current.contains(e.target)) {
23
+ setOpen(false);
24
+ }
25
+ }
26
+ function onKey(e) {
27
+ if (e.key === "Escape") setOpen(false);
28
+ }
29
+ document.addEventListener("mousedown", onDown);
30
+ document.addEventListener("keydown", onKey);
31
+ return () => {
32
+ document.removeEventListener("mousedown", onDown);
33
+ document.removeEventListener("keydown", onKey);
34
+ };
35
+ }, [open]);
36
+ const hiddenCount = layout.hidden.length;
37
+ return /* @__PURE__ */ jsxs("div", { className: "royui-dt-colmenu", ref: wrap, children: [
38
+ /* @__PURE__ */ jsx(
39
+ "button",
40
+ {
41
+ type: "button",
42
+ className: "royui-dt-colmenu__trigger",
43
+ onClick: () => setOpen((o) => !o),
44
+ "aria-expanded": open,
45
+ "aria-haspopup": "menu",
46
+ children: "Columns"
47
+ }
48
+ ),
49
+ hiddenCount > 0 && /* @__PURE__ */ jsxs(
50
+ "button",
51
+ {
52
+ type: "button",
53
+ className: "royui-dt-colmenu__chip",
54
+ onClick: () => setOpen(true),
55
+ children: [
56
+ hiddenCount,
57
+ " hidden"
58
+ ]
59
+ }
60
+ ),
61
+ open && /* @__PURE__ */ jsxs("div", { className: "royui-dt-colmenu__panel", role: "menu", children: [
62
+ /* @__PURE__ */ jsxs("div", { className: "royui-dt-colmenu__head", children: [
63
+ /* @__PURE__ */ jsx("span", { className: "royui-dt-colmenu__title", children: "Columns" }),
64
+ /* @__PURE__ */ jsx(
65
+ "button",
66
+ {
67
+ type: "button",
68
+ className: "royui-dt-colmenu__reset",
69
+ onClick: () => {
70
+ onReset();
71
+ },
72
+ children: "Reset"
73
+ }
74
+ )
75
+ ] }),
76
+ /* @__PURE__ */ jsx("ul", { className: "royui-dt-colmenu__list", children: columns.map((c) => {
77
+ const isHidden = layout.hidden.includes(c.key);
78
+ const disabled = c.hideable === false;
79
+ return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
80
+ "button",
81
+ {
82
+ type: "button",
83
+ role: "menuitemcheckbox",
84
+ "aria-checked": !isHidden,
85
+ className: [
86
+ "royui-dt-colmenu__row",
87
+ isHidden && "royui-dt-colmenu__row--off",
88
+ disabled && "royui-dt-colmenu__row--locked"
89
+ ].filter(Boolean).join(" "),
90
+ onClick: () => !disabled && onToggle(c.key),
91
+ disabled,
92
+ children: [
93
+ /* @__PURE__ */ jsx(
94
+ "span",
95
+ {
96
+ className: [
97
+ "royui-dt-colmenu__dot",
98
+ !isHidden && "royui-dt-colmenu__dot--on"
99
+ ].filter(Boolean).join(" "),
100
+ "aria-hidden": true
101
+ }
102
+ ),
103
+ /* @__PURE__ */ jsx("span", { className: "royui-dt-colmenu__label", children: c.header })
104
+ ]
105
+ }
106
+ ) }, c.key);
107
+ }) })
108
+ ] })
109
+ ] });
110
+ }
111
+ function defaultLayout(columns) {
112
+ return {
113
+ order: columns.map((c) => c.key),
114
+ sizes: columns.reduce((acc, c) => {
115
+ if (c.defaultWidth != null) acc[c.key] = c.defaultWidth;
116
+ return acc;
117
+ }, {}),
118
+ hidden: columns.filter((c) => c.defaultHidden).map((c) => c.key)
119
+ };
120
+ }
121
+ function loadLayout(key) {
122
+ if (!key || typeof window === "undefined") return null;
123
+ try {
124
+ const raw = window.localStorage.getItem(key);
125
+ if (!raw) return null;
126
+ return JSON.parse(raw);
127
+ } catch {
128
+ return null;
129
+ }
130
+ }
131
+ function saveLayout(key, layout) {
132
+ if (!key || typeof window === "undefined") return;
133
+ try {
134
+ window.localStorage.setItem(key, JSON.stringify(layout));
135
+ } catch {
136
+ }
137
+ }
138
+ function useTableLayout(columns, storageKey) {
139
+ const initial = useMemo(() => {
140
+ return loadLayout(storageKey) ?? defaultLayout(columns);
141
+ }, []);
142
+ const [layout, setLayout] = useState(initial);
143
+ useEffect(() => {
144
+ setLayout((prev) => {
145
+ const known = new Set(columns.map((c) => c.key));
146
+ const order = prev.order.filter((k) => known.has(k));
147
+ columns.forEach((c) => {
148
+ if (!order.includes(c.key)) order.push(c.key);
149
+ });
150
+ const sizes = {};
151
+ Object.entries(prev.sizes).forEach(([k, v]) => {
152
+ if (known.has(k)) sizes[k] = v;
153
+ });
154
+ const hidden = prev.hidden.filter((k) => known.has(k));
155
+ return { order, sizes, hidden };
156
+ });
157
+ }, [columns]);
158
+ useEffect(() => {
159
+ saveLayout(storageKey, layout);
160
+ }, [layout, storageKey]);
161
+ const orderedColumns = useMemo(() => {
162
+ const pinnedLeft = columns.filter((c) => c.pinned === "left");
163
+ const pinnedRight = columns.filter((c) => c.pinned === "right");
164
+ const pinnedKeys = new Set(
165
+ [...pinnedLeft, ...pinnedRight].map((c) => c.key)
166
+ );
167
+ const rest = layout.order.filter((k) => !pinnedKeys.has(k)).map((k) => columns.find((c) => c.key === k)).filter(Boolean);
168
+ return [...pinnedLeft, ...rest, ...pinnedRight];
169
+ }, [columns, layout.order]);
170
+ const visibleColumns = useMemo(
171
+ () => orderedColumns.filter((c) => !layout.hidden.includes(c.key)),
172
+ [orderedColumns, layout.hidden]
173
+ );
174
+ const reorder = useCallback((key, toIndex) => {
175
+ setLayout((prev) => {
176
+ const order = [...prev.order];
177
+ const from = order.indexOf(key);
178
+ if (from === -1 || from === toIndex) return prev;
179
+ const item = order.splice(from, 1)[0];
180
+ if (item === void 0) return prev;
181
+ const insertAt = toIndex > from ? toIndex - 1 : toIndex;
182
+ order.splice(Math.max(0, Math.min(insertAt, order.length)), 0, item);
183
+ return { ...prev, order };
184
+ });
185
+ }, []);
186
+ const resize = useCallback((key, px) => {
187
+ setLayout((prev) => {
188
+ const sizes = { ...prev.sizes };
189
+ if (px == null) delete sizes[key];
190
+ else sizes[key] = Math.max(40, Math.round(px));
191
+ return { ...prev, sizes };
192
+ });
193
+ }, []);
194
+ const toggleHidden = useCallback((key) => {
195
+ setLayout((prev) => {
196
+ const hidden = prev.hidden.includes(key) ? prev.hidden.filter((k) => k !== key) : [...prev.hidden, key];
197
+ return { ...prev, hidden };
198
+ });
199
+ }, []);
200
+ const reset = useCallback(() => {
201
+ setLayout(defaultLayout(columns));
202
+ }, [columns]);
203
+ return {
204
+ layout,
205
+ orderedColumns,
206
+ visibleColumns,
207
+ reorder,
208
+ resize,
209
+ toggleHidden,
210
+ reset
211
+ };
212
+ }
213
+
214
+ // src/components/data-table/filters.ts
215
+ function defaultSearchPredicate(row, query, columns) {
216
+ if (!query) return true;
217
+ const q = query.toLowerCase();
218
+ return columns.some((c) => {
219
+ const v = c.accessor(row);
220
+ if (v == null) return false;
221
+ const s = v instanceof Date ? v.toLocaleString() : String(v);
222
+ return s.toLowerCase().includes(q);
223
+ });
224
+ }
225
+ function applyFilters(rows, columns, filters, cfg) {
226
+ const searchFn = cfg.searchPredicate ?? defaultSearchPredicate;
227
+ return rows.filter((row) => {
228
+ if (filters.search && !searchFn(row, filters.search, columns)) return false;
229
+ if (cfg.dateColumn && (filters.dateRange.from || filters.dateRange.to)) {
230
+ const col = columns.find((c) => c.key === cfg.dateColumn);
231
+ if (col) {
232
+ const raw = col.accessor(row);
233
+ const d = raw instanceof Date ? raw : raw ? new Date(raw) : null;
234
+ if (!d || isNaN(d.getTime())) return false;
235
+ const day = startOfDay(d);
236
+ const from = filters.dateRange.from ?? day;
237
+ const to = filters.dateRange.to ?? filters.dateRange.from ?? day;
238
+ if (!isBetween(day, startOfDay(from), startOfDay(to))) return false;
239
+ }
240
+ }
241
+ if (cfg.timeColumn && filters.time) {
242
+ const col = columns.find((c) => c.key === cfg.timeColumn);
243
+ if (col) {
244
+ const raw = col.accessor(row);
245
+ const d = raw instanceof Date ? raw : raw ? new Date(raw) : null;
246
+ if (!d || isNaN(d.getTime())) return false;
247
+ const rowMin = d.getHours() * 60 + d.getMinutes();
248
+ const filterMin = filters.time.hours * 60 + filters.time.minutes;
249
+ const tol = cfg.timeTolerance ?? 0;
250
+ if (Math.abs(rowMin - filterMin) > tol) return false;
251
+ }
252
+ }
253
+ return true;
254
+ });
255
+ }
256
+ function applySort(rows, columns, sort) {
257
+ if (!sort || !sort.dir) return rows;
258
+ const col = columns.find((c) => c.key === sort.key);
259
+ if (!col) return rows;
260
+ const dirMul = sort.dir === "asc" ? 1 : -1;
261
+ const getter = col.sortBy ?? col.accessor;
262
+ return [...rows].sort((a, b) => {
263
+ const av = getter(a);
264
+ const bv = getter(b);
265
+ if (av == null && bv == null) return 0;
266
+ if (av == null) return 1;
267
+ if (bv == null) return -1;
268
+ if (av instanceof Date && bv instanceof Date) {
269
+ return (av.getTime() - bv.getTime()) * dirMul;
270
+ }
271
+ if (typeof av === "number" && typeof bv === "number") {
272
+ return (av - bv) * dirMul;
273
+ }
274
+ return String(av).localeCompare(String(bv)) * dirMul;
275
+ });
276
+ }
277
+ function paginate(rows, page, pageSize) {
278
+ const start = (page - 1) * pageSize;
279
+ return rows.slice(start, start + pageSize);
280
+ }
281
+
282
+ // src/components/data-table/io.ts
283
+ function csvEscape(v) {
284
+ if (v == null) return "";
285
+ const s = typeof v === "string" ? v : v instanceof Date ? v.toISOString() : String(v);
286
+ if (/[",\n\r]/.test(s)) {
287
+ return `"${s.replace(/"/g, '""')}"`;
288
+ }
289
+ return s;
290
+ }
291
+ function toCsv(rows, cols) {
292
+ const visible = cols.filter((c) => !c.defaultHidden);
293
+ const header = visible.map((c) => csvEscape(c.header)).join(",");
294
+ const body = rows.map(
295
+ (row) => visible.map((c) => csvEscape(c.accessor(row))).join(",")
296
+ ).join("\n");
297
+ return body ? `${header}
298
+ ${body}` : header;
299
+ }
300
+ function fromCsv(text) {
301
+ const rows = [];
302
+ let cur = [];
303
+ let field = "";
304
+ let i = 0;
305
+ let inQuotes = false;
306
+ const len = text.length;
307
+ while (i < len) {
308
+ const ch = text[i];
309
+ if (inQuotes) {
310
+ if (ch === '"') {
311
+ if (text[i + 1] === '"') {
312
+ field += '"';
313
+ i += 2;
314
+ continue;
315
+ }
316
+ inQuotes = false;
317
+ i++;
318
+ continue;
319
+ }
320
+ field += ch;
321
+ i++;
322
+ continue;
323
+ }
324
+ if (ch === '"') {
325
+ inQuotes = true;
326
+ i++;
327
+ continue;
328
+ }
329
+ if (ch === ",") {
330
+ cur.push(field);
331
+ field = "";
332
+ i++;
333
+ continue;
334
+ }
335
+ if (ch === "\r") {
336
+ i++;
337
+ continue;
338
+ }
339
+ if (ch === "\n") {
340
+ cur.push(field);
341
+ rows.push(cur);
342
+ cur = [];
343
+ field = "";
344
+ i++;
345
+ continue;
346
+ }
347
+ field += ch;
348
+ i++;
349
+ }
350
+ if (field.length > 0 || cur.length > 0) {
351
+ cur.push(field);
352
+ rows.push(cur);
353
+ }
354
+ if (rows.length === 0) return [];
355
+ const head = rows[0] ?? [];
356
+ const body = rows.slice(1);
357
+ return body.map((r) => {
358
+ const obj = {};
359
+ head.forEach((h, idx) => {
360
+ obj[h] = r[idx] ?? "";
361
+ });
362
+ return obj;
363
+ });
364
+ }
365
+ function toJson(rows, cols) {
366
+ const visible = cols.filter((c) => !c.defaultHidden);
367
+ const out = rows.map((row) => {
368
+ const obj = {};
369
+ visible.forEach((c) => {
370
+ obj[c.key] = c.accessor(row);
371
+ });
372
+ return obj;
373
+ });
374
+ return JSON.stringify(out, null, 2);
375
+ }
376
+ function fromJson(text) {
377
+ const parsed = JSON.parse(text);
378
+ if (!Array.isArray(parsed)) {
379
+ throw new Error("Expected a JSON array of rows");
380
+ }
381
+ return parsed;
382
+ }
383
+ function downloadString(text, filename, mime) {
384
+ if (typeof window === "undefined") return;
385
+ const blob = new Blob([text], { type: mime });
386
+ const url = URL.createObjectURL(blob);
387
+ const a = document.createElement("a");
388
+ a.href = url;
389
+ a.download = filename;
390
+ document.body.appendChild(a);
391
+ a.click();
392
+ a.remove();
393
+ setTimeout(() => URL.revokeObjectURL(url), 1e3);
394
+ }
395
+ function fontStyleFor(spec) {
396
+ if (!spec) return void 0;
397
+ if (typeof spec === "string") return { fontFamily: spec };
398
+ const s = {};
399
+ if (spec.family) s.fontFamily = spec.family;
400
+ if (spec.size != null)
401
+ s.fontSize = typeof spec.size === "number" ? `${spec.size}px` : spec.size;
402
+ if (spec.weight != null) s.fontWeight = spec.weight;
403
+ if (spec.letterSpacing) s.letterSpacing = spec.letterSpacing;
404
+ if (spec.featureSettings) s.fontFeatureSettings = spec.featureSettings;
405
+ return s;
406
+ }
407
+ function renderCellValue(value, type) {
408
+ if (value == null) return "";
409
+ if (value instanceof Date) {
410
+ if (type === "time") {
411
+ return value.toLocaleTimeString([], {
412
+ hour: "2-digit",
413
+ minute: "2-digit"
414
+ });
415
+ }
416
+ if (type === "date") {
417
+ return value.toLocaleDateString();
418
+ }
419
+ return value.toLocaleString();
420
+ }
421
+ return String(value);
422
+ }
423
+ function DataTable({
424
+ data,
425
+ columns,
426
+ getRowId,
427
+ visibleRows = 7,
428
+ rowHeight = 44,
429
+ stickyHeader = true,
430
+ density = "cozy",
431
+ loading,
432
+ empty,
433
+ fitColumns = false,
434
+ search,
435
+ dateFilter,
436
+ timeFilter,
437
+ pagination,
438
+ reorderable = true,
439
+ resizable = true,
440
+ columnMenu = true,
441
+ dataIO,
442
+ headerFont,
443
+ rowHeaderFont,
444
+ cellFont,
445
+ storageKey,
446
+ className = "",
447
+ toolbarExtras
448
+ }) {
449
+ const {
450
+ layout,
451
+ orderedColumns,
452
+ visibleColumns,
453
+ reorder,
454
+ resize,
455
+ toggleHidden,
456
+ reset
457
+ } = useTableLayout(columns, storageKey);
458
+ const [filters, setFilters] = useState({
459
+ search: "",
460
+ dateRange: { from: null, to: null },
461
+ time: null
462
+ });
463
+ const [sort, setSort] = useState(null);
464
+ const pageSize = pagination === false ? Infinity : pagination?.pageSize ?? 25;
465
+ const [page, setPage] = useState(1);
466
+ const filtered = useMemo(
467
+ () => applyFilters(data, columns, filters, {
468
+ dateColumn: dateFilter?.column,
469
+ timeColumn: timeFilter?.column,
470
+ timeTolerance: timeFilter?.toleranceMinutes,
471
+ searchPredicate: search?.predicate
472
+ }),
473
+ [data, columns, filters, dateFilter?.column, timeFilter?.column, timeFilter?.toleranceMinutes, search?.predicate]
474
+ );
475
+ const sorted = useMemo(() => applySort(filtered, columns, sort), [filtered, columns, sort]);
476
+ const pageCount = pagination === false ? 1 : Math.max(1, Math.ceil(sorted.length / pageSize));
477
+ const currentPage = Math.min(page, pageCount);
478
+ const pageRows = useMemo(
479
+ () => pagination === false ? sorted : paginate(sorted, currentPage, pageSize),
480
+ [pagination, sorted, currentPage, pageSize]
481
+ );
482
+ const [dragKey, setDragKey] = useState(null);
483
+ const [dropIndex, setDropIndex] = useState(null);
484
+ const onDragStart = (e, key) => {
485
+ if (!reorderable) return;
486
+ setDragKey(key);
487
+ e.dataTransfer.effectAllowed = "move";
488
+ try {
489
+ e.dataTransfer.setData("text/plain", key);
490
+ } catch {
491
+ }
492
+ };
493
+ const onDragOver = (e, idx) => {
494
+ if (!reorderable || !dragKey) return;
495
+ e.preventDefault();
496
+ e.dataTransfer.dropEffect = "move";
497
+ const rect = e.currentTarget.getBoundingClientRect();
498
+ const isRight = e.clientX - rect.left > rect.width / 2;
499
+ setDropIndex(idx + (isRight ? 1 : 0));
500
+ };
501
+ const onDrop = (e) => {
502
+ if (!reorderable || !dragKey || dropIndex == null) return;
503
+ e.preventDefault();
504
+ reorder(dragKey, dropIndex);
505
+ setDragKey(null);
506
+ setDropIndex(null);
507
+ };
508
+ const onDragEnd = () => {
509
+ setDragKey(null);
510
+ setDropIndex(null);
511
+ };
512
+ const resizingKey = useRef(null);
513
+ const resizeStartX = useRef(0);
514
+ const resizeStartW = useRef(0);
515
+ const beginResize = (e, key) => {
516
+ if (!resizable) return;
517
+ e.stopPropagation();
518
+ e.preventDefault();
519
+ const th = e.currentTarget.parentElement ?? null;
520
+ const startW = th ? th.getBoundingClientRect().width : 120;
521
+ resizingKey.current = key;
522
+ resizeStartX.current = e.clientX;
523
+ resizeStartW.current = startW;
524
+ window.addEventListener("pointermove", onResizeMove);
525
+ window.addEventListener("pointerup", endResize);
526
+ };
527
+ const onResizeMove = (e) => {
528
+ if (!resizingKey.current) return;
529
+ const dx = e.clientX - resizeStartX.current;
530
+ const col = columns.find((c) => c.key === resizingKey.current);
531
+ const min = col?.minWidth ?? 80;
532
+ const max = col?.maxWidth ?? 2e3;
533
+ const next = Math.max(min, Math.min(max, resizeStartW.current + dx));
534
+ resize(resizingKey.current, next);
535
+ };
536
+ const endResize = () => {
537
+ resizingKey.current = null;
538
+ window.removeEventListener("pointermove", onResizeMove);
539
+ window.removeEventListener("pointerup", endResize);
540
+ };
541
+ const doubleClickReset = (key) => resize(key, null);
542
+ const cycleSort = useCallback((key) => {
543
+ setSort((prev) => {
544
+ if (!prev || prev.key !== key) return { key, dir: "asc" };
545
+ if (prev.dir === "asc") return { key, dir: "desc" };
546
+ return null;
547
+ });
548
+ }, []);
549
+ const fileInput = useRef(null);
550
+ const [ioFlash, setIoFlash] = useState(null);
551
+ const flashTimer = useRef(null);
552
+ const flash = (text, ms = 1400) => {
553
+ if (flashTimer.current) clearTimeout(flashTimer.current);
554
+ setIoFlash(text);
555
+ flashTimer.current = setTimeout(() => setIoFlash(null), ms);
556
+ };
557
+ const exportRows = (format) => {
558
+ if (!dataIO?.export) return;
559
+ const scope = dataIO.export.scope ?? "filtered";
560
+ let rows;
561
+ if (scope === "all") rows = data;
562
+ else if (scope === "page") rows = pageRows;
563
+ else rows = sorted;
564
+ const cols = visibleColumns;
565
+ const text = dataIO.export.serialize ? dataIO.export.serialize(rows, cols, format) : format === "csv" ? toCsv(rows, cols) : toJson(rows, cols);
566
+ const baseName = typeof dataIO.export.filename === "function" ? dataIO.export.filename() : dataIO.export.filename ?? "table-export";
567
+ const filename = `${baseName}.${format}`;
568
+ const mime = format === "csv" ? "text/csv;charset=utf-8" : "application/json";
569
+ downloadString(text, filename, mime);
570
+ flash("Exported");
571
+ };
572
+ const onPickFile = async (file) => {
573
+ if (!dataIO?.import) return;
574
+ const text = await file.text();
575
+ try {
576
+ const parsed = dataIO.import.parse ? await dataIO.import.parse(text, file) : file.name.endsWith(".json") ? fromJson(text) : fromCsv(text);
577
+ dataIO.import.onImport(parsed, {
578
+ mode: dataIO.import.mode ?? "replace",
579
+ file
580
+ });
581
+ flash(`Imported ${parsed.length} ${parsed.length === 1 ? "row" : "rows"}`);
582
+ } catch (err) {
583
+ dataIO.import.onError?.(err, file);
584
+ flash("Couldn't read file", 2e3);
585
+ }
586
+ };
587
+ const showToolbar = search?.enabled || !!dateFilter || !!timeFilter || columnMenu || !!dataIO?.export?.enabled || !!dataIO?.import?.enabled || !!toolbarExtras;
588
+ const exportFormats = dataIO?.export?.formats ?? ["csv", "json"];
589
+ const [exportMenuOpen, setExportMenuOpen] = useState(false);
590
+ return /* @__PURE__ */ jsxs("div", { className: ["royui-dt", className].filter(Boolean).join(" "), children: [
591
+ showToolbar && /* @__PURE__ */ jsxs("div", { className: "royui-dt__toolbar", children: [
592
+ /* @__PURE__ */ jsxs("div", { className: "royui-dt__toolbar-left", children: [
593
+ search?.enabled && /* @__PURE__ */ jsx(
594
+ TableSearch,
595
+ {
596
+ value: filters.search,
597
+ onChange: (v) => {
598
+ setFilters((f) => ({ ...f, search: v }));
599
+ setPage(1);
600
+ },
601
+ placeholder: search.placeholder,
602
+ debounceMs: search.debounceMs
603
+ }
604
+ ),
605
+ dateFilter && /* @__PURE__ */ jsx(
606
+ DateRangePicker,
607
+ {
608
+ value: filters.dateRange,
609
+ onChange: (r) => {
610
+ setFilters((f) => ({ ...f, dateRange: r }));
611
+ setPage(1);
612
+ },
613
+ monthsVisible: dateFilter.monthsVisible ?? 2,
614
+ placeholder: dateFilter.placeholder ?? "Date range"
615
+ }
616
+ ),
617
+ timeFilter && /* @__PURE__ */ jsx(
618
+ TimePicker,
619
+ {
620
+ value: filters.time,
621
+ onChange: (t) => {
622
+ setFilters((f) => ({ ...f, time: t }));
623
+ setPage(1);
624
+ },
625
+ variant: timeFilter.variant ?? "analog",
626
+ hourCycle: timeFilter.hourCycle ?? 24,
627
+ placeholder: timeFilter.placeholder ?? "Time"
628
+ }
629
+ )
630
+ ] }),
631
+ /* @__PURE__ */ jsxs("div", { className: "royui-dt__toolbar-right", children: [
632
+ toolbarExtras,
633
+ columnMenu && /* @__PURE__ */ jsx(
634
+ ColumnMenu,
635
+ {
636
+ columns: orderedColumns,
637
+ layout,
638
+ onToggle: toggleHidden,
639
+ onReset: reset
640
+ }
641
+ ),
642
+ (dataIO?.export?.enabled || dataIO?.import?.enabled) && /* @__PURE__ */ jsx("span", { className: "royui-dt__sep", "aria-hidden": true, children: "\xB7" }),
643
+ dataIO?.export?.enabled && /* @__PURE__ */ jsxs("div", { className: "royui-dt-io", children: [
644
+ /* @__PURE__ */ jsx(
645
+ "button",
646
+ {
647
+ type: "button",
648
+ className: "royui-dt-io__btn",
649
+ onClick: () => {
650
+ const only = exportFormats[0];
651
+ if (exportFormats.length === 1 && only) {
652
+ exportRows(only);
653
+ } else {
654
+ setExportMenuOpen((o) => !o);
655
+ }
656
+ },
657
+ children: ioFlash && ioFlash.startsWith("Export") ? ioFlash : "Export"
658
+ }
659
+ ),
660
+ exportMenuOpen && exportFormats.length > 1 && /* @__PURE__ */ jsx(
661
+ "div",
662
+ {
663
+ className: "royui-dt-io__menu",
664
+ onMouseLeave: () => setExportMenuOpen(false),
665
+ children: exportFormats.map((f) => /* @__PURE__ */ jsx(
666
+ "button",
667
+ {
668
+ type: "button",
669
+ className: "royui-dt-io__menu-item",
670
+ onClick: () => {
671
+ exportRows(f);
672
+ setExportMenuOpen(false);
673
+ },
674
+ children: f.toUpperCase()
675
+ },
676
+ f
677
+ ))
678
+ }
679
+ )
680
+ ] }),
681
+ dataIO?.import?.enabled && /* @__PURE__ */ jsxs(Fragment, { children: [
682
+ /* @__PURE__ */ jsx(
683
+ "button",
684
+ {
685
+ type: "button",
686
+ className: "royui-dt-io__btn",
687
+ onClick: () => fileInput.current?.click(),
688
+ children: ioFlash && (ioFlash.startsWith("Import") || ioFlash.startsWith("Couldn't")) ? ioFlash : "Import"
689
+ }
690
+ ),
691
+ /* @__PURE__ */ jsx(
692
+ "input",
693
+ {
694
+ ref: fileInput,
695
+ type: "file",
696
+ accept: dataIO.import.accept ?? ".csv,.json",
697
+ style: { display: "none" },
698
+ onChange: (e) => {
699
+ const f = e.target.files?.[0];
700
+ if (f) onPickFile(f);
701
+ e.target.value = "";
702
+ }
703
+ }
704
+ )
705
+ ] })
706
+ ] })
707
+ ] }),
708
+ /* @__PURE__ */ jsxs(
709
+ Table,
710
+ {
711
+ visibleRows,
712
+ rowHeight,
713
+ stickyHeader,
714
+ density,
715
+ loading,
716
+ empty,
717
+ isEmpty: pageRows.length === 0,
718
+ fitColumns,
719
+ headerFont,
720
+ rowHeaderFont,
721
+ cellFont,
722
+ children: [
723
+ /* @__PURE__ */ jsx("colgroup", { children: visibleColumns.map((c) => {
724
+ const w = fitColumns ? void 0 : layout.sizes[c.key];
725
+ return /* @__PURE__ */ jsx(
726
+ "col",
727
+ {
728
+ style: w ? { width: w } : void 0,
729
+ className: dragKey === c.key ? "royui-dt__col--dragging" : void 0
730
+ },
731
+ c.key
732
+ );
733
+ }) }),
734
+ /* @__PURE__ */ jsx(TableHeader, { children: /* @__PURE__ */ jsxs(TableRow, { children: [
735
+ visibleColumns.map((c, idx) => {
736
+ const isDragging = dragKey === c.key;
737
+ const showDropBefore = dropIndex === idx;
738
+ const sortDir = sort && sort.key === c.key && sort.dir ? sort.dir : null;
739
+ const canReorder = reorderable && c.reorderable !== false && !c.pinned;
740
+ const canResize = resizable && c.resizable !== false;
741
+ return /* @__PURE__ */ jsxs(
742
+ TableHead,
743
+ {
744
+ align: c.align ?? (c.type === "number" ? "right" : "left"),
745
+ className: [
746
+ "royui-dt__th",
747
+ canReorder && "royui-dt__th--reorderable",
748
+ canResize && "royui-dt__th--resizable",
749
+ isDragging && "royui-dt__th--dragging",
750
+ showDropBefore && "royui-dt__th--drop-before"
751
+ ].filter(Boolean).join(" "),
752
+ draggable: canReorder,
753
+ onDragStart: (e) => onDragStart(e, c.key),
754
+ onDragOver: (e) => onDragOver(e, idx),
755
+ onDrop,
756
+ onDragEnd,
757
+ title: canReorder && canResize ? "Drag to reorder \xB7 drag the right edge to resize" : canReorder ? "Drag to reorder" : canResize ? "Drag the right edge to resize" : void 0,
758
+ children: [
759
+ /* @__PURE__ */ jsxs(
760
+ "span",
761
+ {
762
+ className: "royui-dt__th-inner",
763
+ onClick: () => cycleSort(c.key),
764
+ role: "button",
765
+ tabIndex: 0,
766
+ onKeyDown: (e) => {
767
+ if (e.key === "Enter" || e.key === " ") {
768
+ e.preventDefault();
769
+ cycleSort(c.key);
770
+ }
771
+ },
772
+ children: [
773
+ /* @__PURE__ */ jsx("span", { className: "royui-dt__th-label", children: c.header }),
774
+ sortDir && /* @__PURE__ */ jsx(
775
+ "span",
776
+ {
777
+ className: `royui-dt__sort-indicator royui-dt__sort-indicator--${sortDir}`,
778
+ "aria-hidden": true
779
+ }
780
+ )
781
+ ]
782
+ }
783
+ ),
784
+ canResize && /* @__PURE__ */ jsx(
785
+ "span",
786
+ {
787
+ className: "royui-dt__resize",
788
+ onPointerDown: (e) => beginResize(e, c.key),
789
+ onDoubleClick: () => doubleClickReset(c.key),
790
+ "aria-hidden": true,
791
+ children: /* @__PURE__ */ jsx("span", { className: "royui-dt__resize-bar", "aria-hidden": true })
792
+ }
793
+ )
794
+ ]
795
+ },
796
+ c.key
797
+ );
798
+ }),
799
+ dropIndex === visibleColumns.length && /* @__PURE__ */ jsx(TableHead, { className: "royui-dt__th--drop-end", "aria-hidden": true })
800
+ ] }) }),
801
+ /* @__PURE__ */ jsx(TableBody, { children: pageRows.map((row, i) => {
802
+ const id = getRowId ? getRowId(row, i) : row?.["id"] != null ? String(row["id"]) : i;
803
+ return /* @__PURE__ */ jsx(TableRow, { children: visibleColumns.map((c) => {
804
+ const value = c.accessor(row);
805
+ const display = c.cell ? c.cell(value, row) : renderCellValue(value, c.type);
806
+ const cellStyle = fontStyleFor(c.font);
807
+ const isNum = c.type === "number";
808
+ return /* @__PURE__ */ jsx(
809
+ TableCell,
810
+ {
811
+ isRowHeader: c.isRowHeader,
812
+ align: c.align ?? (isNum ? "right" : "left"),
813
+ className: isNum ? "royui-table__td--num" : void 0,
814
+ style: cellStyle,
815
+ children: display
816
+ },
817
+ c.key
818
+ );
819
+ }) }, id);
820
+ }) })
821
+ ]
822
+ }
823
+ ),
824
+ pagination !== false && pageCount > 1 && /* @__PURE__ */ jsx("div", { className: "royui-dt__pagination", children: /* @__PURE__ */ jsx(
825
+ Pagination,
826
+ {
827
+ page: currentPage,
828
+ pageCount,
829
+ onPageChange: setPage,
830
+ siblingCount: pagination?.siblingCount ?? 1,
831
+ showSummary: pagination?.showSummary ?? true,
832
+ summaryRender: (p, pc) => `${(p - 1) * pageSize + 1}\u2013${Math.min(p * pageSize, sorted.length)} of ${sorted.length}`
833
+ }
834
+ ) })
835
+ ] });
836
+ }
837
+
838
+ export { ColumnMenu, DataTable, downloadString, fromCsv, fromJson, toCsv, toJson };
839
+ //# sourceMappingURL=chunk-HUCK7AM7.js.map
840
+ //# sourceMappingURL=chunk-HUCK7AM7.js.map