@requence/table 0.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/dist/VirtualTable.d.ts +1 -1
- package/dist/VirtualTable.d.ts.map +1 -1
- package/dist/index.d.ts +24 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +691 -11
- package/dist/index.js.map +7 -3
- package/package.json +4 -1
- package/src/VirtualTable.tsx +1 -1
- package/src/index.ts +40 -11
- package/dist/VirtualTable.js +0 -411
- package/dist/VirtualTable.js.map +0 -10
- package/dist/useTableCache.js +0 -232
- package/dist/useTableCache.js.map +0 -10
- package/dist/useTableColumnWidths.js +0 -45
- package/dist/useTableColumnWidths.js.map +0 -10
package/dist/index.js
CHANGED
|
@@ -1,14 +1,694 @@
|
|
|
1
|
+
// src/VirtualTable.tsx
|
|
2
|
+
import {
|
|
3
|
+
Children,
|
|
4
|
+
isValidElement,
|
|
5
|
+
useCallback,
|
|
6
|
+
useEffect,
|
|
7
|
+
useRef,
|
|
8
|
+
useState
|
|
9
|
+
} from "react";
|
|
10
|
+
import { flushSync } from "react-dom";
|
|
11
|
+
import { twMerge } from "tailwind-merge";
|
|
12
|
+
import { jsx, jsxs, Fragment } from "react/jsx-runtime";
|
|
13
|
+
function asSlot(slot, defaults) {
|
|
14
|
+
const Component = () => null;
|
|
15
|
+
Component.slot = slot;
|
|
16
|
+
Component.slotDefaults = defaults ?? {};
|
|
17
|
+
return Component;
|
|
18
|
+
}
|
|
19
|
+
function createTableHeader(defaults) {
|
|
20
|
+
return asSlot("header", defaults);
|
|
21
|
+
}
|
|
22
|
+
function createTableColumn(defaults) {
|
|
23
|
+
return asSlot("column", defaults);
|
|
24
|
+
}
|
|
25
|
+
function createTableBody(defaults) {
|
|
26
|
+
return asSlot("body", defaults);
|
|
27
|
+
}
|
|
28
|
+
function createTableSkeletonRow(defaults) {
|
|
29
|
+
return asSlot("skeletonRow", defaults);
|
|
30
|
+
}
|
|
31
|
+
function createTableEmpty(defaults) {
|
|
32
|
+
return asSlot("empty", defaults);
|
|
33
|
+
}
|
|
34
|
+
function createTableFooter(defaults) {
|
|
35
|
+
return asSlot("footer", defaults);
|
|
36
|
+
}
|
|
37
|
+
function createTableRow(defaults) {
|
|
38
|
+
return asSlot("row", defaults);
|
|
39
|
+
}
|
|
40
|
+
var GRID_VAR = "--vtable-grid-cols";
|
|
41
|
+
var GRID_VAR_REF = `var(${GRID_VAR})`;
|
|
42
|
+
function buildGridTemplate(columns) {
|
|
43
|
+
return columns.map((col) => {
|
|
44
|
+
if (typeof col.width === "number") {
|
|
45
|
+
return `${col.width}px`;
|
|
46
|
+
}
|
|
47
|
+
const min = col.minWidth ? `${col.minWidth}px` : "0";
|
|
48
|
+
if (typeof col.width === "string") {
|
|
49
|
+
return `minmax(${min}, ${col.width})`;
|
|
50
|
+
}
|
|
51
|
+
return `minmax(${min}, 1fr)`;
|
|
52
|
+
}).join(" ");
|
|
53
|
+
}
|
|
54
|
+
var VirtualTableHeader = asSlot("header");
|
|
55
|
+
var VirtualTableColumn = asSlot("column");
|
|
56
|
+
var VirtualTableBody = asSlot("body");
|
|
57
|
+
var VirtualTableRow = asSlot("row");
|
|
58
|
+
var VirtualTableSkeletonRow = asSlot("skeletonRow");
|
|
59
|
+
var VirtualTableEmpty = asSlot("empty");
|
|
60
|
+
var VirtualTableFooter = asSlot("footer");
|
|
61
|
+
function slotIs(child, slot) {
|
|
62
|
+
return child.type?.slot === slot;
|
|
63
|
+
}
|
|
64
|
+
function extractSlots(children) {
|
|
65
|
+
let header = null;
|
|
66
|
+
let body = null;
|
|
67
|
+
let skeletonRow = null;
|
|
68
|
+
let empty = null;
|
|
69
|
+
let footer = null;
|
|
70
|
+
Children.forEach(children, (child) => {
|
|
71
|
+
if (!isValidElement(child)) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (slotIs(child, "header")) {
|
|
75
|
+
const defaults = child.type.slotDefaults ?? {};
|
|
76
|
+
const props = child.props;
|
|
77
|
+
const columns = [];
|
|
78
|
+
Children.forEach(props.children, (col) => {
|
|
79
|
+
if (isValidElement(col) && slotIs(col, "column")) {
|
|
80
|
+
const d = col.type.slotDefaults ?? {};
|
|
81
|
+
const p = col.props;
|
|
82
|
+
columns.push({
|
|
83
|
+
width: p.width ?? d.width,
|
|
84
|
+
header: p.children,
|
|
85
|
+
className: twMerge(d.className, p.className),
|
|
86
|
+
resizable: p.resizable ?? d.resizable,
|
|
87
|
+
minWidth: p.minWidth ?? d.minWidth,
|
|
88
|
+
maxWidth: p.maxWidth ?? d.maxWidth,
|
|
89
|
+
transparent: p.transparent ?? d.transparent,
|
|
90
|
+
onResizeStart: p.onResizeStart ?? d.onResizeStart,
|
|
91
|
+
onResizeEnd: p.onResizeEnd ?? d.onResizeEnd
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
header = {
|
|
96
|
+
className: twMerge(defaults.className, props.className),
|
|
97
|
+
columns
|
|
98
|
+
};
|
|
99
|
+
} else if (slotIs(child, "body")) {
|
|
100
|
+
body = child.props;
|
|
101
|
+
} else if (slotIs(child, "skeletonRow")) {
|
|
102
|
+
const defaults = child.type.slotDefaults ?? {};
|
|
103
|
+
const props = child.props;
|
|
104
|
+
skeletonRow = {
|
|
105
|
+
...defaults,
|
|
106
|
+
...props,
|
|
107
|
+
className: twMerge(defaults.className, props.className)
|
|
108
|
+
};
|
|
109
|
+
} else if (slotIs(child, "empty")) {
|
|
110
|
+
const defaults = child.type.slotDefaults ?? {};
|
|
111
|
+
const props = child.props;
|
|
112
|
+
empty = {
|
|
113
|
+
...defaults,
|
|
114
|
+
...props,
|
|
115
|
+
className: twMerge(defaults.className, props.className)
|
|
116
|
+
};
|
|
117
|
+
} else if (slotIs(child, "footer")) {
|
|
118
|
+
const defaults = child.type.slotDefaults ?? {};
|
|
119
|
+
const props = child.props;
|
|
120
|
+
footer = {
|
|
121
|
+
...defaults,
|
|
122
|
+
...props,
|
|
123
|
+
className: twMerge(defaults.className, props.className)
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
return { header, body, skeletonRow, empty, footer };
|
|
128
|
+
}
|
|
129
|
+
function ResizeHandle({ onMouseDown }) {
|
|
130
|
+
return /* @__PURE__ */ jsx("div", {
|
|
131
|
+
role: "separator",
|
|
132
|
+
"aria-orientation": "vertical",
|
|
133
|
+
className: "resizer absolute right-0 top-0 z-10 h-full w-1.5 cursor-col-resize",
|
|
134
|
+
onMouseDown
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
function VirtualTableCell({
|
|
138
|
+
className,
|
|
139
|
+
style,
|
|
140
|
+
showOnHover,
|
|
141
|
+
colSpan,
|
|
142
|
+
...rest
|
|
143
|
+
}) {
|
|
144
|
+
return /* @__PURE__ */ jsx("div", {
|
|
145
|
+
role: "cell",
|
|
146
|
+
className: twMerge("overflow-hidden text-ellipsis whitespace-nowrap", showOnHover && "not-group-hover/row:*:delay-200 *:opacity-10 *:transition-opacity *:duration-300 *:ease-in-out group-hover/row:*:opacity-100", className),
|
|
147
|
+
style: colSpan ? { gridColumn: `span ${colSpan}`, ...style } : style,
|
|
148
|
+
...rest
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
function DataRow({ index, rowHeight, rowProps, children }) {
|
|
152
|
+
const { className, style, ...restProps } = rowProps;
|
|
153
|
+
return /* @__PURE__ */ jsx("div", {
|
|
154
|
+
role: "row",
|
|
155
|
+
"aria-rowindex": index + 1,
|
|
156
|
+
className: twMerge("group/row absolute w-full", className),
|
|
157
|
+
style: {
|
|
158
|
+
height: rowHeight,
|
|
159
|
+
transform: `translateY(${index * rowHeight}px)`,
|
|
160
|
+
display: "grid",
|
|
161
|
+
gridTemplateColumns: GRID_VAR_REF,
|
|
162
|
+
alignItems: "center",
|
|
163
|
+
willChange: "transform",
|
|
164
|
+
contain: "layout style paint",
|
|
165
|
+
...style
|
|
166
|
+
},
|
|
167
|
+
...restProps,
|
|
168
|
+
children
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
function VirtualTableRoot({
|
|
172
|
+
totalCount,
|
|
173
|
+
rowHeight,
|
|
174
|
+
overscan = 5,
|
|
175
|
+
onRangeChange,
|
|
176
|
+
className,
|
|
177
|
+
style: styleProp,
|
|
178
|
+
"aria-label": ariaLabel,
|
|
179
|
+
children
|
|
180
|
+
}) {
|
|
181
|
+
const scrollRef = useRef(null);
|
|
182
|
+
const [visibleRange, setVisibleRange] = useState({ start: 0, end: 0 });
|
|
183
|
+
const prevRangeRef = useRef({ start: 0, end: 0 });
|
|
184
|
+
const { header, body, skeletonRow, empty, footer } = extractSlots(children);
|
|
185
|
+
const columns = header?.columns ?? [];
|
|
186
|
+
const gridTemplate = buildGridTemplate(columns);
|
|
187
|
+
const renderRow = body?.children ?? (() => null);
|
|
188
|
+
const calculateRange = useCallback(() => {
|
|
189
|
+
const el = scrollRef.current;
|
|
190
|
+
if (!el || totalCount === 0) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
const scrollTop = el.scrollTop;
|
|
194
|
+
const viewportHeight = el.clientHeight;
|
|
195
|
+
const rawStart = Math.floor(scrollTop / rowHeight);
|
|
196
|
+
const rawEnd = Math.ceil((scrollTop + viewportHeight) / rowHeight);
|
|
197
|
+
const start = Math.max(0, rawStart - overscan);
|
|
198
|
+
const end = Math.min(totalCount, rawEnd + overscan);
|
|
199
|
+
const prev = prevRangeRef.current;
|
|
200
|
+
if (prev.start !== start || prev.end !== end) {
|
|
201
|
+
prevRangeRef.current = { start, end };
|
|
202
|
+
flushSync(() => setVisibleRange({ start, end }));
|
|
203
|
+
}
|
|
204
|
+
}, [totalCount, rowHeight, overscan]);
|
|
205
|
+
const handleScroll = useCallback(() => {
|
|
206
|
+
calculateRange();
|
|
207
|
+
}, [calculateRange]);
|
|
208
|
+
useEffect(() => {
|
|
209
|
+
calculateRange();
|
|
210
|
+
}, [calculateRange]);
|
|
211
|
+
useEffect(() => {
|
|
212
|
+
onRangeChange?.(visibleRange);
|
|
213
|
+
}, [visibleRange, onRangeChange]);
|
|
214
|
+
const handleResizeMouseDown = useCallback((columnIndex, e) => {
|
|
215
|
+
e.preventDefault();
|
|
216
|
+
const container = scrollRef.current;
|
|
217
|
+
if (!container) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
const col = columns[columnIndex];
|
|
221
|
+
const headerCells = container.querySelectorAll('[role="columnheader"]');
|
|
222
|
+
const startWidth = headerCells[columnIndex]?.getBoundingClientRect().width ?? (typeof col.width === "number" ? col.width : 100);
|
|
223
|
+
const startX = e.clientX;
|
|
224
|
+
const minW = col.minWidth ?? 50;
|
|
225
|
+
const maxW = col.maxWidth ?? Infinity;
|
|
226
|
+
const otherColumnsMinWidth = columns.reduce((sum, c, i) => {
|
|
227
|
+
if (i === columnIndex) {
|
|
228
|
+
return sum;
|
|
229
|
+
}
|
|
230
|
+
if (typeof c.width === "number") {
|
|
231
|
+
return sum + c.width;
|
|
232
|
+
}
|
|
233
|
+
return sum + (c.minWidth ?? 0);
|
|
234
|
+
}, 0);
|
|
235
|
+
const maxAllowedWidth = container.clientWidth - otherColumnsMinWidth;
|
|
236
|
+
let currentWidth = startWidth;
|
|
237
|
+
col.onResizeStart?.();
|
|
238
|
+
const prevCursor = document.body.style.cursor;
|
|
239
|
+
document.body.style.cursor = "col-resize";
|
|
240
|
+
const handleMouseMove = (moveEvent) => {
|
|
241
|
+
const delta = moveEvent.clientX - startX;
|
|
242
|
+
currentWidth = Math.min(maxW, maxAllowedWidth, Math.max(minW, startWidth + delta));
|
|
243
|
+
const template = columns.map((c, i) => {
|
|
244
|
+
if (i === columnIndex) {
|
|
245
|
+
return `${currentWidth}px`;
|
|
246
|
+
}
|
|
247
|
+
return buildGridTemplate([c]);
|
|
248
|
+
}).join(" ");
|
|
249
|
+
container.style.setProperty(GRID_VAR, template);
|
|
250
|
+
};
|
|
251
|
+
const handleMouseUp = () => {
|
|
252
|
+
document.body.style.cursor = prevCursor;
|
|
253
|
+
document.removeEventListener("mousemove", handleMouseMove);
|
|
254
|
+
document.removeEventListener("mouseup", handleMouseUp);
|
|
255
|
+
const allCells = container.querySelectorAll('[role="columnheader"]');
|
|
256
|
+
let otherFrPx = 0;
|
|
257
|
+
let otherFrUnits = 0;
|
|
258
|
+
columns.forEach((c, i) => {
|
|
259
|
+
if (i === columnIndex) {
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
if (typeof c.width !== "number") {
|
|
263
|
+
otherFrPx += allCells[i]?.getBoundingClientRect().width ?? 0;
|
|
264
|
+
otherFrUnits += typeof c.width === "string" ? parseFloat(c.width) || 1 : 1;
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
const frValue = otherFrPx > 0 ? currentWidth / otherFrPx * otherFrUnits : 1;
|
|
268
|
+
col.onResizeEnd?.(currentWidth, startWidth, frValue);
|
|
269
|
+
};
|
|
270
|
+
document.addEventListener("mousemove", handleMouseMove);
|
|
271
|
+
document.addEventListener("mouseup", handleMouseUp);
|
|
272
|
+
}, [columns]);
|
|
273
|
+
if (totalCount === 0 && empty) {
|
|
274
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
275
|
+
role: "table",
|
|
276
|
+
"aria-label": ariaLabel,
|
|
277
|
+
className: twMerge("flex flex-col overflow-hidden", className),
|
|
278
|
+
style: { [GRID_VAR]: gridTemplate, ...styleProp },
|
|
279
|
+
children: [
|
|
280
|
+
/* @__PURE__ */ jsx("div", {
|
|
281
|
+
role: "rowgroup",
|
|
282
|
+
className: twMerge("sticky top-0 z-10", header?.className),
|
|
283
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
284
|
+
role: "row",
|
|
285
|
+
style: {
|
|
286
|
+
display: "grid",
|
|
287
|
+
gridTemplateColumns: GRID_VAR_REF,
|
|
288
|
+
alignItems: "center",
|
|
289
|
+
height: rowHeight
|
|
290
|
+
},
|
|
291
|
+
children: columns.map((col, i) => /* @__PURE__ */ jsxs("div", {
|
|
292
|
+
role: "columnheader",
|
|
293
|
+
className: twMerge("whitespace-nowrap", col.resizable && "relative", col.className),
|
|
294
|
+
children: [
|
|
295
|
+
col.header,
|
|
296
|
+
col.resizable && /* @__PURE__ */ jsx(ResizeHandle, {
|
|
297
|
+
onMouseDown: (e) => handleResizeMouseDown(i, e)
|
|
298
|
+
})
|
|
299
|
+
]
|
|
300
|
+
}, i))
|
|
301
|
+
})
|
|
302
|
+
}),
|
|
303
|
+
/* @__PURE__ */ jsx("div", {
|
|
304
|
+
className: twMerge("flex items-center justify-center", empty.className),
|
|
305
|
+
children: empty.children
|
|
306
|
+
})
|
|
307
|
+
]
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
const rows = [];
|
|
311
|
+
for (let i = visibleRange.start;i < visibleRange.end; i++) {
|
|
312
|
+
const content = renderRow(i);
|
|
313
|
+
if (content === null) {
|
|
314
|
+
if (skeletonRow) {
|
|
315
|
+
const { children: skeletonContent, ...skeletonProps } = skeletonRow;
|
|
316
|
+
rows.push(/* @__PURE__ */ jsx(DataRow, {
|
|
317
|
+
index: i,
|
|
318
|
+
rowHeight,
|
|
319
|
+
rowProps: skeletonProps,
|
|
320
|
+
children: skeletonContent
|
|
321
|
+
}, `row-${i}`));
|
|
322
|
+
}
|
|
323
|
+
} else {
|
|
324
|
+
const rowElement = content;
|
|
325
|
+
const rowDefaults = rowElement.type?.slotDefaults ?? {};
|
|
326
|
+
const rawProps = rowElement.props;
|
|
327
|
+
const { children: cellContent, ...userRowProps } = rawProps;
|
|
328
|
+
const rowProps = {
|
|
329
|
+
...rowDefaults,
|
|
330
|
+
...userRowProps,
|
|
331
|
+
className: twMerge(rowDefaults.className, userRowProps.className)
|
|
332
|
+
};
|
|
333
|
+
rows.push(/* @__PURE__ */ jsx(DataRow, {
|
|
334
|
+
index: i,
|
|
335
|
+
rowHeight,
|
|
336
|
+
rowProps,
|
|
337
|
+
children: cellContent
|
|
338
|
+
}, `row-${i}`));
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
const totalHeight = totalCount * rowHeight;
|
|
342
|
+
return /* @__PURE__ */ jsxs(Fragment, {
|
|
343
|
+
children: [
|
|
344
|
+
/* @__PURE__ */ jsxs("div", {
|
|
345
|
+
role: "table",
|
|
346
|
+
"aria-label": ariaLabel,
|
|
347
|
+
"aria-rowcount": totalCount,
|
|
348
|
+
ref: scrollRef,
|
|
349
|
+
onScroll: handleScroll,
|
|
350
|
+
className: twMerge("relative overflow-auto", className),
|
|
351
|
+
style: { [GRID_VAR]: gridTemplate, ...styleProp },
|
|
352
|
+
children: [
|
|
353
|
+
/* @__PURE__ */ jsx("div", {
|
|
354
|
+
role: "rowgroup",
|
|
355
|
+
className: twMerge("sticky top-0 z-10", header?.className),
|
|
356
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
357
|
+
role: "row",
|
|
358
|
+
style: {
|
|
359
|
+
display: "grid",
|
|
360
|
+
gridTemplateColumns: GRID_VAR_REF,
|
|
361
|
+
alignItems: "center"
|
|
362
|
+
},
|
|
363
|
+
children: columns.map((col, i) => /* @__PURE__ */ jsxs("div", {
|
|
364
|
+
role: "columnheader",
|
|
365
|
+
className: twMerge("whitespace-nowrap", col.resizable && "relative", col.className),
|
|
366
|
+
children: [
|
|
367
|
+
col.header,
|
|
368
|
+
col.resizable && /* @__PURE__ */ jsx(ResizeHandle, {
|
|
369
|
+
onMouseDown: (e) => handleResizeMouseDown(i, e)
|
|
370
|
+
})
|
|
371
|
+
]
|
|
372
|
+
}, i))
|
|
373
|
+
})
|
|
374
|
+
}),
|
|
375
|
+
/* @__PURE__ */ jsx("div", {
|
|
376
|
+
role: "rowgroup",
|
|
377
|
+
className: "relative",
|
|
378
|
+
style: { height: totalHeight },
|
|
379
|
+
children: rows
|
|
380
|
+
})
|
|
381
|
+
]
|
|
382
|
+
}),
|
|
383
|
+
footer && totalCount > 0 && /* @__PURE__ */ jsx("div", {
|
|
384
|
+
className: footer.className,
|
|
385
|
+
children: footer.children(visibleRange)
|
|
386
|
+
})
|
|
387
|
+
]
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
var VirtualTable = Object.assign(VirtualTableRoot, {
|
|
391
|
+
Header: VirtualTableHeader,
|
|
392
|
+
Column: VirtualTableColumn,
|
|
393
|
+
Body: VirtualTableBody,
|
|
394
|
+
SkeletonRow: VirtualTableSkeletonRow,
|
|
395
|
+
Row: VirtualTableRow,
|
|
396
|
+
Cell: VirtualTableCell,
|
|
397
|
+
Empty: VirtualTableEmpty,
|
|
398
|
+
Footer: VirtualTableFooter
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
// src/useTableCache.ts
|
|
402
|
+
import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
|
|
403
|
+
var cacheMap = new Map;
|
|
404
|
+
function resolveCache(id, options, pageSize) {
|
|
405
|
+
let state = cacheMap.get(id);
|
|
406
|
+
if (!state) {
|
|
407
|
+
state = {
|
|
408
|
+
pages: new Map,
|
|
409
|
+
totalCount: 0,
|
|
410
|
+
inflight: new Set,
|
|
411
|
+
compare: options.compare,
|
|
412
|
+
fetchItems: options.fetchItems,
|
|
413
|
+
fetchCount: options.fetchCount,
|
|
414
|
+
getItemId: options.getItemId,
|
|
415
|
+
promise: null,
|
|
416
|
+
knownIds: new Set,
|
|
417
|
+
fetchCountTimer: null
|
|
418
|
+
};
|
|
419
|
+
cacheMap.set(id, state);
|
|
420
|
+
const s = state;
|
|
421
|
+
s.promise = options.fetchItems(0, pageSize).then((result) => {
|
|
422
|
+
if (cacheMap.get(id) === s) {
|
|
423
|
+
s.pages.set(0, result.items);
|
|
424
|
+
s.totalCount = result.total;
|
|
425
|
+
for (const item of result.items) {
|
|
426
|
+
s.knownIds.add(s.getItemId(item));
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}).then(() => {
|
|
430
|
+
s.promise = null;
|
|
431
|
+
}).catch((error) => {
|
|
432
|
+
console.error(error);
|
|
433
|
+
throw error;
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
state.fetchItems = options.fetchItems;
|
|
437
|
+
state.fetchCount = options.fetchCount;
|
|
438
|
+
state.compare = options.compare;
|
|
439
|
+
state.getItemId = options.getItemId;
|
|
440
|
+
return state;
|
|
441
|
+
}
|
|
442
|
+
function useTableCache(key, options) {
|
|
443
|
+
const { pageSize, getItemId, compare, fetchItems, fetchCount } = options;
|
|
444
|
+
const [, forceRender] = useState2(0);
|
|
445
|
+
const [iteration, setIteration] = useState2(0);
|
|
446
|
+
const rerender = useCallback2(() => {
|
|
447
|
+
forceRender((c) => c + 1);
|
|
448
|
+
}, []);
|
|
449
|
+
const activeKey = [key, iteration].join("-");
|
|
450
|
+
const currentCache = resolveCache(activeKey, { fetchItems, fetchCount, compare, getItemId }, pageSize);
|
|
451
|
+
const cacheRef = useRef2(currentCache);
|
|
452
|
+
cacheRef.current = currentCache;
|
|
453
|
+
if (currentCache.promise) {
|
|
454
|
+
throw currentCache.promise;
|
|
455
|
+
}
|
|
456
|
+
useEffect2(() => () => {
|
|
457
|
+
for (const k of cacheMap.keys()) {
|
|
458
|
+
if (k.startsWith(key)) {
|
|
459
|
+
cacheMap.delete(k);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}, [key]);
|
|
463
|
+
const fetchPage = useCallback2((pageIndex) => {
|
|
464
|
+
const c = cacheRef.current;
|
|
465
|
+
if (c.pages.has(pageIndex) || c.inflight.has(pageIndex)) {
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
const offset = pageIndex * pageSize;
|
|
469
|
+
c.inflight.add(pageIndex);
|
|
470
|
+
c.fetchItems(offset, pageSize).then((result) => {
|
|
471
|
+
if (cacheRef.current === c) {
|
|
472
|
+
c.pages.set(pageIndex, result.items);
|
|
473
|
+
c.totalCount = result.total;
|
|
474
|
+
c.inflight.delete(pageIndex);
|
|
475
|
+
for (const item of result.items) {
|
|
476
|
+
c.knownIds.add(c.getItemId(item));
|
|
477
|
+
}
|
|
478
|
+
rerender();
|
|
479
|
+
}
|
|
480
|
+
});
|
|
481
|
+
}, [pageSize, rerender]);
|
|
482
|
+
const getItem = useCallback2((index) => {
|
|
483
|
+
const c = cacheRef.current;
|
|
484
|
+
const pageIndex = Math.floor(index / pageSize);
|
|
485
|
+
const page = c.pages.get(pageIndex);
|
|
486
|
+
if (!page) {
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
const offsetInPage = index - pageIndex * pageSize;
|
|
490
|
+
return page[offsetInPage];
|
|
491
|
+
}, [pageSize]);
|
|
492
|
+
const handleRangeChange = useCallback2((range) => {
|
|
493
|
+
const c = cacheRef.current;
|
|
494
|
+
if (c.totalCount === 0) {
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
const startPage = Math.floor(range.start / pageSize);
|
|
498
|
+
const endPage = Math.floor(Math.min(range.end, c.totalCount - 1) / pageSize);
|
|
499
|
+
for (let p = startPage;p <= endPage; p++) {
|
|
500
|
+
fetchPage(p);
|
|
501
|
+
}
|
|
502
|
+
}, [pageSize, fetchPage]);
|
|
503
|
+
const debouncedFetchCount = useCallback2(() => {
|
|
504
|
+
const c = cacheRef.current;
|
|
505
|
+
if (!c.fetchCount) {
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
if (c.fetchCountTimer) {
|
|
509
|
+
clearTimeout(c.fetchCountTimer);
|
|
510
|
+
}
|
|
511
|
+
c.fetchCountTimer = setTimeout(() => {
|
|
512
|
+
const current = cacheRef.current;
|
|
513
|
+
current.fetchCountTimer = null;
|
|
514
|
+
current.fetchCount?.().then((total) => {
|
|
515
|
+
if (cacheRef.current === current) {
|
|
516
|
+
current.totalCount = total;
|
|
517
|
+
rerender();
|
|
518
|
+
}
|
|
519
|
+
});
|
|
520
|
+
}, 150);
|
|
521
|
+
}, [rerender]);
|
|
522
|
+
const upsert = useCallback2((item) => {
|
|
523
|
+
const c = cacheRef.current;
|
|
524
|
+
const id = getItemId(item);
|
|
525
|
+
for (const [, page] of c.pages) {
|
|
526
|
+
const idx = page.findIndex((p) => getItemId(p) === id);
|
|
527
|
+
if (idx !== -1) {
|
|
528
|
+
page[idx] = item;
|
|
529
|
+
rerender();
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
if (c.knownIds.has(id)) {
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
c.knownIds.add(id);
|
|
537
|
+
let inserted = false;
|
|
538
|
+
const sortedPageIndices = [...c.pages.keys()].sort((a, b) => a - b);
|
|
539
|
+
for (const pageIndex of sortedPageIndices) {
|
|
540
|
+
const page = c.pages.get(pageIndex);
|
|
541
|
+
if (page.length > 0 && c.compare(item, page[0]) <= 0) {
|
|
542
|
+
page.unshift(item);
|
|
543
|
+
inserted = true;
|
|
544
|
+
invalidateAfter(c, pageIndex);
|
|
545
|
+
break;
|
|
546
|
+
}
|
|
547
|
+
if (page.length > 0) {
|
|
548
|
+
const lastItem = page[page.length - 1];
|
|
549
|
+
if (c.compare(item, lastItem) <= 0) {
|
|
550
|
+
let lo = 0;
|
|
551
|
+
let hi = page.length;
|
|
552
|
+
while (lo < hi) {
|
|
553
|
+
const mid = lo + hi >>> 1;
|
|
554
|
+
if (c.compare(item, page[mid]) <= 0) {
|
|
555
|
+
hi = mid;
|
|
556
|
+
} else {
|
|
557
|
+
lo = mid + 1;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
page.splice(lo, 0, item);
|
|
561
|
+
inserted = true;
|
|
562
|
+
invalidateAfter(c, pageIndex);
|
|
563
|
+
break;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
if (!inserted) {
|
|
568
|
+
if (sortedPageIndices.length > 0) {
|
|
569
|
+
const firstPageIndex = sortedPageIndices[0];
|
|
570
|
+
const firstPage = c.pages.get(firstPageIndex);
|
|
571
|
+
if (firstPage.length > 0 && c.compare(item, firstPage[0]) <= 0) {
|
|
572
|
+
firstPage.unshift(item);
|
|
573
|
+
invalidateAfter(c, firstPageIndex);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
if (c.fetchCount) {
|
|
578
|
+
debouncedFetchCount();
|
|
579
|
+
} else {
|
|
580
|
+
c.totalCount += 1;
|
|
581
|
+
}
|
|
582
|
+
rerender();
|
|
583
|
+
}, [getItemId, rerender, debouncedFetchCount]);
|
|
584
|
+
const remove = useCallback2((id) => {
|
|
585
|
+
const c = cacheRef.current;
|
|
586
|
+
let removedFromPage = null;
|
|
587
|
+
c.knownIds.delete(id);
|
|
588
|
+
for (const [pageIndex, page] of c.pages) {
|
|
589
|
+
const idx = page.findIndex((item) => getItemId(item) === id);
|
|
590
|
+
if (idx !== -1) {
|
|
591
|
+
page.splice(idx, 1);
|
|
592
|
+
removedFromPage = pageIndex;
|
|
593
|
+
break;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
c.totalCount = Math.max(0, c.totalCount - 1);
|
|
597
|
+
if (removedFromPage !== null) {
|
|
598
|
+
invalidateAfter(c, removedFromPage);
|
|
599
|
+
}
|
|
600
|
+
rerender();
|
|
601
|
+
}, [getItemId, rerender]);
|
|
602
|
+
const reset = useCallback2(() => {
|
|
603
|
+
const c = cacheRef.current;
|
|
604
|
+
if (c.fetchCountTimer) {
|
|
605
|
+
clearTimeout(c.fetchCountTimer);
|
|
606
|
+
c.fetchCountTimer = null;
|
|
607
|
+
}
|
|
608
|
+
setIteration((iteration2) => iteration2 + 1);
|
|
609
|
+
rerender();
|
|
610
|
+
}, [rerender]);
|
|
611
|
+
return {
|
|
612
|
+
totalCount: currentCache.totalCount,
|
|
613
|
+
getItem,
|
|
614
|
+
handleRangeChange,
|
|
615
|
+
upsert,
|
|
616
|
+
remove,
|
|
617
|
+
reset,
|
|
618
|
+
loading: currentCache.inflight.size > 0
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
function invalidateAfter(cache, afterPageIndex) {
|
|
622
|
+
for (const key of cache.pages.keys()) {
|
|
623
|
+
if (key > afterPageIndex) {
|
|
624
|
+
cache.pages.delete(key);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// src/useTableColumnWidths.ts
|
|
630
|
+
import { useCallback as useCallback3, useEffect as useEffect3, useState as useState3 } from "react";
|
|
631
|
+
function loadPersisted(key) {
|
|
632
|
+
try {
|
|
633
|
+
const raw = localStorage.getItem(`columnWidths:${key}`);
|
|
634
|
+
return raw ? JSON.parse(raw) : null;
|
|
635
|
+
} catch {
|
|
636
|
+
return null;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
function useTableColumnWidths(options) {
|
|
640
|
+
const persistKey = options?.persist;
|
|
641
|
+
const [widths, setWidths] = useState3(() => (persistKey ? loadPersisted(persistKey) : null) ?? {});
|
|
642
|
+
useEffect3(() => {
|
|
643
|
+
if (persistKey) {
|
|
644
|
+
localStorage.setItem(`columnWidths:${persistKey}`, JSON.stringify(widths));
|
|
645
|
+
}
|
|
646
|
+
}, [widths, persistKey]);
|
|
647
|
+
const register = useCallback3((key, registerOptions) => ({
|
|
648
|
+
width: widths[key] ?? registerOptions?.defaultValue,
|
|
649
|
+
resizable: true,
|
|
650
|
+
onResizeEnd: (width, _startWidth, frValue) => {
|
|
651
|
+
if (registerOptions?.relative) {
|
|
652
|
+
setWidths((prev) => ({
|
|
653
|
+
...prev,
|
|
654
|
+
[key]: `${parseFloat(frValue.toFixed(2))}fr`
|
|
655
|
+
}));
|
|
656
|
+
} else {
|
|
657
|
+
setWidths((prev) => ({ ...prev, [key]: width }));
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}), [widths]);
|
|
661
|
+
const reset = useCallback3(() => {
|
|
662
|
+
setWidths({});
|
|
663
|
+
if (persistKey) {
|
|
664
|
+
localStorage.removeItem(`columnWidths:${persistKey}`);
|
|
665
|
+
}
|
|
666
|
+
}, [persistKey]);
|
|
667
|
+
return { register, reset };
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// src/index.ts
|
|
671
|
+
var VirtualTable2 = VirtualTable;
|
|
672
|
+
var createTableHeader2 = createTableHeader;
|
|
673
|
+
var createTableColumn2 = createTableColumn;
|
|
674
|
+
var createTableBody2 = createTableBody;
|
|
675
|
+
var createTableSkeletonRow2 = createTableSkeletonRow;
|
|
676
|
+
var createTableEmpty2 = createTableEmpty;
|
|
677
|
+
var createTableFooter2 = createTableFooter;
|
|
678
|
+
var createTableRow2 = createTableRow;
|
|
679
|
+
var useTableCache2 = useTableCache;
|
|
680
|
+
var useTableColumnWidths2 = useTableColumnWidths;
|
|
1
681
|
export {
|
|
2
|
-
useTableColumnWidths,
|
|
3
|
-
useTableCache,
|
|
4
|
-
createTableSkeletonRow,
|
|
5
|
-
createTableRow,
|
|
6
|
-
createTableHeader,
|
|
7
|
-
createTableFooter,
|
|
8
|
-
createTableEmpty,
|
|
9
|
-
createTableColumn,
|
|
10
|
-
createTableBody,
|
|
11
|
-
VirtualTable
|
|
682
|
+
useTableColumnWidths2 as useTableColumnWidths,
|
|
683
|
+
useTableCache2 as useTableCache,
|
|
684
|
+
createTableSkeletonRow2 as createTableSkeletonRow,
|
|
685
|
+
createTableRow2 as createTableRow,
|
|
686
|
+
createTableHeader2 as createTableHeader,
|
|
687
|
+
createTableFooter2 as createTableFooter,
|
|
688
|
+
createTableEmpty2 as createTableEmpty,
|
|
689
|
+
createTableColumn2 as createTableColumn,
|
|
690
|
+
createTableBody2 as createTableBody,
|
|
691
|
+
VirtualTable2 as VirtualTable
|
|
12
692
|
};
|
|
13
693
|
|
|
14
|
-
//# debugId=
|
|
694
|
+
//# debugId=35F50DE454A2B11E64756E2164756E21
|