npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

nummo-sheet

v1.0.3

Published

Reusable Excel component for React applications

Readme

Nummo Spreadsheet

A reusable React spreadsheet component with Excel-like functionality, designed to be easily integrated into any React application, including Electron apps.

Features

  • Excel-like grid with rows, columns, and cells
  • Formula support via HyperFormula
  • Cell formatting (bold, italic, alignment, colors)
  • Copy/paste functionality
  • Multiple sheets
  • Import/export JSON data
  • Electron compatibility
  • Programmatic API for external control

Installation

npm install nummo-sheet
# or
yarn add nummo-sheet

Peer Dependencies

This package has the following peer dependencies that need to be installed in your project:

npm install react react-dom tailwindcss

Basic Usage

import { Spreadsheet } from 'nummo-sheet';

// Import the styles
import 'nummo-sheet/styles.css';

function App() {
  return (
    <div style={{ width: '100%', height: '600px' }}>
      <Spreadsheet />
    </div>
  );
}

Importing Data

You can import data from JSON using the jsonData prop:

import { Spreadsheet } from 'nummo-sheet';
import 'nummo-sheet/styles.css';

function App() {
  const data = {
    tables: [
      {
        table_name: 'Sheet 1',
        cells: [
          [
            { value: 'Name' },
            { value: 'Age' },
            { value: 'City' }
          ],
          [
            { value: 'John' },
            { value: 30 },
            { value: 'New York' }
          ]
        ]
      }
    ]
  };

  return (
    <div style={{ width: '100%', height: '600px' }}>
      <Spreadsheet jsonData={data} />
    </div>
  );
}

Programmatic API

The spreadsheet component provides a programmatic API for external control. You can access this API using the onReady callback:

import { Spreadsheet } from 'nummo-sheet';
import { SpreadsheetAPI } from 'nummo-sheet/types';
import 'nummo-sheet/styles.css';

function App() {
  const handleReady = (api) => {
    // Store the API reference for later use
    spreadsheetApi.current = api;
    
    // Example: Get current data
    const data = api.getData();
    console.log('Current spreadsheet data:', data);
    
    // Example: Set a cell value
    api.setCellValue(0, 0, 'Hello from API');
  };
  
  const spreadsheetApi = useRef(null);
  
  const handleButtonClick = () => {
    // Use the API to interact with the spreadsheet
    if (spreadsheetApi.current) {
      spreadsheetApi.current.setCellValue(0, 1, 'Updated value');
    }
  };
  
  return (
    <div>
      <button onClick={handleButtonClick}>Update Cell</button>
      <div style={{ width: '100%', height: '600px' }}>
        <Spreadsheet onReady={handleReady} />
      </div>
    </div>
  );
}

Available API Methods

| Method | Description | |--------|-------------| | getData() | Get the current spreadsheet data | | setData(data) | Set spreadsheet data from external source | | getSelection() | Get the current selection | | setSelection(selection) | Set the current selection | | getCellValue(row, col, sheetIndex?) | Get the value of a specific cell | | setCellValue(row, col, value, sheetIndex?) | Set the value of a specific cell | | addSheet(name) | Add a new sheet | | switchSheet(index) | Switch to a specific sheet | | exportToJson() | Export the current spreadsheet data to JSON format |

Electron Integration

When using this component in an Electron application, you'll need to handle browser-specific APIs differently. The spreadsheet component provides several abstractions and props to help with this.

Storage Abstraction

The module provides a StorageProvider interface that abstracts storage operations. This allows you to implement custom storage solutions for Electron, such as using the file system or IPC communication with the main process instead of localStorage.

// Types for storage abstraction
import type { StorageProvider } from 'nummo-sheet/types';
// Helper functions for creating storage providers
import { createMemoryStorageProvider, createLocalStorageProvider } from 'nummo-sheet';

// In-memory storage provider (useful for Electron)
const memoryStorage = createMemoryStorageProvider();

// Custom storage provider using Electron's main process
const createElectronStorageProvider = (): StorageProvider => {
  return {
    getItem: (key) => {
      // Use IPC to get data from main process
      return window.electron.invoke('get-storage-item', key);
    },
    setItem: (key, value) => {
      // Use IPC to save data to main process
      return window.electron.invoke('set-storage-item', key, value);
    },
    removeItem: (key) => {
      // Use IPC to remove data in main process
      return window.electron.invoke('remove-storage-item', key);
    }
  };
};

Clipboard Abstraction

The module also provides a ClipboardProvider interface to abstract clipboard operations. This allows you to use Electron's clipboard API instead of the browser's navigator.clipboard.

// Types for clipboard abstraction
import type { ClipboardProvider } from 'nummo-sheet/types';

// Custom clipboard provider using Electron's clipboard API
const createElectronClipboardProvider = (): ClipboardProvider => {
  return {
    readText: () => {
      // Use Electron's clipboard API
      return Promise.resolve(require('electron').clipboard.readText());
    },
    writeText: (text) => {
      // Use Electron's clipboard API
      require('electron').clipboard.writeText(text);
      return Promise.resolve();
    }
  };
};

Complete Electron Integration Example

import { Spreadsheet } from 'nummo-spreadsheet';
import type { ClipboardProvider, StorageProvider } from 'nummo-spreadsheet/types';
import 'nummo-spreadsheet/dist/styles.css';
import { useState, useEffect, useRef } from 'react';

// Custom Electron storage provider
const createElectronStorageProvider = (): StorageProvider => {
  return {
    getItem: (key) => window.electron.invoke('get-storage-item', key),
    setItem: (key, value) => window.electron.invoke('set-storage-item', key, value),
    removeItem: (key) => window.electron.invoke('remove-storage-item', key)
  };
};

// Custom Electron clipboard provider
const createElectronClipboardProvider = (): ClipboardProvider => {
  const { clipboard } = window.require('electron');
  return {
    readText: () => Promise.resolve(clipboard.readText()),
    writeText: (text) => {
      clipboard.writeText(text);
      return Promise.resolve();
    }
  };
};

function ElectronApp() {
  // Create custom providers for Electron
  const storageProvider = createElectronStorageProvider();
  const clipboardProvider = createElectronClipboardProvider();
  
  // Reference to the spreadsheet API
  const spreadsheetApi = useRef(null);
  
  // Example data from Electron main process
  const [data, setData] = useState(null);
  
  useEffect(() => {
    // Listen for data from the main process
    window.electron.receive('spreadsheet-data', (receivedData) => {
      setData(receivedData);
    });
    
    // Listen for commands from the main process
    window.electron.receive('spreadsheet-command', (command, ...args) => {
      if (spreadsheetApi.current) {
        // Execute commands from main process
        switch (command) {
          case 'export':
            const data = spreadsheetApi.current.exportToJson();
            window.electron.send('spreadsheet-exported', data);
            break;
          case 'set-cell':
            const [row, col, value] = args;
            spreadsheetApi.current.setCellValue(row, col, value);
            break;
          // Add more command handlers as needed
        }
      }
    });
  }, []);
  
  const handleDataChange = (updatedData) => {
    // Send updated data back to the main process
    window.electron.send('update-spreadsheet-data', updatedData);
  };
  
  const handleReady = (api) => {
    // Store API reference for programmatic control
    spreadsheetApi.current = api;
    
    // Notify main process that the spreadsheet is ready
    window.electron.send('spreadsheet-ready');
  };
  
  return (
    <div style={{ width: '100%', height: '600px' }}>
      <Spreadsheet 
        jsonData={data} 
        onDataChange={handleDataChange}
        onReady={handleReady}
        storageProvider={storageProvider}
        useSystemClipboard={true}
        useFileSystem={true}
      />
    </div>
  );
}

Main Process Implementation (Electron)

Here's an example of how to set up the main process to communicate with the spreadsheet component:

// In main.js or preload.js
const { contextBridge, ipcRenderer, ipcMain } = require('electron');

// Preload script for exposing safe APIs to renderer
contextBridge.exposeInMainWorld('electron', {
  // IPC communication
  send: (channel, ...args) => ipcRenderer.send(channel, ...args),
  receive: (channel, func) => {
    ipcRenderer.on(channel, (event, ...args) => func(...args));
  },
  invoke: (channel, ...args) => ipcRenderer.invoke(channel, ...args),
  
  // File system operations
  saveFile: (options) => ipcRenderer.invoke('save-file', options),
  openFile: (options) => ipcRenderer.invoke('open-file', options)
});

// In the main process
let storageData = {}; // In-memory storage for the main process

// Handle storage operations
ipcMain.handle('get-storage-item', (event, key) => {
  return storageData[key] || null;
});

ipcMain.handle('set-storage-item', (event, key, value) => {
  storageData[key] = value;
  return true;
});

ipcMain.handle('remove-storage-item', (event, key) => {
  delete storageData[key];
  return true;
});

// Handle file operations
ipcMain.handle('save-file', async (event, { defaultPath, filters, data }) => {
  const { dialog } = require('electron');
  const { filePath } = await dialog.showSaveDialog({
    defaultPath,
    filters
  });
  
  if (filePath) {
    const fs = require('fs');
    fs.writeFileSync(filePath, data);
    return { success: true, filePath };
  }
  
  return { success: false };
});

ipcMain.handle('open-file', async (event, { filters }) => {
  const { dialog } = require('electron');
  const { filePaths } = await dialog.showOpenDialog({
    properties: ['openFile'],
    filters
  });
  
  if (filePaths && filePaths.length > 0) {
    const fs = require('fs');
    const data = fs.readFileSync(filePaths[0], 'utf8');
    return { success: true, data, filePath: filePaths[0] };
  }
  
  return { success: false };
});

Electron-specific Props

| Prop | Type | Description | |------|------|-------------| | storageProvider | StorageProvider | Custom storage implementation for persistence. Use this to implement Electron-specific storage. | | useSystemClipboard | boolean | When true, the spreadsheet will attempt to use the system clipboard. In Electron, you should provide a custom clipboard provider through the hooks. | | useFileSystem | boolean | When true, file operations will be delegated to the host application. In Electron, you should implement file system operations using the Electron API. | | readOnly | boolean | Make the spreadsheet read-only. Useful for view-only scenarios. |

Handling File Operations in Electron

For file operations like import/export, you'll need to handle these through Electron's dialog and fs modules:

// In your Electron renderer process
const handleExport = () => {
  if (spreadsheetApi.current) {
    const data = spreadsheetApi.current.exportToJson();
    window.electron.saveFile({
      defaultPath: 'spreadsheet-data.json',
      filters: [{ name: 'JSON Files', extensions: ['json'] }],
      data: JSON.stringify(data, null, 2)
    });
  }
};

const handleImport = async () => {
  const result = await window.electron.openFile({
    filters: [{ name: 'JSON Files', extensions: ['json'] }]
  });
  
  if (result.success) {
    try {
      const jsonData = JSON.parse(result.data);
      // Set the data to the spreadsheet
      if (spreadsheetApi.current) {
        spreadsheetApi.current.setData(jsonData);
      }
    } catch (error) {
      console.error('Failed to parse JSON:', error);
    }
  }
};

TypeScript Support

This package includes TypeScript definitions. You can import types directly:

import { Spreadsheet } from 'nummo-spreadsheet';
import type { SpreadsheetAPI, SpreadsheetProps } from 'nummo-spreadsheet/types';

{ files: ['**/*.{ts,tsx}'], extends: [ // Other configs... // Enable lint rules for React reactX.configs['recommended-typescript'], // Enable lint rules for React DOM reactDom.configs.recommended, ], languageOptions: { parserOptions: { project: ['./tsconfig.node.json', './tsconfig.app.json'], tsconfigRootDir: import.meta.dirname, }, // other options... }, }, ])