# @litable/core

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

## Install

```bash
npm install @litable/core
```

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

---

## Quick Start

### Existing HTML table
```ts
import LiTable from '@litable/core';

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

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

### AJAX
```ts
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)

```json
[{ ... }, { ... }]

{ "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.

```ts
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

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

Also accepted (flat envelope):
```json
{ "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'`)

```ts
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

```ts
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 (`gradeName` → `GradeName`).

---

## Sticky Columns & Headers

```ts
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

```ts
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.

```ts
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.

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

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

```html
<th no-sort>Actions</th>
```

---

## Filtering / Search

```ts
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

```ts
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).

```ts
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:

```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

```ts
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

```ts
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

```ts
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

```ts
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](../../LICENSE).
