@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/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.toFixed(decimals)}`;
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 ?? 1;
39537
- return `${(numVal * 100).toFixed(decimals)}%`;
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
- if (decimals !== undefined) {
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.jsx("tbody", { children: filteredData.map((row, rowIndex) => {
39578
- const isFrozen = isRowFrozen(rowIndex);
39579
- const isZebra = zebraStripes && rowIndex % 2 === 1;
39580
- return (jsxRuntime.jsxs("tr", { className: `${isZebra ? 'bg-paper-50' : 'bg-white'} ${isFrozen ? 'sticky z-10' : ''} ${isFrozen ? 'shadow-sm' : ''}`, style: {
39581
- top: isFrozen ? `${40 + rowIndex * 40}px` : undefined,
39582
- }, 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) => {
39583
- const column = columns[colIndex];
39584
- const isFrozenCol = colIndex < frozenColumns;
39585
- const isEditing = editingCell?.row === rowIndex && editingCell?.col === colIndex;
39586
- const isSelected = selectedCell?.row === rowIndex && selectedCell?.col === colIndex;
39587
- const hasFormula = !!cell?.formula;
39588
- const leftOffset = rowHeaders
39589
- ? 50 + columns.slice(0, colIndex).reduce((sum, c) => sum + (c.width || 100), 0)
39590
- : columns.slice(0, colIndex).reduce((sum, c) => sum + (c.width || 100), 0);
39591
- const cellBgClass = getCellBgClass(column, isZebra, isFrozenCol);
39592
- 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: {
39593
- left: isFrozenCol ? leftOffset : undefined,
39594
- minWidth: column?.minWidth || 80,
39595
- }, 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));
39596
- })] }, rowIndex));
39597
- }) })] }) }), 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 }))] }))] })] }));
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