@papernote/ui 1.10.24 → 1.10.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/DataGrid.d.ts +6 -0
- package/dist/components/DataGrid.d.ts.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.esm.js +83 -43
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +83 -43
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/DataGrid.tsx +164 -28
package/dist/index.js
CHANGED
|
@@ -39126,9 +39126,10 @@ const buildGroupColorMap = (columns) => {
|
|
|
39126
39126
|
* />
|
|
39127
39127
|
* ```
|
|
39128
39128
|
*/
|
|
39129
|
-
const DataGrid = React.forwardRef(({ data: initialData, columns, onChange, rowHeaders = false, frozenRows: frozenRowsProp = 'none', frozenColumns = 0, showFreezeRowToggle = false, zebraStripes = false, formulas = false, readOnly = false, height = 400, width = '100%', showToolbar = false, title, enableExport = false, exportFileName = 'export.csv', enableSave = false, onSave, toolbarActions, className = '', density = 'normal', }, ref) => {
|
|
39129
|
+
const DataGrid = React.forwardRef(({ data: initialData, columns, onChange, rowHeaders = false, frozenRows: frozenRowsProp = 'none', frozenColumns = 0, showFreezeRowToggle = false, zebraStripes = false, formulas = false, readOnly = false, height = 400, width = '100%', showToolbar = false, title, enableExport = false, exportFileName = 'export.csv', enableSave = false, onSave, toolbarActions, className = '', density = 'normal', virtualized = false, virtualRowHeight = 40, virtualOverscan = 5, }, ref) => {
|
|
39130
39130
|
// State
|
|
39131
39131
|
const [data, setData] = React.useState(initialData);
|
|
39132
|
+
const [scrollTop, setScrollTop] = React.useState(0);
|
|
39132
39133
|
const [editingCell, setEditingCell] = React.useState(null);
|
|
39133
39134
|
const [editValue, setEditValue] = React.useState('');
|
|
39134
39135
|
const [sortConfig, setSortConfig] = React.useState(null);
|
|
@@ -39187,23 +39188,16 @@ const DataGrid = React.forwardRef(({ data: initialData, columns, onChange, rowHe
|
|
|
39187
39188
|
return 'bg-white';
|
|
39188
39189
|
return '';
|
|
39189
39190
|
}, [groupColorMap]);
|
|
39190
|
-
// Check if a specific row is frozen
|
|
39191
|
-
const isRowFrozen = React.useCallback((rowIndex) => {
|
|
39192
|
-
if (frozenRowsState === 'none')
|
|
39193
|
-
return false;
|
|
39194
|
-
if (frozenRowsState === 'first')
|
|
39195
|
-
return rowIndex === 0;
|
|
39196
|
-
if (frozenRowsState === 'selected') {
|
|
39197
|
-
return selectedCell ? rowIndex === selectedCell.row : false;
|
|
39198
|
-
}
|
|
39199
|
-
if (typeof frozenRowsState === 'number')
|
|
39200
|
-
return rowIndex < frozenRowsState;
|
|
39201
|
-
return false;
|
|
39202
|
-
}, [frozenRowsState, selectedCell]);
|
|
39203
39191
|
// Update data when initialData changes
|
|
39204
39192
|
React.useEffect(() => {
|
|
39205
39193
|
setData(initialData);
|
|
39206
39194
|
}, [initialData]);
|
|
39195
|
+
// Handle scroll for virtualization
|
|
39196
|
+
const handleScroll = React.useCallback((e) => {
|
|
39197
|
+
if (virtualized) {
|
|
39198
|
+
setScrollTop(e.currentTarget.scrollTop);
|
|
39199
|
+
}
|
|
39200
|
+
}, [virtualized]);
|
|
39207
39201
|
// Get computed data with formulas evaluated
|
|
39208
39202
|
// Uses a cache to handle formula dependencies (formulas referencing other formulas)
|
|
39209
39203
|
const computedData = React.useMemo(() => {
|
|
@@ -39306,6 +39300,30 @@ const DataGrid = React.forwardRef(({ data: initialData, columns, onChange, rowHe
|
|
|
39306
39300
|
});
|
|
39307
39301
|
return [...frozenData, ...filtered];
|
|
39308
39302
|
}, [sortedData, filters, columns, frozenRows]);
|
|
39303
|
+
// Calculate visible rows for virtualization
|
|
39304
|
+
const visibleRowRange = React.useMemo(() => {
|
|
39305
|
+
if (!virtualized) {
|
|
39306
|
+
return { startIndex: 0, endIndex: filteredData.length, paddingTop: 0, paddingBottom: 0, frozenRowCount: 0 };
|
|
39307
|
+
}
|
|
39308
|
+
const containerHeight = typeof height === 'number' ? height : 400;
|
|
39309
|
+
const headerHeight = 40; // Approximate header height
|
|
39310
|
+
const availableHeight = containerHeight - headerHeight;
|
|
39311
|
+
// Account for frozen rows
|
|
39312
|
+
const frozenRowCount = frozenRows;
|
|
39313
|
+
const scrollableData = filteredData.slice(frozenRowCount);
|
|
39314
|
+
const visibleCount = Math.ceil(availableHeight / virtualRowHeight);
|
|
39315
|
+
const startIndex = Math.max(0, Math.floor(scrollTop / virtualRowHeight) - virtualOverscan);
|
|
39316
|
+
const endIndex = Math.min(scrollableData.length, startIndex + visibleCount + (virtualOverscan * 2));
|
|
39317
|
+
const paddingTop = startIndex * virtualRowHeight;
|
|
39318
|
+
const paddingBottom = Math.max(0, (scrollableData.length - endIndex) * virtualRowHeight);
|
|
39319
|
+
return {
|
|
39320
|
+
startIndex: startIndex + frozenRowCount,
|
|
39321
|
+
endIndex: endIndex + frozenRowCount,
|
|
39322
|
+
paddingTop,
|
|
39323
|
+
paddingBottom,
|
|
39324
|
+
frozenRowCount
|
|
39325
|
+
};
|
|
39326
|
+
}, [virtualized, filteredData.length, scrollTop, height, virtualRowHeight, virtualOverscan, frozenRows]);
|
|
39309
39327
|
// Handle cell edit start
|
|
39310
39328
|
const handleCellDoubleClick = React.useCallback((rowIndex, colIndex, cellElement) => {
|
|
39311
39329
|
if (readOnly)
|
|
@@ -39530,17 +39548,15 @@ const DataGrid = React.forwardRef(({ data: initialData, columns, onChange, rowHe
|
|
|
39530
39548
|
if (column.type === 'currency' && !isNaN(numVal)) {
|
|
39531
39549
|
const decimals = column.format?.decimals ?? 2;
|
|
39532
39550
|
const prefix = column.format?.prefix ?? '$';
|
|
39533
|
-
return `${prefix}${numVal.
|
|
39551
|
+
return `${prefix}${numVal.toLocaleString('en-US', { minimumFractionDigits: decimals, maximumFractionDigits: decimals })}`;
|
|
39534
39552
|
}
|
|
39535
39553
|
if (column.type === 'percent' && !isNaN(numVal)) {
|
|
39536
|
-
const decimals = column.format?.decimals ??
|
|
39537
|
-
return `${(numVal * 100).
|
|
39554
|
+
const decimals = column.format?.decimals ?? 2;
|
|
39555
|
+
return `${(numVal * 100).toLocaleString('en-US', { minimumFractionDigits: decimals, maximumFractionDigits: decimals })}%`;
|
|
39538
39556
|
}
|
|
39539
39557
|
if (column.type === 'number' && !isNaN(numVal)) {
|
|
39540
|
-
const decimals = column.format?.decimals;
|
|
39541
|
-
|
|
39542
|
-
return numVal.toFixed(decimals);
|
|
39543
|
-
}
|
|
39558
|
+
const decimals = column.format?.decimals ?? 0;
|
|
39559
|
+
return numVal.toLocaleString('en-US', { minimumFractionDigits: decimals, maximumFractionDigits: decimals });
|
|
39544
39560
|
}
|
|
39545
39561
|
return String(value);
|
|
39546
39562
|
}, []);
|
|
@@ -39558,7 +39574,7 @@ const DataGrid = React.forwardRef(({ data: initialData, columns, onChange, rowHe
|
|
|
39558
39574
|
: 'Freeze Row' }) })), enableExport && (jsxRuntime.jsx(Button, { variant: "ghost", size: "sm", icon: jsxRuntime.jsx(lucideReact.Download, { className: "h-4 w-4" }), onClick: exportToCSV, children: "Export" })), enableSave && onSave && (jsxRuntime.jsx(Button, { variant: "primary", size: "sm", icon: jsxRuntime.jsx(lucideReact.Save, { className: "h-4 w-4" }), onClick: handleSave, loading: isSaving, children: "Save" })), toolbarActions] })), filters.length > 0 && (jsxRuntime.jsx(Stack, { direction: "horizontal", spacing: "sm", className: "mb-2 px-1 flex-wrap", children: filters.map((filter) => {
|
|
39559
39575
|
const column = columns.find((c) => c.key === filter.key);
|
|
39560
39576
|
return (jsxRuntime.jsxs("div", { className: "inline-flex items-center gap-1 px-2 py-1 bg-primary-100 text-primary-700 rounded text-xs", children: [jsxRuntime.jsxs("span", { className: "font-medium", children: [column?.header, ":"] }), jsxRuntime.jsx("span", { children: filter.value }), jsxRuntime.jsx("button", { onClick: () => clearFilter(filter.key), className: "ml-1 hover:bg-primary-200 rounded p-0.5", children: jsxRuntime.jsx(lucideReact.X, { className: "h-3 w-3" }) })] }, filter.key));
|
|
39561
|
-
}) })), jsxRuntime.jsx("div", { ref: tableRef, className: "relative overflow-auto border border-stone-200 rounded-lg bg-white", style: { height }, onKeyDown: handleKeyDown, tabIndex: 0, children: jsxRuntime.jsxs("table", { className: "border-collapse", style: { tableLayout: 'auto' }, children: [jsxRuntime.jsx("thead", { className: "sticky top-0 z-20 bg-stone-100", children: jsxRuntime.jsxs("tr", { children: [rowHeaders && (jsxRuntime.jsx("th", { className: `${cellPadding} border-b border-r border-stone-200 bg-stone-100 text-left font-semibold text-ink-600 sticky left-0 z-30`, style: { width: 50, minWidth: 50, maxWidth: 50 }, children: "#" })), columns.map((column, colIndex) => {
|
|
39577
|
+
}) })), jsxRuntime.jsx("div", { ref: tableRef, className: "relative overflow-auto border border-stone-200 rounded-lg bg-white", style: { height }, onKeyDown: handleKeyDown, onScroll: handleScroll, tabIndex: 0, children: jsxRuntime.jsxs("table", { className: "border-collapse", style: { tableLayout: 'auto' }, children: [jsxRuntime.jsx("thead", { className: "sticky top-0 z-20 bg-stone-100", children: jsxRuntime.jsxs("tr", { children: [rowHeaders && (jsxRuntime.jsx("th", { className: `${cellPadding} border-b border-r border-stone-200 bg-stone-100 text-left font-semibold text-ink-600 sticky left-0 z-30`, style: { width: 50, minWidth: 50, maxWidth: 50 }, children: "#" })), columns.map((column, colIndex) => {
|
|
39562
39578
|
const isFrozen = colIndex < frozenColumns;
|
|
39563
39579
|
const leftOffset = rowHeaders ? 50 + columns.slice(0, colIndex).reduce((sum, c) => sum + (c.width || 100), 0) : columns.slice(0, colIndex).reduce((sum, c) => sum + (c.width || 100), 0);
|
|
39564
39580
|
const headerBgClass = getHeaderBgClass(column);
|
|
@@ -39574,27 +39590,51 @@ const DataGrid = React.forwardRef(({ data: initialData, columns, onChange, rowHe
|
|
|
39574
39590
|
if (e.key === 'Escape')
|
|
39575
39591
|
setActiveFilter(null);
|
|
39576
39592
|
}, autoFocus: true }), jsxRuntime.jsxs(Stack, { direction: "horizontal", spacing: "sm", className: "mt-2", children: [jsxRuntime.jsx(Button, { size: "sm", variant: "ghost", onClick: () => setActiveFilter(null), children: "Cancel" }), jsxRuntime.jsx(Button, { size: "sm", variant: "primary", onClick: applyFilter, children: "Apply" })] })] }))] }))] }) }, column.key));
|
|
39577
|
-
})] }) }), jsxRuntime.
|
|
39578
|
-
|
|
39579
|
-
|
|
39580
|
-
|
|
39581
|
-
|
|
39582
|
-
|
|
39583
|
-
|
|
39584
|
-
|
|
39585
|
-
|
|
39586
|
-
|
|
39587
|
-
|
|
39588
|
-
|
|
39589
|
-
|
|
39590
|
-
|
|
39591
|
-
|
|
39592
|
-
|
|
39593
|
-
|
|
39594
|
-
|
|
39595
|
-
|
|
39596
|
-
|
|
39597
|
-
|
|
39593
|
+
})] }) }), jsxRuntime.jsxs("tbody", { children: [virtualized && visibleRowRange.paddingTop > 0 && (jsxRuntime.jsx("tr", { style: { height: visibleRowRange.paddingTop }, children: jsxRuntime.jsx("td", { colSpan: columns.length + (rowHeaders ? 1 : 0) }) })), filteredData.slice(0, visibleRowRange.frozenRowCount).map((row, rowIndex) => {
|
|
39594
|
+
const isZebra = zebraStripes && rowIndex % 2 === 1;
|
|
39595
|
+
return (jsxRuntime.jsxs("tr", { className: `${isZebra ? 'bg-paper-50' : 'bg-white'} sticky z-10 shadow-sm`, style: {
|
|
39596
|
+
top: `${40 + rowIndex * virtualRowHeight}px`,
|
|
39597
|
+
}, children: [rowHeaders && (jsxRuntime.jsx("td", { className: `${cellPadding} border-b border-r border-stone-200 bg-stone-50 text-ink-500 font-medium sticky left-0 z-10`, style: { width: 50, minWidth: 50, maxWidth: 50 }, children: Array.isArray(rowHeaders) ? rowHeaders[rowIndex] : rowIndex + 1 })), row.map((cell, colIndex) => {
|
|
39598
|
+
const column = columns[colIndex];
|
|
39599
|
+
const isFrozenCol = colIndex < frozenColumns;
|
|
39600
|
+
const isEditing = editingCell?.row === rowIndex && editingCell?.col === colIndex;
|
|
39601
|
+
const isSelected = selectedCell?.row === rowIndex && selectedCell?.col === colIndex;
|
|
39602
|
+
const hasFormula = !!cell?.formula;
|
|
39603
|
+
const leftOffset = rowHeaders
|
|
39604
|
+
? 50 + columns.slice(0, colIndex).reduce((sum, c) => sum + (c.width || 100), 0)
|
|
39605
|
+
: columns.slice(0, colIndex).reduce((sum, c) => sum + (c.width || 100), 0);
|
|
39606
|
+
const cellBgClass = getCellBgClass(column, isZebra, isFrozenCol);
|
|
39607
|
+
return (jsxRuntime.jsx("td", { className: `${cellPadding} border-b border-r border-stone-200 text-${column?.align || 'left'} ${isFrozenCol ? 'sticky z-10' : ''} ${cellBgClass} ${isSelected ? 'ring-2 ring-inset ring-primary-500' : ''} ${hasFormula ? 'bg-blue-50' : ''} ${cell?.className || ''}`, style: {
|
|
39608
|
+
left: isFrozenCol ? leftOffset : undefined,
|
|
39609
|
+
minWidth: column?.minWidth || 80,
|
|
39610
|
+
height: virtualized ? virtualRowHeight : undefined,
|
|
39611
|
+
}, onClick: () => handleCellClick(rowIndex, colIndex), onDoubleClick: (e) => handleCellDoubleClick(rowIndex, colIndex, e.currentTarget), children: isEditing ? (formulas ? (jsxRuntime.jsx(FormulaAutocomplete, { value: editValue, onChange: setEditValue, onComplete: handleEditComplete, onCancel: handleEditCancel, anchorRect: editingCellRect, autoFocus: true })) : (jsxRuntime.jsx("input", { ref: inputRef, type: "text", value: editValue, onChange: (e) => setEditValue(e.target.value), onBlur: handleEditComplete, onKeyDown: handleEditKeyDown, className: "w-full h-full border-none outline-none bg-transparent", style: { margin: '-4px', padding: '4px' } }))) : (formatValue(cell?.value, column)) }, colIndex));
|
|
39612
|
+
})] }, `frozen-${rowIndex}`));
|
|
39613
|
+
}), filteredData
|
|
39614
|
+
.slice(virtualized ? Math.max(visibleRowRange.startIndex, visibleRowRange.frozenRowCount) : visibleRowRange.frozenRowCount, virtualized ? visibleRowRange.endIndex : filteredData.length)
|
|
39615
|
+
.map((row, idx) => {
|
|
39616
|
+
const rowIndex = virtualized
|
|
39617
|
+
? Math.max(visibleRowRange.startIndex, visibleRowRange.frozenRowCount) + idx
|
|
39618
|
+
: visibleRowRange.frozenRowCount + idx;
|
|
39619
|
+
const isZebra = zebraStripes && rowIndex % 2 === 1;
|
|
39620
|
+
return (jsxRuntime.jsxs("tr", { className: `${isZebra ? 'bg-paper-50' : 'bg-white'}`, style: {
|
|
39621
|
+
height: virtualized ? virtualRowHeight : undefined,
|
|
39622
|
+
}, children: [rowHeaders && (jsxRuntime.jsx("td", { className: `${cellPadding} border-b border-r border-stone-200 bg-stone-50 text-ink-500 font-medium sticky left-0 z-10`, style: { width: 50, minWidth: 50, maxWidth: 50 }, children: Array.isArray(rowHeaders) ? rowHeaders[rowIndex] : rowIndex + 1 })), row.map((cell, colIndex) => {
|
|
39623
|
+
const column = columns[colIndex];
|
|
39624
|
+
const isFrozenCol = colIndex < frozenColumns;
|
|
39625
|
+
const isEditing = editingCell?.row === rowIndex && editingCell?.col === colIndex;
|
|
39626
|
+
const isSelected = selectedCell?.row === rowIndex && selectedCell?.col === colIndex;
|
|
39627
|
+
const hasFormula = !!cell?.formula;
|
|
39628
|
+
const leftOffset = rowHeaders
|
|
39629
|
+
? 50 + columns.slice(0, colIndex).reduce((sum, c) => sum + (c.width || 100), 0)
|
|
39630
|
+
: columns.slice(0, colIndex).reduce((sum, c) => sum + (c.width || 100), 0);
|
|
39631
|
+
const cellBgClass = getCellBgClass(column, isZebra, isFrozenCol);
|
|
39632
|
+
return (jsxRuntime.jsx("td", { className: `${cellPadding} border-b border-r border-stone-200 text-${column?.align || 'left'} ${isFrozenCol ? 'sticky z-10' : ''} ${cellBgClass} ${isSelected ? 'ring-2 ring-inset ring-primary-500' : ''} ${hasFormula ? 'bg-blue-50' : ''} ${cell?.className || ''}`, style: {
|
|
39633
|
+
left: isFrozenCol ? leftOffset : undefined,
|
|
39634
|
+
minWidth: column?.minWidth || 80,
|
|
39635
|
+
}, onClick: () => handleCellClick(rowIndex, colIndex), onDoubleClick: (e) => handleCellDoubleClick(rowIndex, colIndex, e.currentTarget), children: isEditing ? (formulas ? (jsxRuntime.jsx(FormulaAutocomplete, { value: editValue, onChange: setEditValue, onComplete: handleEditComplete, onCancel: handleEditCancel, anchorRect: editingCellRect, autoFocus: true })) : (jsxRuntime.jsx("input", { ref: inputRef, type: "text", value: editValue, onChange: (e) => setEditValue(e.target.value), onBlur: handleEditComplete, onKeyDown: handleEditKeyDown, className: "w-full h-full border-none outline-none bg-transparent", style: { margin: '-4px', padding: '4px' } }))) : (formatValue(cell?.value, column)) }, colIndex));
|
|
39636
|
+
})] }, rowIndex));
|
|
39637
|
+
}), virtualized && visibleRowRange.paddingBottom > 0 && (jsxRuntime.jsx("tr", { style: { height: visibleRowRange.paddingBottom }, children: jsxRuntime.jsx("td", { colSpan: columns.length + (rowHeaders ? 1 : 0) }) }))] })] }) }), jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-2 py-1 text-xs text-ink-500 border-t border-stone-200 bg-stone-50 rounded-b-lg", children: [jsxRuntime.jsxs("span", { children: [filteredData.length, " row", filteredData.length !== 1 ? 's' : '', filters.length > 0 && ` (filtered)`] }), selectedCell && (jsxRuntime.jsxs("span", { children: [colIndexToLetter(selectedCell.col), selectedCell.row + 1, data[selectedCell.row]?.[selectedCell.col]?.formula && (jsxRuntime.jsx("span", { className: "ml-2 text-blue-600", children: data[selectedCell.row][selectedCell.col].formula }))] }))] })] }));
|
|
39598
39638
|
});
|
|
39599
39639
|
DataGrid.displayName = 'DataGrid';
|
|
39600
39640
|
|