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

@litable/core

v1.0.4

Published

LiTable core — framework-agnostic, dependency-free table plugin

Readme

@litable/core

Framework-agnostic, dependency-free table plugin — the engine behind all @litable/* framework wrappers.

Install

npm install @litable/core

CDN:

<link rel="stylesheet" href="litable.min.css">
<script src="litable.min.js"></script>
<!-- window.LiTable is now available -->

Quick Start

Existing HTML table

import LiTable from '@litable/core';

new LiTable('#myTable', {
  sorting:    true,
  filtering:  true,
  pagination: { pageSize: 10 },
});

Inline data

new LiTable('#myTable', {
  data: [
    { id: 1, name: 'Alice', city: 'New York' },
    { id: 2, name: 'Bob',   city: 'London'   },
  ],
  pagination: { pageSize: 10 },
});

AJAX

new LiTable('#myTable', {
  ajax: 'https://api.example.com/users',
  pagination: { pageSize: 20 },
});

Data Sources

Use exactly one of the three sources. They are mutually exclusive.

| Option | Type | Description | |----------|----------|-------------| | ajax | string | URL to fetch JSON data. Supports array, { data: [...] }, or nested envelope response. | | data | Row[] | Inline array of objects. Column headers are auto-generated from field names. | | (none) | — | Reads existing <tbody> rows directly from the HTML. |

AJAX response formats (all auto-detected)

[{ ... }, { ... }]

{ "data": [{ ... }] }

{ "statusCode": 200, "data": { "total": 12363, "page": 1, "limit": 10, "data": [{ ... }] } }

Server-Side Mode

Set serverSide: true to delegate pagination, sorting, and search to the API. LiTable appends query parameters automatically on every interaction.

new LiTable('#myTable', {
  ajax:       'https://api.example.com/data?school=1811&isPaginated=true',
  serverSide: true,
  pagination: { pageSize: 20 },
});

Query params sent to the server

| Param | Example | When included | |------------|-------------------|---------------| | page | page=1 | Always | | pageSize | pageSize=20 | Always | | sortBy | sortBy=name | After a column header is clicked | | sortDir | sortDir=asc | After a column header is clicked (asc or desc) | | search | search=alice | When search box is non-empty (debounced 400 ms) |

Example URL: https://api.example.com/data?school=1811&page=2&pageSize=20&sortBy=name&sortDir=asc&search=alice

Expected server response

{
  "statusCode": 200,
  "message": "successful",
  "data": {
    "total": 12363,
    "page": 2,
    "limit": 20,
    "data": [...]
  }
}

Also accepted (flat envelope):

{ "total": 12363, "page": 2, "limit": 20, "data": [...] }

columns[]

columns is an override map — every field in the data source is rendered as a column. A columns entry applies properties to a targeted column without hiding the rest.

Target a column by:

  • Number — positional index (0 = first, -1 = last, -2 = second-to-last, …)
  • String — field name in the data object (e.g. 'gradeName')
columns: [
  { key: 0,           pinLeft:  true },
  { key: 1,           pinLeft:  true, render: (val) => `<b style="color:red;">${val}</b>` },
  { key: 'status',    header: 'Status', render: (val) => `<span class="badge">${val}</span>` },
  { key: -1,          pinRight: true },
  { key: -2,          pinRight: true },
]

ColumnDef fields

| Field | Type | Description | |-------------|---------------------------|-------------| | key | string \| number | Column target — field name or positional index | | header | string | Override the column header text | | pinLeft | boolean | Sticky left (stacks in order of columns entries) | | pinRight | boolean | Sticky right (stacks in order of columns entries) | | hidden | boolean | Hide this column (th + all td) | | width | number \| string | Column width (px number or CSS string) | | minWidth | number | Minimum column width in px | | maxWidth | number | Maximum column width in px | | sortable | boolean | Set false to disable sorting for this column | | render | (value, row) => string | Custom cell renderer — return an HTML string | | attribute | object | Set an HTML attribute on th, td, or both |

attribute

attribute: {
  name:   'data-type',  // HTML attribute name
  value:  'badge',      // HTML attribute value
  target: 'td'          // 'th' | 'td' | 'both'
}

Auto headers: If header is omitted, the field name is used with the first character uppercased (gradeNameGradeName).


Sticky Columns & Headers

new LiTable('#myTable', {
  ajax: '...',
  columns: [
    { key: 0,  pinLeft:  true },  // pin column 0 left
    { key: 1,  pinLeft:  true },  // pin column 1 left (stacks)
    { key: -1, pinRight: true },  // pin last column right
    { key: -2, pinRight: true },  // pin second-to-last right (stacks)
  ],
  stickyHeader: [0, 1],           // pin first two <thead> rows to the top
});

Pagination

pagination: {
  pageSize:  20,                           // initial rows per page
  pageSizes: [5, 10, 20, 50, 100, 'All'], // dropdown options — 'All' shows all rows
  sizeLabel: ['Show', 'entries'],          // label around the size selector
}

Virtual Scrolling

Renders only the rows visible in the viewport. Can be combined with pagination.

virtualScrolling: {
  height:      '400px',  // scroll container height (required)
  width:       '100%',   // optional max-width on the scroll container
  rowHeight:   40,       // fixed row height in px (auto-measured if omitted)
  bufferSize:  5,        // extra rows rendered above/below viewport
  yBufferSize: 10,       // overrides bufferSize for vertical axis only
}

Sorting

Enabled by default. Click any <th> to sort ascending; click again to toggle to descending.

sorting: true   // default — set false to disable globally

Disable sorting on a specific column with the no-sort attribute on <th>:

<th no-sort>Actions</th>

Filtering / Search

filtering: true  // default

// or with customization:
filtering: {
  searchLabel:       'Search:',
  searchPlaceholder: 'Filter rows...',
}

In serverSide mode the search term is sent as ?search=term with a 400 ms debounce instead of doing client-side filtering.


Export

export: {
  formats:  ['csv', 'excel', 'pdf', 'image'],
  filename: 'my-data',
}

Supported formats: 'csv', 'excel', 'pdf', 'image'.


Editable (CRUD)

Row selection with modal-based add / edit / delete. Requires wrapper: true (the default).

editable: {
  type:   'full_row',  // only supported type — clicking a row selects it
  url:    '/api/crud', // POST endpoint for all three operations
  key:    [0],         // column index(es) that form the primary key
  hide:   [3, 5],      // column indexes hidden in Add/Edit form (e.g. auto-generated fields)
  action: {
    add:    true,      // show "New" button
    edit:   true,      // show "Edit" button (enabled on row select)
    delete: true,      // show "Delete" button (enabled on row select)
  },
}

Request format

POSTs to editable.url with action_vals = base64-encoded JSON:

{
  "opration":  "add | update | delete",
  "condition": [{ "id": "5" }],
  "data":      { "Name": "Alice", "Age": "30" },
  "key":       ["id"]
}

Response format

| Operation | Success | Failure | |-----------|---------|---------| | add | [{ col: val, ... }] | null | | update | { "keyVal": true } | { "keyVal": false } | | delete | { "keyVal": true } | { "keyVal": false } |


Theme

theme: {
  striped:     true,        // alternate row background
  stripeColor: '#f9f9f9',  // stripe color (default #f9f9f9)
  border: {
    borderWidth:  1,         // border thickness in px — creates uniform cell borders via box-shadow
    borderColor:  '#cccccc', // border color (default #cccccc)
    tableBorder:  true,      // outer table border
    rowBorder:    true,      // horizontal row separators
    columnBorder: true,      // vertical column separators
  },
}

All Options at a Glance

new LiTable('#myTable', {
  // ── Data ──
  ajax:       'https://api.example.com/data?school=1811&isPaginated=true',
  serverSide: true,
  data:       [],            // alternative to ajax
  columns: [
    { key: 0,  pinLeft:  true },
    { key: 1,  pinLeft:  true, render: (val) => `<b style="color:red;">${val}</b>` },
    { key: -1, pinRight: true },
    { key: -2, pinRight: true },
  ],

  // ── Layout ──
  wrapper:      true,         // render the controls wrapper (default true)
  stickyHeader: [0, 1],       // sticky thead row indices

  // ── Features ──
  sorting:          true,
  filtering:        { searchLabel: 'Search:', searchPlaceholder: '' },
  pagination:       { pageSize: 20, pageSizes: [10, 20, 50, 100, 'All'] },
  info:             true,     // "Showing X to Y of Z entries" text
  virtualScrolling: { height: '400px' },
  columnResizing:   true,
  columnReordering: true,
  rowSelection:     { mode: 'multiple' },
  inlineEditing:    true,

  // ── Export ──
  export: {
    formats:  ['csv', 'excel', 'pdf', 'image'],
    filename: 'my-data',
  },

  // ── CRUD ──
  editable: {
    type:   'full_row',
    url:    '/api/crud',
    key:    [0],
    hide:   [3, 5],
    action: { add: true, edit: true, delete: true },
  },

  // ── Theme ──
  theme: {
    striped: true,
    border: {
      tableBorder:  true,
      borderWidth:  1,
      columnBorder: true,
      rowBorder:    true,
      borderColor:  'gray',
    },
  },

  // ── Callbacks ──
  onSort:       (column, direction) => console.log(column, direction),
  onFilter:     (term) => console.log(term),
  onPageChange: (page, pageSize) => console.log(page, pageSize),
  onRowSelect:  (rows) => console.log(rows),
  onChange:     (data) => console.log(data),
  beforeRender: () => {},
  onRender:     () => {},
});

Instance API

const table = new LiTable('#myTable', { ... });

table.update({ data: newData });  // update options & re-render
table.refresh();                  // re-render with current state
table.reload();                   // full re-initialise from original HTML
table.destroy();                  // remove all enhancements

table.getData();                  // → Row[]
table.setData(newData);           // replace all rows & re-render
table.getHead();                  // → <thead> element
table.setHead(htmlOrElement);     // replace <thead>
table.getBodyRow();               // → HTMLTableRowElement[]
table.setBodyRow(rows);           // replace internal row array (call refresh() after)

Global State

window.litable['myTable'].thlist   // string[]       — column header labels
window.litable['myTable'].thrlist  // HTMLCollection — <thead> row elements
window.litable['myTable'].d        // Row[]          — raw data (ajax/data mode)
window.litable['myTable'].sel_list // number[]       — selected row indices

Framework Wrappers

| Package | Framework | |--------------------|--------------| | @litable/react | React 18/19 | | @litable/angular | Angular 17+ | | @litable/vue3 | Vue 3 | | @litable/vue2 | Vue 2.7 | | @litable/svelte | Svelte 4/5 | | @litable/solid | SolidJS | | @litable/preact | Preact 10 | | @litable/alpine | Alpine.js 3 | | @litable/qwik | Qwik | | @litable/astro | Astro 4 | | @litable/nuxt | Nuxt 3 |


License

Copyright © 2026 Naveen Yadav. All rights reserved.
Proprietary and confidential — see LICENSE.