@requence/table 1.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/VirtualTable.js.map
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../src/VirtualTable.tsx"],
|
|
4
|
-
"sourcesContent": [
|
|
5
|
-
"import {\n type CSSProperties,\n Children,\n type ComponentProps,\n type ReactElement,\n type ReactNode,\n isValidElement,\n useCallback,\n useEffect,\n useRef,\n useState,\n} from 'react'\nimport { flushSync } from 'react-dom'\nimport { twMerge } from 'tailwind-merge'\n\n/* ── Types ──────────────────────────────────────────────────────── */\n\nexport interface VirtualTableProps {\n /** Total number of rows in the dataset */\n totalCount: number\n /** Fixed height of each row in pixels */\n rowHeight: number\n /** Number of extra rows rendered above/below viewport (default: 5) */\n overscan?: number\n /** Called when the visible row range changes (for triggering page fetches) */\n onRangeChange?: (range: { start: number; end: number }) => void\n /** Additional className for the outer container */\n className?: string\n /** Accessible label for the table */\n 'aria-label'?: string\n /** Additional inline styles for the outer container */\n style?: CSSProperties\n children: ReactNode\n}\n\nexport interface VirtualTableHeaderProps {\n className?: string\n children: ReactNode\n}\n\nexport interface VirtualTableColumnProps {\n /** Column width. Number for pixels, string for CSS grid values (e.g. '1fr'). Defaults to '1fr'. */\n width?: number | string\n /** Optional className for the header cell */\n className?: string\n /** Whether this column can be resized by dragging. Default: false */\n resizable?: boolean\n /** Minimum width in pixels during resize. Default: 50 */\n minWidth?: number\n /** Maximum width in pixels during resize */\n maxWidth?: number\n /** Mark this column as transparent — the row background will not extend behind it. */\n transparent?: boolean\n /** Called when a resize drag starts */\n onResizeStart?: () => void\n /** Called when a resize drag ends with the final pixel width, original width, and equivalent fr value */\n onResizeEnd?: (width: number, startWidth: number, frValue: number) => void\n children?: ReactNode\n}\n\nexport interface VirtualTableBodyProps {\n children: (index: number) => ReactNode | null\n}\n\nexport interface VirtualTableRowProps extends ComponentProps<'div'> {}\n\nexport interface VirtualTableSkeletonRowProps extends ComponentProps<'div'> {}\n\nexport interface VirtualTableCellProps extends ComponentProps<'div'> {\n /** Show cell content only on row hover */\n showOnHover?: boolean\n /** Number of columns this cell spans */\n colSpan?: number\n}\n\nexport interface VirtualTableEmptyProps {\n className?: string\n children: ReactNode\n}\n\nexport interface VirtualTableFooterProps {\n className?: string\n children: (range: { start: number; end: number }) => ReactNode\n}\n\n/* ── Internal types ─────────────────────────────────────────────── */\n\ninterface ColumnDef {\n width?: number | string\n header?: ReactNode\n className?: string\n resizable?: boolean\n minWidth?: number\n maxWidth?: number\n transparent?: boolean\n onResizeStart?: () => void\n onResizeEnd?: (width: number, startWidth: number, frValue: number) => void\n}\n\ninterface Slots {\n header: { className?: string; columns: ColumnDef[] } | null\n body: VirtualTableBodyProps | null\n skeletonRow: VirtualTableSkeletonRowProps | null\n empty: VirtualTableEmptyProps | null\n footer: VirtualTableFooterProps | null\n}\n\n/* ── Slot system ───────────────────────────────────────────────── */\n\ninterface SlotComponent<P> {\n (props: P): ReactNode\n slot: string\n slotDefaults: Partial<P>\n}\n\nfunction asSlot<P>(slot: string, defaults?: Partial<P>): SlotComponent<P> {\n const Component = (() => null) as unknown as SlotComponent<P>\n Component.slot = slot\n Component.slotDefaults = defaults ?? ({} as Partial<P>)\n return Component\n}\n\nexport function createTableHeader(\n defaults?: Partial<VirtualTableHeaderProps>,\n): SlotComponent<VirtualTableHeaderProps> {\n return asSlot('header', defaults)\n}\n\nexport function createTableColumn(\n defaults?: Partial<VirtualTableColumnProps>,\n): SlotComponent<VirtualTableColumnProps> {\n return asSlot('column', defaults)\n}\n\nexport function createTableBody(\n defaults?: Partial<VirtualTableBodyProps>,\n): SlotComponent<VirtualTableBodyProps> {\n return asSlot('body', defaults)\n}\n\nexport function createTableSkeletonRow(\n defaults?: Partial<VirtualTableSkeletonRowProps>,\n): SlotComponent<VirtualTableSkeletonRowProps> {\n return asSlot('skeletonRow', defaults)\n}\n\nexport function createTableEmpty(\n defaults?: Partial<VirtualTableEmptyProps>,\n): SlotComponent<VirtualTableEmptyProps> {\n return asSlot('empty', defaults)\n}\n\nexport function createTableFooter(\n defaults?: Partial<VirtualTableFooterProps>,\n): SlotComponent<VirtualTableFooterProps> {\n return asSlot('footer', defaults)\n}\n\nexport function createTableRow(\n defaults?: Partial<VirtualTableRowProps>,\n): SlotComponent<VirtualTableRowProps> {\n return asSlot('row', defaults)\n}\n\n/* ── Constants ──────────────────────────────────────────────────── */\n\nconst GRID_VAR = '--vtable-grid-cols'\nconst GRID_VAR_REF = `var(${GRID_VAR})`\n\n/* ── Helpers ────────────────────────────────────────────────────── */\n\nfunction buildGridTemplate(columns: ColumnDef[]): string {\n return columns\n .map((col) => {\n if (typeof col.width === 'number') {\n return `${col.width}px`\n }\n const min = col.minWidth ? `${col.minWidth}px` : '0'\n if (typeof col.width === 'string') {\n return `minmax(${min}, ${col.width})`\n }\n return `minmax(${min}, 1fr)`\n })\n .join(' ')\n}\n\n/* ── Slot Components (render nothing — used as config markers) ── */\n\nconst VirtualTableHeader = asSlot<VirtualTableHeaderProps>('header')\nconst VirtualTableColumn = asSlot<VirtualTableColumnProps>('column')\nconst VirtualTableBody = asSlot<VirtualTableBodyProps>('body')\nconst VirtualTableRow = asSlot<VirtualTableRowProps>('row')\nconst VirtualTableSkeletonRow =\n asSlot<VirtualTableSkeletonRowProps>('skeletonRow')\nconst VirtualTableEmpty = asSlot<VirtualTableEmptyProps>('empty')\nconst VirtualTableFooter = asSlot<VirtualTableFooterProps>('footer')\n\nfunction slotIs(child: React.ReactElement, slot: string): boolean {\n return (child.type as any)?.slot === slot\n}\n\nfunction extractSlots(children: ReactNode): Slots {\n let header: Slots['header'] = null\n let body: Slots['body'] = null\n let skeletonRow: Slots['skeletonRow'] = null\n let empty: Slots['empty'] = null\n let footer: Slots['footer'] = null\n\n Children.forEach(children, (child) => {\n if (!isValidElement(child)) {\n return\n }\n\n if (slotIs(child, 'header')) {\n const defaults = (child.type as any).slotDefaults ?? {}\n const props = child.props as VirtualTableHeaderProps\n const columns: ColumnDef[] = []\n Children.forEach(props.children, (col) => {\n if (isValidElement(col) && slotIs(col, 'column')) {\n const d = (col.type as any).slotDefaults ?? {}\n const p = col.props as VirtualTableColumnProps\n columns.push({\n width: p.width ?? d.width,\n header: p.children,\n className: twMerge(d.className as string, p.className),\n resizable: p.resizable ?? d.resizable,\n minWidth: p.minWidth ?? d.minWidth,\n maxWidth: p.maxWidth ?? d.maxWidth,\n transparent: p.transparent ?? d.transparent,\n onResizeStart: p.onResizeStart ?? d.onResizeStart,\n onResizeEnd: p.onResizeEnd ?? d.onResizeEnd,\n })\n }\n })\n header = {\n className: twMerge(defaults.className as string, props.className),\n columns,\n }\n } else if (slotIs(child, 'body')) {\n body = child.props as VirtualTableBodyProps\n } else if (slotIs(child, 'skeletonRow')) {\n const defaults = (child.type as any).slotDefaults ?? {}\n const props = child.props as VirtualTableSkeletonRowProps\n skeletonRow = {\n ...defaults,\n ...props,\n className: twMerge(defaults.className as string, props.className),\n }\n } else if (slotIs(child, 'empty')) {\n const defaults = (child.type as any).slotDefaults ?? {}\n const props = child.props as VirtualTableEmptyProps\n empty = {\n ...defaults,\n ...props,\n className: twMerge(defaults.className as string, props.className),\n }\n } else if (slotIs(child, 'footer')) {\n const defaults = (child.type as any).slotDefaults ?? {}\n const props = child.props as VirtualTableFooterProps\n footer = {\n ...defaults,\n ...props,\n className: twMerge(defaults.className as string, props.className),\n }\n }\n })\n\n return { header, body, skeletonRow, empty, footer }\n}\n\n/* ── Resize Handle ─────────────────────────────────────────────── */\n\ninterface ResizeHandleProps {\n onMouseDown: (e: React.MouseEvent) => void\n}\n\nfunction ResizeHandle({ onMouseDown }: ResizeHandleProps) {\n return (\n <div\n role=\"separator\"\n aria-orientation=\"vertical\"\n className=\"resizer absolute right-0 top-0 z-10 h-full w-1.5 cursor-col-resize\"\n onMouseDown={onMouseDown}\n />\n )\n}\n\n/* ── Cell ───────────────────────────────────────────────────────── */\n\nfunction VirtualTableCell({\n className,\n style,\n showOnHover,\n colSpan,\n ...rest\n}: VirtualTableCellProps) {\n return (\n <div\n role=\"cell\"\n className={twMerge(\n 'overflow-hidden text-ellipsis whitespace-nowrap',\n showOnHover &&\n 'not-group-hover/row:*:delay-200 *:opacity-10 *:transition-opacity *:duration-300 *:ease-in-out group-hover/row:*:opacity-100',\n className,\n )}\n style={colSpan ? { gridColumn: `span ${colSpan}`, ...style } : style}\n {...rest}\n />\n )\n}\n\n/* ── Data Row ──────────────────────────────────────────────────── */\n\ninterface DataRowProps {\n index: number\n rowHeight: number\n rowProps: ComponentProps<'div'>\n children: ReactNode\n}\n\nfunction DataRow({ index, rowHeight, rowProps, children }: DataRowProps) {\n const { className, style, ...restProps } = rowProps\n return (\n <div\n role=\"row\"\n aria-rowindex={index + 1}\n className={twMerge('group/row absolute w-full', className)}\n style={{\n height: rowHeight,\n transform: `translateY(${index * rowHeight}px)`,\n display: 'grid',\n gridTemplateColumns: GRID_VAR_REF,\n alignItems: 'center',\n willChange: 'transform',\n contain: 'layout style paint',\n ...style,\n }}\n {...restProps}\n >\n {children}\n </div>\n )\n}\n\n/* ── VirtualTable ──────────────────────────────────────────────── */\n\nfunction VirtualTableRoot({\n totalCount,\n rowHeight,\n overscan = 5,\n onRangeChange,\n className,\n style: styleProp,\n 'aria-label': ariaLabel,\n children,\n}: VirtualTableProps) {\n const scrollRef = useRef<HTMLDivElement>(null)\n const [visibleRange, setVisibleRange] = useState({ start: 0, end: 0 })\n const prevRangeRef = useRef({ start: 0, end: 0 })\n\n const { header, body, skeletonRow, empty, footer } = extractSlots(children)\n const columns = header?.columns ?? []\n const gridTemplate = buildGridTemplate(columns)\n const renderRow = body?.children ?? (() => null)\n\n // ── Scroll handler (synchronous for flicker-free scrolling) ────\n const calculateRange = useCallback(() => {\n const el = scrollRef.current\n if (!el || totalCount === 0) {\n return\n }\n\n const scrollTop = el.scrollTop\n const viewportHeight = el.clientHeight\n\n const rawStart = Math.floor(scrollTop / rowHeight)\n const rawEnd = Math.ceil((scrollTop + viewportHeight) / rowHeight)\n\n const start = Math.max(0, rawStart - overscan)\n const end = Math.min(totalCount, rawEnd + overscan)\n\n const prev = prevRangeRef.current\n if (prev.start !== start || prev.end !== end) {\n prevRangeRef.current = { start, end }\n flushSync(() => setVisibleRange({ start, end }))\n }\n }, [totalCount, rowHeight, overscan])\n\n const handleScroll = useCallback(() => {\n calculateRange()\n }, [calculateRange])\n\n // Recalculate on totalCount/rowHeight changes\n useEffect(() => {\n calculateRange()\n }, [calculateRange])\n\n // Notify consumer when visible range changes\n useEffect(() => {\n onRangeChange?.(visibleRange)\n }, [visibleRange, onRangeChange])\n\n // ── Column resize ──────────────────────────────────────────────\n const handleResizeMouseDown = useCallback(\n (columnIndex: number, e: React.MouseEvent) => {\n e.preventDefault()\n\n const container = scrollRef.current\n if (!container) {\n return\n }\n\n const col = columns[columnIndex]\n const headerCells = container.querySelectorAll('[role=\"columnheader\"]')\n\n // Resolve current pixel width (handles fr columns)\n const startWidth =\n headerCells[columnIndex]?.getBoundingClientRect().width ??\n (typeof col.width === 'number' ? col.width : 100)\n const startX = e.clientX\n\n const minW = col.minWidth ?? 50\n const maxW = col.maxWidth ?? Infinity\n\n // Prevent the resized column from pushing the table beyond the container\n const otherColumnsMinWidth = columns.reduce((sum, c, i) => {\n if (i === columnIndex) {\n return sum\n }\n if (typeof c.width === 'number') {\n return sum + c.width\n }\n return sum + (c.minWidth ?? 0)\n }, 0)\n const maxAllowedWidth = container.clientWidth - otherColumnsMinWidth\n\n let currentWidth = startWidth\n\n col.onResizeStart?.()\n\n const prevCursor = document.body.style.cursor\n document.body.style.cursor = 'col-resize'\n\n const handleMouseMove = (moveEvent: MouseEvent) => {\n const delta = moveEvent.clientX - startX\n currentWidth = Math.min(\n maxW,\n maxAllowedWidth,\n Math.max(minW, startWidth + delta),\n )\n\n // Only override the dragged column — preserve original values for others\n const template = columns\n .map((c, i) => {\n if (i === columnIndex) {\n return `${currentWidth}px`\n }\n return buildGridTemplate([c])\n })\n .join(' ')\n container.style.setProperty(GRID_VAR, template)\n }\n\n const handleMouseUp = () => {\n document.body.style.cursor = prevCursor\n document.removeEventListener('mousemove', handleMouseMove)\n document.removeEventListener('mouseup', handleMouseUp)\n\n // Compute equivalent fr value from other fr columns' pixel widths\n const allCells = container.querySelectorAll('[role=\"columnheader\"]')\n let otherFrPx = 0\n let otherFrUnits = 0\n columns.forEach((c, i) => {\n if (i === columnIndex) {\n return\n }\n if (typeof c.width !== 'number') {\n otherFrPx += allCells[i]?.getBoundingClientRect().width ?? 0\n otherFrUnits +=\n typeof c.width === 'string' ? parseFloat(c.width) || 1 : 1\n }\n })\n const frValue =\n otherFrPx > 0 ? (currentWidth / otherFrPx) * otherFrUnits : 1\n\n col.onResizeEnd?.(currentWidth, startWidth, frValue)\n }\n\n document.addEventListener('mousemove', handleMouseMove)\n document.addEventListener('mouseup', handleMouseUp)\n },\n [columns],\n )\n\n // ── Empty state ────────────────────────────────────────────────\n if (totalCount === 0 && empty) {\n return (\n <div\n role=\"table\"\n aria-label={ariaLabel}\n className={twMerge('flex flex-col overflow-hidden', className)}\n style={{ [GRID_VAR]: gridTemplate, ...styleProp } as CSSProperties}\n >\n {/* Header */}\n <div\n role=\"rowgroup\"\n className={twMerge('sticky top-0 z-10', header?.className)}\n >\n <div\n role=\"row\"\n style={{\n display: 'grid',\n gridTemplateColumns: GRID_VAR_REF,\n alignItems: 'center',\n height: rowHeight,\n }}\n >\n {columns.map((col, i) => (\n <div\n key={i}\n role=\"columnheader\"\n className={twMerge(\n 'whitespace-nowrap',\n col.resizable && 'relative',\n col.className,\n )}\n >\n {col.header}\n {col.resizable && (\n <ResizeHandle\n onMouseDown={(e) => handleResizeMouseDown(i, e)}\n />\n )}\n </div>\n ))}\n </div>\n </div>\n\n {/* Empty */}\n <div\n className={twMerge(\n 'flex items-center justify-center',\n empty.className,\n )}\n >\n {empty.children}\n </div>\n </div>\n )\n }\n\n // ── Build visible rows ─────────────────────────────────────────\n const rows: ReactNode[] = []\n for (let i = visibleRange.start; i < visibleRange.end; i++) {\n const content = renderRow(i)\n if (content === null) {\n if (skeletonRow) {\n const { children: skeletonContent, ...skeletonProps } = skeletonRow\n rows.push(\n <DataRow\n key={`row-${i}`}\n index={i}\n rowHeight={rowHeight}\n rowProps={skeletonProps}\n >\n {skeletonContent}\n </DataRow>,\n )\n }\n } else {\n const rowElement = content as ReactElement\n const rowDefaults = (rowElement.type as any)?.slotDefaults ?? {}\n const rawProps = rowElement.props as VirtualTableRowProps\n const { children: cellContent, ...userRowProps } = rawProps\n const rowProps = {\n ...rowDefaults,\n ...userRowProps,\n className: twMerge(\n rowDefaults.className as string,\n userRowProps.className,\n ),\n }\n\n rows.push(\n <DataRow\n key={`row-${i}`}\n index={i}\n rowHeight={rowHeight}\n rowProps={rowProps}\n >\n {cellContent}\n </DataRow>,\n )\n }\n }\n\n const totalHeight = totalCount * rowHeight\n\n return (\n <>\n <div\n role=\"table\"\n aria-label={ariaLabel}\n aria-rowcount={totalCount}\n ref={scrollRef}\n onScroll={handleScroll}\n className={twMerge('relative overflow-auto', className)}\n style={{ [GRID_VAR]: gridTemplate, ...styleProp } as CSSProperties}\n >\n {/* Header */}\n <div\n role=\"rowgroup\"\n className={twMerge('sticky top-0 z-10', header?.className)}\n >\n <div\n role=\"row\"\n style={{\n display: 'grid',\n gridTemplateColumns: GRID_VAR_REF,\n alignItems: 'center',\n }}\n >\n {columns.map((col, i) => (\n <div\n key={i}\n role=\"columnheader\"\n className={twMerge(\n 'whitespace-nowrap',\n col.resizable && 'relative',\n col.className,\n )}\n >\n {col.header}\n {col.resizable && (\n <ResizeHandle\n onMouseDown={(e) => handleResizeMouseDown(i, e)}\n />\n )}\n </div>\n ))}\n </div>\n </div>\n\n {/* Body sentinel + visible rows */}\n <div\n role=\"rowgroup\"\n className=\"relative\"\n style={{ height: totalHeight }}\n >\n {rows}\n </div>\n </div>\n\n {/* Footer */}\n {footer && totalCount > 0 && (\n <div className={footer.className}>{footer.children(visibleRange)}</div>\n )}\n </>\n )\n}\n\n/* ── Compound export ───────────────────────────────────────────── */\n\nexport const VirtualTable = Object.assign(VirtualTableRoot, {\n Header: VirtualTableHeader,\n Column: VirtualTableColumn,\n Body: VirtualTableBody,\n SkeletonRow: VirtualTableSkeletonRow,\n Row: VirtualTableRow,\n Cell: VirtualTableCell,\n Empty: VirtualTableEmpty,\n Footer: VirtualTableFooter,\n})\n"
|
|
6
|
-
],
|
|
7
|
-
"mappings": "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",
|
|
8
|
-
"debugId": "D3E702252F525EB164756E2164756E21",
|
|
9
|
-
"names": []
|
|
10
|
-
}
|
package/dist/useTableCache.js
DELETED
|
@@ -1,232 +0,0 @@
|
|
|
1
|
-
// src/useTableCache.ts
|
|
2
|
-
import { useCallback, useEffect, useRef, useState } from "react";
|
|
3
|
-
var cacheMap = new Map;
|
|
4
|
-
function resolveCache(id, options, pageSize) {
|
|
5
|
-
let state = cacheMap.get(id);
|
|
6
|
-
if (!state) {
|
|
7
|
-
state = {
|
|
8
|
-
pages: new Map,
|
|
9
|
-
totalCount: 0,
|
|
10
|
-
inflight: new Set,
|
|
11
|
-
compare: options.compare,
|
|
12
|
-
fetchItems: options.fetchItems,
|
|
13
|
-
fetchCount: options.fetchCount,
|
|
14
|
-
getItemId: options.getItemId,
|
|
15
|
-
promise: null,
|
|
16
|
-
knownIds: new Set,
|
|
17
|
-
fetchCountTimer: null
|
|
18
|
-
};
|
|
19
|
-
cacheMap.set(id, state);
|
|
20
|
-
const s = state;
|
|
21
|
-
s.promise = options.fetchItems(0, pageSize).then((result) => {
|
|
22
|
-
if (cacheMap.get(id) === s) {
|
|
23
|
-
s.pages.set(0, result.items);
|
|
24
|
-
s.totalCount = result.total;
|
|
25
|
-
for (const item of result.items) {
|
|
26
|
-
s.knownIds.add(s.getItemId(item));
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
}).then(() => {
|
|
30
|
-
s.promise = null;
|
|
31
|
-
}).catch((error) => {
|
|
32
|
-
console.error(error);
|
|
33
|
-
throw error;
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
state.fetchItems = options.fetchItems;
|
|
37
|
-
state.fetchCount = options.fetchCount;
|
|
38
|
-
state.compare = options.compare;
|
|
39
|
-
state.getItemId = options.getItemId;
|
|
40
|
-
return state;
|
|
41
|
-
}
|
|
42
|
-
function useTableCache(key, options) {
|
|
43
|
-
const { pageSize, getItemId, compare, fetchItems, fetchCount } = options;
|
|
44
|
-
const [, forceRender] = useState(0);
|
|
45
|
-
const [iteration, setIteration] = useState(0);
|
|
46
|
-
const rerender = useCallback(() => {
|
|
47
|
-
forceRender((c) => c + 1);
|
|
48
|
-
}, []);
|
|
49
|
-
const activeKey = [key, iteration].join("-");
|
|
50
|
-
const currentCache = resolveCache(activeKey, { fetchItems, fetchCount, compare, getItemId }, pageSize);
|
|
51
|
-
const cacheRef = useRef(currentCache);
|
|
52
|
-
cacheRef.current = currentCache;
|
|
53
|
-
if (currentCache.promise) {
|
|
54
|
-
throw currentCache.promise;
|
|
55
|
-
}
|
|
56
|
-
useEffect(() => () => {
|
|
57
|
-
for (const k of cacheMap.keys()) {
|
|
58
|
-
if (k.startsWith(key)) {
|
|
59
|
-
cacheMap.delete(k);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}, [key]);
|
|
63
|
-
const fetchPage = useCallback((pageIndex) => {
|
|
64
|
-
const c = cacheRef.current;
|
|
65
|
-
if (c.pages.has(pageIndex) || c.inflight.has(pageIndex)) {
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
const offset = pageIndex * pageSize;
|
|
69
|
-
c.inflight.add(pageIndex);
|
|
70
|
-
c.fetchItems(offset, pageSize).then((result) => {
|
|
71
|
-
if (cacheRef.current === c) {
|
|
72
|
-
c.pages.set(pageIndex, result.items);
|
|
73
|
-
c.totalCount = result.total;
|
|
74
|
-
c.inflight.delete(pageIndex);
|
|
75
|
-
for (const item of result.items) {
|
|
76
|
-
c.knownIds.add(c.getItemId(item));
|
|
77
|
-
}
|
|
78
|
-
rerender();
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
|
-
}, [pageSize, rerender]);
|
|
82
|
-
const getItem = useCallback((index) => {
|
|
83
|
-
const c = cacheRef.current;
|
|
84
|
-
const pageIndex = Math.floor(index / pageSize);
|
|
85
|
-
const page = c.pages.get(pageIndex);
|
|
86
|
-
if (!page) {
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
const offsetInPage = index - pageIndex * pageSize;
|
|
90
|
-
return page[offsetInPage];
|
|
91
|
-
}, [pageSize]);
|
|
92
|
-
const handleRangeChange = useCallback((range) => {
|
|
93
|
-
const c = cacheRef.current;
|
|
94
|
-
if (c.totalCount === 0) {
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
const startPage = Math.floor(range.start / pageSize);
|
|
98
|
-
const endPage = Math.floor(Math.min(range.end, c.totalCount - 1) / pageSize);
|
|
99
|
-
for (let p = startPage;p <= endPage; p++) {
|
|
100
|
-
fetchPage(p);
|
|
101
|
-
}
|
|
102
|
-
}, [pageSize, fetchPage]);
|
|
103
|
-
const debouncedFetchCount = useCallback(() => {
|
|
104
|
-
const c = cacheRef.current;
|
|
105
|
-
if (!c.fetchCount) {
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
if (c.fetchCountTimer) {
|
|
109
|
-
clearTimeout(c.fetchCountTimer);
|
|
110
|
-
}
|
|
111
|
-
c.fetchCountTimer = setTimeout(() => {
|
|
112
|
-
const current = cacheRef.current;
|
|
113
|
-
current.fetchCountTimer = null;
|
|
114
|
-
current.fetchCount?.().then((total) => {
|
|
115
|
-
if (cacheRef.current === current) {
|
|
116
|
-
current.totalCount = total;
|
|
117
|
-
rerender();
|
|
118
|
-
}
|
|
119
|
-
});
|
|
120
|
-
}, 150);
|
|
121
|
-
}, [rerender]);
|
|
122
|
-
const upsert = useCallback((item) => {
|
|
123
|
-
const c = cacheRef.current;
|
|
124
|
-
const id = getItemId(item);
|
|
125
|
-
for (const [, page] of c.pages) {
|
|
126
|
-
const idx = page.findIndex((p) => getItemId(p) === id);
|
|
127
|
-
if (idx !== -1) {
|
|
128
|
-
page[idx] = item;
|
|
129
|
-
rerender();
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
if (c.knownIds.has(id)) {
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
c.knownIds.add(id);
|
|
137
|
-
let inserted = false;
|
|
138
|
-
const sortedPageIndices = [...c.pages.keys()].sort((a, b) => a - b);
|
|
139
|
-
for (const pageIndex of sortedPageIndices) {
|
|
140
|
-
const page = c.pages.get(pageIndex);
|
|
141
|
-
if (page.length > 0 && c.compare(item, page[0]) <= 0) {
|
|
142
|
-
page.unshift(item);
|
|
143
|
-
inserted = true;
|
|
144
|
-
invalidateAfter(c, pageIndex);
|
|
145
|
-
break;
|
|
146
|
-
}
|
|
147
|
-
if (page.length > 0) {
|
|
148
|
-
const lastItem = page[page.length - 1];
|
|
149
|
-
if (c.compare(item, lastItem) <= 0) {
|
|
150
|
-
let lo = 0;
|
|
151
|
-
let hi = page.length;
|
|
152
|
-
while (lo < hi) {
|
|
153
|
-
const mid = lo + hi >>> 1;
|
|
154
|
-
if (c.compare(item, page[mid]) <= 0) {
|
|
155
|
-
hi = mid;
|
|
156
|
-
} else {
|
|
157
|
-
lo = mid + 1;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
page.splice(lo, 0, item);
|
|
161
|
-
inserted = true;
|
|
162
|
-
invalidateAfter(c, pageIndex);
|
|
163
|
-
break;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
if (!inserted) {
|
|
168
|
-
if (sortedPageIndices.length > 0) {
|
|
169
|
-
const firstPageIndex = sortedPageIndices[0];
|
|
170
|
-
const firstPage = c.pages.get(firstPageIndex);
|
|
171
|
-
if (firstPage.length > 0 && c.compare(item, firstPage[0]) <= 0) {
|
|
172
|
-
firstPage.unshift(item);
|
|
173
|
-
invalidateAfter(c, firstPageIndex);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
if (c.fetchCount) {
|
|
178
|
-
debouncedFetchCount();
|
|
179
|
-
} else {
|
|
180
|
-
c.totalCount += 1;
|
|
181
|
-
}
|
|
182
|
-
rerender();
|
|
183
|
-
}, [getItemId, rerender, debouncedFetchCount]);
|
|
184
|
-
const remove = useCallback((id) => {
|
|
185
|
-
const c = cacheRef.current;
|
|
186
|
-
let removedFromPage = null;
|
|
187
|
-
c.knownIds.delete(id);
|
|
188
|
-
for (const [pageIndex, page] of c.pages) {
|
|
189
|
-
const idx = page.findIndex((item) => getItemId(item) === id);
|
|
190
|
-
if (idx !== -1) {
|
|
191
|
-
page.splice(idx, 1);
|
|
192
|
-
removedFromPage = pageIndex;
|
|
193
|
-
break;
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
c.totalCount = Math.max(0, c.totalCount - 1);
|
|
197
|
-
if (removedFromPage !== null) {
|
|
198
|
-
invalidateAfter(c, removedFromPage);
|
|
199
|
-
}
|
|
200
|
-
rerender();
|
|
201
|
-
}, [getItemId, rerender]);
|
|
202
|
-
const reset = useCallback(() => {
|
|
203
|
-
const c = cacheRef.current;
|
|
204
|
-
if (c.fetchCountTimer) {
|
|
205
|
-
clearTimeout(c.fetchCountTimer);
|
|
206
|
-
c.fetchCountTimer = null;
|
|
207
|
-
}
|
|
208
|
-
setIteration((iteration2) => iteration2 + 1);
|
|
209
|
-
rerender();
|
|
210
|
-
}, [rerender]);
|
|
211
|
-
return {
|
|
212
|
-
totalCount: currentCache.totalCount,
|
|
213
|
-
getItem,
|
|
214
|
-
handleRangeChange,
|
|
215
|
-
upsert,
|
|
216
|
-
remove,
|
|
217
|
-
reset,
|
|
218
|
-
loading: currentCache.inflight.size > 0
|
|
219
|
-
};
|
|
220
|
-
}
|
|
221
|
-
function invalidateAfter(cache, afterPageIndex) {
|
|
222
|
-
for (const key of cache.pages.keys()) {
|
|
223
|
-
if (key > afterPageIndex) {
|
|
224
|
-
cache.pages.delete(key);
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
export {
|
|
229
|
-
useTableCache
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
//# debugId=8F5E9DD1F36E2A0164756E2164756E21
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../src/useTableCache.ts"],
|
|
4
|
-
"sourcesContent": [
|
|
5
|
-
"import { useCallback, useEffect, useRef, useState } from 'react'\n\n/* ── Types ──────────────────────────────────────────────────────── */\n\nexport interface UseTableCacheOptions<T> {\n /** Number of rows per page */\n pageSize: number\n /** Extract unique ID from an item (for update/remove) */\n getItemId: (item: T) => string\n /**\n * Comparator for sort order. Used by `upsert()` to place subscription\n * items at the correct position within cached pages.\n * Return negative if a comes before b, positive if after, 0 if equal.\n */\n compare: (a: T, b: T) => number\n /**\n * Fetch a page of data. Called automatically when the visible range\n * enters an unfetched page. Must return the items and the total count.\n *\n * ── Suspense behavior ──\n * • First call (no cached pages): the returned promise is THROWN\n * → component suspends → Suspense fallback is shown.\n * • Subsequent calls (at least one page cached): non-blocking\n * → cache.loading becomes true → skeleton rows shown for missing indices.\n */\n fetchItems: (\n offset: number,\n limit: number,\n ) => Promise<{ items: T[]; total: number }>\n /**\n * Optional. Fetch just the total count from the server.\n * Called (debounced) when an upsert arrives for an unknown ID,\n * where the cache cannot determine if it's a new item or an\n * update to a never-fetched item.\n * If not provided, the cache falls back to incrementing totalCount\n * (which may drift when items on non-cached pages are updated).\n */\n fetchCount?: () => Promise<number>\n}\n\nexport interface TableCache<T> {\n /** Current total count (updated by fetch results and mutations) */\n totalCount: number\n /** Get item at absolute index. Returns undefined if page not yet fetched. */\n getItem: (index: number) => T | undefined\n /** Pass this to VirtualTable.onRangeChange — triggers page fetches */\n handleRangeChange: (range: { start: number; end: number }) => void\n /**\n * Upsert an item: if an item with the same ID already exists in a cached\n * page it is updated in-place (no position change, no totalCount change).\n * If the ID is known from a previous fetch but not on a cached page,\n * the item is ignored (it's an update to a non-visible item).\n * Otherwise the item is inserted at its correct sorted position (using\n * `compare`) and — if `fetchCount` was provided — a debounced count\n * re-fetch is triggered to get the authoritative total from the server.\n */\n upsert: (item: T) => void\n /**\n * Remove item by ID. Decrements totalCount.\n * Removes from all cached pages. Invalidates pages after removal point.\n */\n remove: (id: string) => void\n /** Clear all cached pages. Next render will re-suspend. */\n reset: () => void\n /**\n * true when a scroll-triggered page fetch is in-flight.\n * false during the initial Suspense-suspended fetch (Suspense handles that).\n * Use this to show a small loading indicator in the header/footer.\n */\n loading: boolean\n}\n\n/* ── Cache state ─────────────────────────────────────────────── */\n\ninterface CacheState<T> {\n pages: Map<number, T[]>\n totalCount: number\n inflight: Set<number>\n compare: UseTableCacheOptions<T>['compare']\n fetchItems: UseTableCacheOptions<T>['fetchItems']\n fetchCount: UseTableCacheOptions<T>['fetchCount']\n getItemId: UseTableCacheOptions<T>['getItemId']\n promise: Promise<void> | null\n /**\n * Tracks every item ID the cache has ever seen in the current result set.\n * Survives page eviction. Used by `upsert()` to distinguish updates to\n * non-cached items (no count change) from genuinely new items.\n */\n knownIds: Set<string>\n /** Timer handle for the debounced fetchCount call */\n fetchCountTimer: ReturnType<typeof setTimeout> | null\n}\n\n/* ── Module-level cache keyed by useId() ─────────────────────── */\n// useId() survives Suspense throws, so the map key is stable.\n// The `deps` string on each entry tracks when the dataset parameters\n// change (sort, filter, scope). A deps mismatch replaces the entry.\n\nconst cacheMap = new Map<string, CacheState<any>>()\n\nfunction resolveCache<T>(\n id: string,\n options: Pick<\n UseTableCacheOptions<T>,\n 'fetchItems' | 'fetchCount' | 'compare' | 'getItemId'\n >,\n pageSize: number,\n): CacheState<T> {\n let state = cacheMap.get(id)\n if (!state) {\n state = {\n pages: new Map(),\n totalCount: 0,\n inflight: new Set(),\n compare: options.compare,\n fetchItems: options.fetchItems,\n fetchCount: options.fetchCount,\n getItemId: options.getItemId,\n promise: null,\n knownIds: new Set(),\n fetchCountTimer: null,\n }\n cacheMap.set(id, state)\n\n // Kick off initial fetch immediately\n const s = state\n s.promise = options\n .fetchItems(0, pageSize)\n .then((result) => {\n // Only apply if this entry is still current\n if (cacheMap.get(id) === s) {\n s.pages.set(0, result.items)\n s.totalCount = result.total\n for (const item of result.items) {\n s.knownIds.add(s.getItemId(item))\n }\n }\n })\n .then(() => {\n s.promise = null\n })\n .catch((error) => {\n console.error(error)\n throw error\n })\n }\n\n state.fetchItems = options.fetchItems\n state.fetchCount = options.fetchCount\n state.compare = options.compare\n state.getItemId = options.getItemId\n\n return state\n}\n\n/* ── Hook ────────────────────────────────────────────────────── */\n\nexport function useTableCache<T>(\n key: string,\n options: UseTableCacheOptions<T>,\n): TableCache<T> {\n const { pageSize, getItemId, compare, fetchItems, fetchCount } = options\n\n const [, forceRender] = useState(0)\n const [iteration, setIteration] = useState(0)\n const rerender = useCallback(() => {\n forceRender((c) => c + 1)\n }, [])\n\n const activeKey = [key, iteration].join('-')\n // ── Get or create cache (useId() survives Suspense) ────────────\n const currentCache = resolveCache(\n activeKey,\n { fetchItems, fetchCount, compare, getItemId },\n pageSize,\n )\n\n // Keep a ref so mutation callbacks always access the current cache\n const cacheRef = useRef(currentCache)\n cacheRef.current = currentCache\n\n // ── Suspense: throw if initial fetch is pending ────────────────\n if (currentCache.promise) {\n throw currentCache.promise\n }\n\n // ── Cleanup all cache entries for this key on unmount ──────────\n useEffect(\n () => () => {\n for (const k of cacheMap.keys()) {\n if (k.startsWith(key)) {\n cacheMap.delete(k)\n }\n }\n },\n [key],\n )\n\n // ── Fetch a page (non-blocking, for scroll-triggered loads) ────\n const fetchPage = useCallback(\n (pageIndex: number) => {\n const c = cacheRef.current\n if (c.pages.has(pageIndex) || c.inflight.has(pageIndex)) {\n return\n }\n\n const offset = pageIndex * pageSize\n c.inflight.add(pageIndex)\n\n c.fetchItems(offset, pageSize).then((result) => {\n if (cacheRef.current === c) {\n c.pages.set(pageIndex, result.items)\n c.totalCount = result.total\n c.inflight.delete(pageIndex)\n for (const item of result.items) {\n c.knownIds.add(c.getItemId(item))\n }\n rerender()\n }\n })\n },\n [pageSize, rerender],\n )\n\n // ── getItem ────────────────────────────────────────────────────\n const getItem = useCallback(\n (index: number): T | undefined => {\n const c = cacheRef.current\n const pageIndex = Math.floor(index / pageSize)\n const page = c.pages.get(pageIndex)\n if (!page) {\n return undefined\n }\n const offsetInPage = index - pageIndex * pageSize\n return page[offsetInPage]\n },\n [pageSize],\n )\n\n // ── handleRangeChange ──────────────────────────────────────────\n const handleRangeChange = useCallback(\n (range: { start: number; end: number }) => {\n const c = cacheRef.current\n if (c.totalCount === 0) {\n return\n }\n\n const startPage = Math.floor(range.start / pageSize)\n const endPage = Math.floor(\n Math.min(range.end, c.totalCount - 1) / pageSize,\n )\n\n for (let p = startPage; p <= endPage; p++) {\n fetchPage(p)\n }\n },\n [pageSize, fetchPage],\n )\n\n // ── debounced fetchCount ────────────────────────────────────────\n const debouncedFetchCount = useCallback(() => {\n const c = cacheRef.current\n if (!c.fetchCount) {\n return\n }\n\n if (c.fetchCountTimer) {\n clearTimeout(c.fetchCountTimer)\n }\n\n c.fetchCountTimer = setTimeout(() => {\n const current = cacheRef.current\n current.fetchCountTimer = null\n current.fetchCount?.().then((total) => {\n if (cacheRef.current === current) {\n current.totalCount = total\n rerender()\n }\n })\n }, 150)\n }, [rerender])\n\n // ── upsert (sort-aware insert or in-place update) ──────────────\n const upsert = useCallback(\n (item: T) => {\n const c = cacheRef.current\n const id = getItemId(item)\n\n // ── Check for existing item on cached page → update in-place\n for (const [, page] of c.pages) {\n const idx = page.findIndex((p) => getItemId(p) === id)\n if (idx !== -1) {\n page[idx] = item\n rerender()\n return\n }\n }\n\n // ── Known ID on a non-cached page → skip (no count change)\n if (c.knownIds.has(id)) {\n return\n }\n\n // ── Unknown ID → genuinely new to this result set ──────────\n c.knownIds.add(id)\n\n let inserted = false\n\n const sortedPageIndices = [...c.pages.keys()].sort((a, b) => a - b)\n\n for (const pageIndex of sortedPageIndices) {\n const page = c.pages.get(pageIndex)!\n\n if (page.length > 0 && c.compare(item, page[0]) <= 0) {\n page.unshift(item)\n inserted = true\n invalidateAfter(c, pageIndex)\n break\n }\n\n if (page.length > 0) {\n const lastItem = page[page.length - 1]\n if (c.compare(item, lastItem) <= 0) {\n let lo = 0\n let hi = page.length\n while (lo < hi) {\n const mid = (lo + hi) >>> 1\n if (c.compare(item, page[mid]) <= 0) {\n hi = mid\n } else {\n lo = mid + 1\n }\n }\n page.splice(lo, 0, item)\n inserted = true\n invalidateAfter(c, pageIndex)\n break\n }\n }\n }\n\n if (!inserted) {\n if (sortedPageIndices.length > 0) {\n const firstPageIndex = sortedPageIndices[0]\n const firstPage = c.pages.get(firstPageIndex)!\n if (firstPage.length > 0 && c.compare(item, firstPage[0]) <= 0) {\n firstPage.unshift(item)\n invalidateAfter(c, firstPageIndex)\n }\n }\n }\n\n if (c.fetchCount) {\n // Ask the server for the authoritative count (debounced)\n debouncedFetchCount()\n } else {\n // Fallback: optimistic increment (may drift)\n c.totalCount += 1\n }\n\n rerender()\n },\n [getItemId, rerender, debouncedFetchCount],\n )\n\n // ── remove ─────────────────────────────────────────────────────\n const remove = useCallback(\n (id: string) => {\n const c = cacheRef.current\n let removedFromPage: number | null = null\n\n c.knownIds.delete(id)\n\n for (const [pageIndex, page] of c.pages) {\n const idx = page.findIndex((item) => getItemId(item) === id)\n if (idx !== -1) {\n page.splice(idx, 1)\n removedFromPage = pageIndex\n break\n }\n }\n\n c.totalCount = Math.max(0, c.totalCount - 1)\n\n if (removedFromPage !== null) {\n invalidateAfter(c, removedFromPage)\n }\n\n rerender()\n },\n [getItemId, rerender],\n )\n\n // ── reset ──────────────────────────────────────────────────────\n const reset = useCallback(() => {\n const c = cacheRef.current\n if (c.fetchCountTimer) {\n clearTimeout(c.fetchCountTimer)\n c.fetchCountTimer = null\n }\n setIteration((iteration) => iteration + 1)\n rerender()\n }, [rerender])\n\n return {\n totalCount: currentCache.totalCount,\n getItem,\n handleRangeChange,\n upsert,\n remove,\n reset,\n loading: currentCache.inflight.size > 0,\n }\n}\n\n/* ── Helpers ──────────────────────────────────────────────────── */\n\nfunction invalidateAfter<T>(cache: CacheState<T>, afterPageIndex: number) {\n for (const key of cache.pages.keys()) {\n if (key > afterPageIndex) {\n cache.pages.delete(key)\n }\n }\n}\n"
|
|
6
|
-
],
|
|
7
|
-
"mappings": "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",
|
|
8
|
-
"debugId": "8F5E9DD1F36E2A0164756E2164756E21",
|
|
9
|
-
"names": []
|
|
10
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
// src/useTableColumnWidths.ts
|
|
2
|
-
import { useCallback, useEffect, useState } from "react";
|
|
3
|
-
function loadPersisted(key) {
|
|
4
|
-
try {
|
|
5
|
-
const raw = localStorage.getItem(`columnWidths:${key}`);
|
|
6
|
-
return raw ? JSON.parse(raw) : null;
|
|
7
|
-
} catch {
|
|
8
|
-
return null;
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
function useTableColumnWidths(options) {
|
|
12
|
-
const persistKey = options?.persist;
|
|
13
|
-
const [widths, setWidths] = useState(() => (persistKey ? loadPersisted(persistKey) : null) ?? {});
|
|
14
|
-
useEffect(() => {
|
|
15
|
-
if (persistKey) {
|
|
16
|
-
localStorage.setItem(`columnWidths:${persistKey}`, JSON.stringify(widths));
|
|
17
|
-
}
|
|
18
|
-
}, [widths, persistKey]);
|
|
19
|
-
const register = useCallback((key, registerOptions) => ({
|
|
20
|
-
width: widths[key] ?? registerOptions?.defaultValue,
|
|
21
|
-
resizable: true,
|
|
22
|
-
onResizeEnd: (width, _startWidth, frValue) => {
|
|
23
|
-
if (registerOptions?.relative) {
|
|
24
|
-
setWidths((prev) => ({
|
|
25
|
-
...prev,
|
|
26
|
-
[key]: `${parseFloat(frValue.toFixed(2))}fr`
|
|
27
|
-
}));
|
|
28
|
-
} else {
|
|
29
|
-
setWidths((prev) => ({ ...prev, [key]: width }));
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
}), [widths]);
|
|
33
|
-
const reset = useCallback(() => {
|
|
34
|
-
setWidths({});
|
|
35
|
-
if (persistKey) {
|
|
36
|
-
localStorage.removeItem(`columnWidths:${persistKey}`);
|
|
37
|
-
}
|
|
38
|
-
}, [persistKey]);
|
|
39
|
-
return { register, reset };
|
|
40
|
-
}
|
|
41
|
-
export {
|
|
42
|
-
useTableColumnWidths
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
//# debugId=114E438FE0AE9DE064756E2164756E21
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../src/useTableColumnWidths.ts"],
|
|
4
|
-
"sourcesContent": [
|
|
5
|
-
"import { useCallback, useEffect, useState } from 'react'\n\ninterface UseColumnWidthsOptions {\n /** Persist widths to localStorage under `columnWidths:{persist}` */\n persist?: string\n}\n\ninterface RegisterOptions {\n /** Default width. Number for pixels, string for CSS grid values (e.g. '1fr'). */\n defaultValue?: number | string\n /** Store resized widths as fr values instead of pixels. Default: false */\n relative?: boolean\n}\n\nfunction loadPersisted(key: string): Record<string, number | string> | null {\n try {\n const raw = localStorage.getItem(`columnWidths:${key}`)\n return raw ? (JSON.parse(raw) as Record<string, number | string>) : null\n } catch {\n return null\n }\n}\n\nexport function useTableColumnWidths(options?: UseColumnWidthsOptions) {\n const persistKey = options?.persist\n\n const [widths, setWidths] = useState<Record<string, number | string>>(\n () => (persistKey ? loadPersisted(persistKey) : null) ?? {},\n )\n\n useEffect(() => {\n if (persistKey) {\n localStorage.setItem(`columnWidths:${persistKey}`, JSON.stringify(widths))\n }\n }, [widths, persistKey])\n\n const register = useCallback(\n (key: string, registerOptions?: RegisterOptions) => ({\n width: widths[key] ?? registerOptions?.defaultValue,\n resizable: true as const,\n onResizeEnd: (\n width: number,\n _startWidth: number,\n frValue: number,\n ) => {\n if (registerOptions?.relative) {\n setWidths((prev) => ({\n ...prev,\n [key]: `${parseFloat(frValue.toFixed(2))}fr`,\n }))\n } else {\n setWidths((prev) => ({ ...prev, [key]: width }))\n }\n },\n }),\n [widths],\n )\n\n const reset = useCallback(() => {\n setWidths({})\n if (persistKey) {\n localStorage.removeItem(`columnWidths:${persistKey}`)\n }\n }, [persistKey])\n\n return { register, reset }\n}\n"
|
|
6
|
-
],
|
|
7
|
-
"mappings": ";AAAA;AAcA,SAAS,aAAa,CAAC,KAAqD;AAAA,EAC1E,IAAI;AAAA,IACF,MAAM,MAAM,aAAa,QAAQ,gBAAgB,KAAK;AAAA,IACtD,OAAO,MAAO,KAAK,MAAM,GAAG,IAAwC;AAAA,IACpE,MAAM;AAAA,IACN,OAAO;AAAA;AAAA;AAIJ,SAAS,oBAAoB,CAAC,SAAkC;AAAA,EACrE,MAAM,aAAa,SAAS;AAAA,EAE5B,OAAO,QAAQ,aAAa,SAC1B,OAAO,aAAa,cAAc,UAAU,IAAI,SAAS,CAAC,CAC5D;AAAA,EAEA,UAAU,MAAM;AAAA,IACd,IAAI,YAAY;AAAA,MACd,aAAa,QAAQ,gBAAgB,cAAc,KAAK,UAAU,MAAM,CAAC;AAAA,IAC3E;AAAA,KACC,CAAC,QAAQ,UAAU,CAAC;AAAA,EAEvB,MAAM,WAAW,YACf,CAAC,KAAa,qBAAuC;AAAA,IACnD,OAAO,OAAO,QAAQ,iBAAiB;AAAA,IACvC,WAAW;AAAA,IACX,aAAa,CACX,OACA,aACA,YACG;AAAA,MACH,IAAI,iBAAiB,UAAU;AAAA,QAC7B,UAAU,CAAC,UAAU;AAAA,aAChB;AAAA,WACF,MAAM,GAAG,WAAW,QAAQ,QAAQ,CAAC,CAAC;AAAA,QACzC,EAAE;AAAA,MACJ,EAAO;AAAA,QACL,UAAU,CAAC,UAAU,KAAK,OAAO,MAAM,MAAM,EAAE;AAAA;AAAA;AAAA,EAGrD,IACA,CAAC,MAAM,CACT;AAAA,EAEA,MAAM,QAAQ,YAAY,MAAM;AAAA,IAC9B,UAAU,CAAC,CAAC;AAAA,IACZ,IAAI,YAAY;AAAA,MACd,aAAa,WAAW,gBAAgB,YAAY;AAAA,IACtD;AAAA,KACC,CAAC,UAAU,CAAC;AAAA,EAEf,OAAO,EAAE,UAAU,MAAM;AAAA;",
|
|
8
|
-
"debugId": "114E438FE0AE9DE064756E2164756E21",
|
|
9
|
-
"names": []
|
|
10
|
-
}
|