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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@witan/xlsx-view

v0.1.1

Published

Spreadsheet Viewer SDK for rendering Excel files with Witan API

Readme

@witan/xlsx-view

A client SDK for rendering Excel spreadsheets from the Witan API. The Witan API renders spreadsheet cells as image tiles server-side, and this SDK composites those tiles into an interactive viewer.

Note: This package requires a Witan API subscription. The SDK connects to Witan's WebSocket API to stream pre-rendered spreadsheet tiles.

Features

  • Tile-based rendering: Server renders cells as images, client composites them for perfect visual fidelity
  • Framework-agnostic core: XlsxClient class works with any framework
  • React integration: Ready-to-use XlsxView component with hooks
  • HiDPI support: Automatic device pixel ratio handling for crisp rendering
  • Efficient caching: LRU tile cache with proper ImageBitmap cleanup
  • Priority loading: Tiles closest to viewport center load first
  • WebSocket streaming: Real-time tile updates with automatic reconnection
  • Adaptive format: WebP tiles when browser supports it, PNG fallback
  • Skeleton loading: Smooth loading states while tiles stream in
  • Customizable: CSS variables for theming, render slots for custom UI

Installation

pnpm add @witan/xlsx-view

Quick Start

Prerequisites

  1. A Witan API subscription (get one at witanlabs.com)
  2. An uploaded XLSX file (via the Witan API)
  3. An authentication token for the Witan API

React

import { XlsxView, xlsxViewStyles } from "@witan/xlsx-view/react";

// Include default styles (or define your own CSS variables)
const styleSheet = document.createElement("style");
styleSheet.textContent = xlsxViewStyles;
document.head.appendChild(styleSheet);

function SpreadsheetViewer({
  fileId,
  revisionId,
}: {
  fileId: string;
  revisionId: string;
}) {
  // getToken should return a valid Witan API access token
  const getToken = async () => {
    const response = await fetch("/api/witan-token");
    const { token } = await response.json();
    return token;
  };

  return (
    <XlsxView
      apiOrigin="https://api.witanlabs.com" // Your Witan API endpoint
      fileId={fileId}
      revisionId={revisionId}
      getAccessToken={getToken}
    />
  );
}

Core (Framework-Agnostic)

import { XlsxClient } from "@witan/xlsx-view";

const client = new XlsxClient({
  apiOrigin: "https://api.witanlabs.com", // Your Witan API endpoint
  fileId: "your-file-id",
  revisionId: "your-revision-id",
  getAccessToken: () => fetchWitanToken(), // Return your Witan API token
});

client.on("connectionChange", ({ status }) => {
  console.log("Connection:", status);
});

client.on("metadataLoaded", ({ metadata }) => {
  console.log(
    "Sheets:",
    metadata.sheets.map((s) => s.name),
  );
});

client.on("tileLoaded", ({ key }) => {
  // Re-render when tiles arrive
  const tiles = client.getTilesForViewport(viewport);
  render(tiles);
});

client.connect();

// Set viewport when container is ready
client.setViewport({ scrollX: 0, scrollY: 0, width: 800, height: 600 });

// Cleanup
client.dispose();

React API

XlsxView

Main component for rendering spreadsheets.

interface XlsxViewProps {
  // Connection (required)
  apiOrigin: string;
  fileId: string;
  revisionId: string;
  getAccessToken: () => Promise<string>;

  // State (hybrid: uncontrolled by default, controlled if props provided)
  selection?: Selection | null;
  defaultSelection?: Selection;
  onSelectionChange?: (selection: Selection | null) => void;

  activeSheet?: string;
  defaultSheet?: string;
  onSheetChange?: (sheetName: string) => void;

  // UI customization
  formulaBar?: (props: FormulaBarProps) => ReactNode;
  sheetTabs?: (props: SheetTabsProps) => ReactNode;
  selectionAction?: (props: SelectionActionProps) => ReactNode;

  // Callbacks
  onError?: (error: Error) => void;
  onReady?: () => void; // Called when viewer is ready (metadata loaded, tiles rendered)
  onHyperlinkClick?: (link: HyperlinkInfo) => void; // Called when a hyperlink is clicked

  // Styling
  className?: string;
}

// Imperative handle (via ref)
interface XlsxViewHandle {
  /** Scroll a cell into view. Returns true if successful, false if not ready. */
  scrollToCell: (row: number, col: number) => boolean;
}

Usage Examples

Uncontrolled (simplest):

<XlsxView
  apiOrigin="https://api.example.com"
  fileId="abc123"
  revisionId="rev456"
  getAccessToken={getToken}
/>

Controlled selection:

const [selection, setSelection] = useState<Selection | null>(null);

<XlsxView
  {...connectionProps}
  selection={selection}
  onSelectionChange={setSelection}
/>;

Custom UI slots:

<XlsxView
  {...connectionProps}
  // Custom sheet tabs
  sheetTabs={({ sheets, activeSheet, onSheetChange }) => (
    <MyCustomTabs
      sheets={sheets}
      active={activeSheet}
      onChange={onSheetChange}
    />
  )}
  // Custom action button on selection
  selectionAction={({ selection, position }) => (
    <button style={{ position: "absolute" }}>Analyze Selection</button>
  )}
/>

Programmatic scrolling with ref:

import { useRef, useState } from "react";
import { XlsxView, type XlsxViewHandle } from "@witan/xlsx-view/react";

function SpreadsheetWithNavigation() {
  const viewRef = useRef<XlsxViewHandle>(null);
  const [ready, setReady] = useState(false);

  const navigateToCell = (row: number, col: number) => {
    // scrollToCell scrolls the minimum amount needed to bring the cell into view.
    // If the cell is already visible, no scrolling occurs.
    viewRef.current?.scrollToCell(row, col);
  };

  return (
    <>
      <button onClick={() => navigateToCell(50, 10)} disabled={!ready}>
        Go to K51
      </button>
      <XlsxView
        ref={viewRef}
        {...connectionProps}
        onReady={() => setReady(true)}
      />
    </>
  );
}

Hooks

useXlsxClient

Access the XlsxClient instance and state.

const {
  client, // XlsxClient instance
  status, // ConnectionStatus
  metadata, // ViewSheetsResponse | null
  activeSheet, // string | null
  selection, // Selection | null
  error, // Error | null
  setActiveSheet,
  setSelection,
} = useXlsxClient({ apiOrigin, fileId, revisionId, getAccessToken });

useViewport

Manage viewport state and scroll handling.

const {
  viewport, // { scrollX, scrollY, width, height }
  setContainerRef, // Ref callback for scroll container
  handleScroll, // onScroll handler
  scrollTo, // (x, y) => void
} = useViewport();

useSelection

Handle cell selection with mouse interactions.

const {
  selection, // Selection | null
  isDragging, // boolean
  handleMouseDown, // Event handler
  handleMouseMove, // Event handler
  handleMouseUp, // Event handler
} = useSelection({
  client,
  viewport,
  initialSelection,
  selection,
  onSelectionChange,
});

Hyperlink handling:

import type { HyperlinkInfo } from "@witan/xlsx-view/react";

<XlsxView
  {...connectionProps}
  onHyperlinkClick={(link: HyperlinkInfo) => {
    if (link.type === "external") {
      // Custom external link handling
      window.open(link.target, "_blank");
    } else {
      // Internal link - link.target is like "Sheet2!A1"
      console.log("Navigate to:", link.target);
    }
  }}
/>;

// Default behavior (when onHyperlinkClick not provided):
// - External links open in new tab
// - Internal links scroll to target cell (and switch sheets if needed)

Sub-components

For custom layouts, individual components are exported:

import {
  TileCanvas,
  ColumnHeaders,
  RowHeaders,
  SelectionOverlay,
  HyperlinkOverlay,
  LoadingSkeleton,
  parseInternalLink,
} from "@witan/xlsx-view/react";

Core API

XlsxClient

Framework-agnostic engine for managing spreadsheet state.

class XlsxClient extends EventEmitter<XlsxClientEvents> {
  // Readonly state
  readonly status: ConnectionStatus;
  readonly metadata: ViewSheetsResponse | null;
  readonly activeSheet: string | null;
  readonly selection: Selection | null;
  readonly dpr: number;

  constructor(options: XlsxClientOptions);

  // Lifecycle
  connect(): void;
  dispose(): void;

  // Navigation
  setActiveSheet(sheetName: string): void;
  setViewport(viewport: Viewport): void;

  // Selection
  setSelection(selection: Selection | null): void;

  // Cell data (instant cached access from prefetched visible region)
  getCell(row: number, col: number): CellData | undefined;
  hasCell(row: number, col: number): boolean;

  // Coordinate conversion
  pixelToCell(x: number, y: number): CellPosition | null;
  getCellBounds(row: number, col: number): CellBounds | null;
  getSheetDimensions(): { width: number; height: number };
  getPositions(): SheetPositions | null;
  getActiveSheetMetadata(): SheetMetadata | null;

  // Tiles
  getTilesForViewport(viewport: Viewport): Map<string, TileState>;
}

Options

interface XlsxClientOptions {
  apiOrigin: string; // API server origin
  fileId: string; // File identifier
  revisionId: string; // Revision identifier
  getAccessToken: () => Promise<string>; // Token refresh function
  maxCacheSize?: number; // Tile cache size (default: 200)
  maxConcurrent?: number; // Concurrent requests (default: 6)
}

Events

type XlsxClientEvents = {
  connectionChange: { status: ConnectionStatus; error?: Error };
  metadataLoaded: { metadata: ViewSheetsResponse };
  sheetChange: { sheet: string };
  selectionChange: { selection: Selection | null };
  tileLoaded: { key: string };
  cellsLoaded: { range: RangeBounds };
  error: { error: Error };
};

// Subscribe to events
const unsubscribe = client.on("tileLoaded", ({ key }) => {
  console.log("Tile loaded:", key);
});

// Unsubscribe
unsubscribe();

Utilities

import {
  computeSheetPositions, // Compute position arrays from sheet metadata
  getTilePosition, // Get pixel position of a tile
  getTileSize, // Get pixel size of a tile
  tileKey, // Generate cache key for a tile
  parseTileKey, // Parse cache key back to coordinates
} from "@witan/xlsx-view";

Styling

Theme the viewer using CSS custom properties:

.xlsx-view {
  /* Headers */
  --xv-header-bg: #f5f5f5;
  --xv-header-bg-selected: #e9e9eb;
  --xv-header-text: #666666;
  --xv-header-border: #e0e0e0;
  --xv-col-header-height: 24px;
  --xv-row-header-width: 28px;

  /* Selection */
  --xv-selection-border: #166534;
  --xv-selection-fill: rgba(22, 101, 52, 0.08);
  --xv-selection-width: 2px;

  /* Grid */
  --xv-gridline-color: #e0e0e0;
  --xv-cell-bg: #ffffff;
}

/* Custom theme example */
.my-dark-theme .xlsx-view {
  --xv-header-bg: #2d2d2d;
  --xv-header-text: #cccccc;
  --xv-selection-border: #0066cc;
  --xv-cell-bg: #1e1e1e;
}

Including Default Styles

The package exports default CSS as a string:

import { xlsxViewStyles, loadingSkeletonStyles } from "@witan/xlsx-view/react";

// Option 1: Inject into document
const style = document.createElement("style");
style.textContent = xlsxViewStyles + loadingSkeletonStyles;
document.head.appendChild(style);

// Option 2: Import in CSS bundler (if supported)
// Add to your CSS: @import '@witan/xlsx-view/styles.css';

Types

Core Types

type ConnectionStatus =
  | "disconnected"
  | "connecting"
  | "connected"
  | "reconnecting"
  | "error";

interface Viewport {
  scrollX: number;
  scrollY: number;
  width: number;
  height: number;
}

interface Selection {
  cell: CellPosition; // Anchor cell
  range: RangeBounds | null; // Multi-cell range (null for single cell)
}

interface CellPosition {
  row: number; // 0-indexed
  col: number; // 0-indexed
}

interface CellBounds {
  x: number;
  y: number;
  width: number;
  height: number;
}

Tile Types

type TileStatus = "pending" | "loading" | "loaded" | "error";

interface TileState {
  status: TileStatus;
  bitmap?: ImageBitmap; // Only when loaded
  width?: number; // CSS pixels
  height?: number; // CSS pixels
  error?: Error; // Only when error
}

interface TileCoord {
  tileRow: number;
  tileCol: number;
}

Cell Data

interface CellData {
  row: number;
  col: number;
  text: string;
  formula?: string;
  error?: string;
  richText?: RichTextRun[];
  style?: CellStyle;
  link?: {
    type: "internal" | "external";
    target: string;
    tooltip?: string;
  };
  note?: {
    author: string;
    text: string;
  };
  thread?: {
    resolved: boolean;
    comments: {
      authorId: string;
      text: string;
      createdAt: string;
    }[];
  };
}

interface HyperlinkInfo {
  type: "internal" | "external";
  target: string;
  tooltip?: string;
  cell: CellPosition;
}

Sheet Metadata

interface ViewSheetsResponse {
  sheets: SheetMetadata[];
  activeSheetIndex: number;
  defaultStyle: CellStyle;
  persons?: Record<string, { name: string }>;
  tileRows: number; // Rows per tile
  tileCols: number; // Columns per tile
}

interface SheetMetadata {
  name: string;
  hidden: boolean;
  usedRange?: RangeBounds;
  defaultRowHeight: number;
  defaultColWidth: number;
  rowHeights: Record<string, number>;
  colWidths: Record<string, number>;
  hiddenRows: number[];
  hiddenCols: number[];
  merges: MergeRange[];
  frozenRows: number;
  frozenCols: number;
  showGridLines: boolean;
  tileRowCount: number;
  tileColCount: number;
  tileRows: number; // Rows per tile
  tileCols: number; // Columns per tile
}

Constants

import {
  DEFAULT_COL_WIDTH, // 64px
  DEFAULT_ROW_HEIGHT, // 15px
} from "@witan/xlsx-view";

Architecture

The SDK uses a tile-based rendering approach with the Witan API:

┌─────────────────┐         WebSocket          ┌─────────────────┐
│   Your App      │  ←───────────────────────→ │   Witan API     │
│                 │                             │                 │
│  @witan/xlsx-   │   - Upload XLSX files      │  - Parses       │
│  view SDK       │   - Request tile images    │    spreadsheet  │
│                 │   - Receive PNG/WebP tiles │  - Resolves &   │
│  - Composites   │   - Get cell metadata      │    normalizes   │
│    tiles        │                             │    styles       │
│  - Handles UI   │                             │  - Renders      │
│                 │                             │    image tiles  │
└─────────────────┘                             └─────────────────┘
  1. Witan API renders cells as image tiles (50 rows x 26 columns each)
  2. SDK requests tiles for visible viewport via WebSocket, prioritized by distance from center
  3. Tiles are cached as ImageBitmap objects for efficient compositing
  4. Headers are rendered client-side on separate canvases
  5. Selection is drawn on an overlay canvas above tiles

Caching & Immutability

The SDK operates on fileId/revisionId pairs. Revisions are immutable — when a file changes, a new revisionId is created. This enables aggressive caching:

  • Tiles for a given revision never change, so they can be cached indefinitely
  • Multiple users viewing the same revision benefit from shared CDN cache
  • Return visits to a previously-viewed revision load instantly from cache
  • No cache invalidation logic needed — just cache forever

Benefits:

  • Perfect visual fidelity (fonts, colors, borders match Excel exactly)
  • Lower client complexity (no font measurement, text wrapping, or cell rendering)
  • Smaller bundle size (no heavy grid library dependency)
  • Efficient caching and memory management

Browser Support

  • Chrome/Edge 80+
  • Firefox 75+
  • Safari 14.1+

Requires:

  • ImageBitmap API
  • WebSocket support
  • CSS custom properties

License

MIT