@humanspeak/svelte-headless-table
Version:
A powerful, headless table library for Svelte that provides complete control over table UI while handling complex data operations like sorting, filtering, pagination, grouping, and row expansion. Build custom, accessible data tables with zero styling opin
171 lines (170 loc) • 4.96 kB
JavaScript
import { TableComponent } from './tableComponent.js';
import { derived } from 'svelte/store';
/**
* Abstract base class representing a cell in the table body.
* Extended by DataBodyCell for data cells and DisplayBodyCell for display-only cells.
*
* @template Item - The type of data items in the table.
* @template Plugins - The plugins used by the table.
*/
export class BodyCell extends TableComponent {
row;
constructor({ id, row }) {
super({ id });
this.row = row;
}
attrs() {
return derived(super.attrs(), ($baseAttrs) => {
return {
...$baseAttrs,
role: 'cell'
};
});
}
/**
* Gets a unique identifier combining the row ID and column ID.
*
* @returns A string in the format "rowId:columnId".
*/
rowColId() {
return `${this.row.id}:${this.column.id}`;
}
/**
* Gets a unique identifier combining the data row ID and column ID.
* Only available for cells in data rows.
*
* @returns A string in the format "dataId:columnId", or undefined if not a data row.
*/
dataRowColId() {
if (!this.row.isData()) {
return undefined;
}
return `${this.row.dataId}:${this.column.id}`;
}
/**
* Type guard to check if this cell is a data cell.
*
* @returns True if this is a DataBodyCell.
*/
// TODO Workaround for https://github.com/vitejs/vite/issues/9528
isData() {
return '__data' in this;
}
/**
* Type guard to check if this cell is a display cell.
*
* @returns True if this is a DisplayBodyCell.
*/
// TODO Workaround for https://github.com/vitejs/vite/issues/9528
isDisplay() {
return '__display' in this;
}
}
/**
* A body cell that contains actual data from the data source.
* Provides access to the cell value and custom rendering.
*
* @template Item - The type of data items in the table.
* @template Plugins - The plugins used by the table.
* @template Value - The type of the cell value.
*/
export class DataBodyCell extends BodyCell {
// TODO Workaround for https://github.com/vitejs/vite/issues/9528
__data = true;
/** The data column this cell belongs to. */
column;
/** Optional custom label renderer for the cell. */
label;
/** The cell's value. */
value;
/**
* Creates a new DataBodyCell.
*
* @param init - Initialization options.
*/
constructor({ row, column, label, value }) {
super({ id: column.id, row });
this.column = column;
this.label = label;
this.value = value;
}
/**
* Renders the cell content using the label function or the raw value.
*
* @returns The render configuration for displaying this cell.
* @throws Error if state reference is missing when using a custom label.
*/
render() {
if (this.label === undefined) {
return `${this.value}`;
}
if (this.state === undefined) {
throw new Error('Missing `state` reference');
}
return this.label(this, this.state);
}
/**
* Creates a copy of this cell.
*
* @returns A cloned DataBodyCell.
*/
clone() {
const clonedCell = new DataBodyCell({
row: this.row,
column: this.column,
label: this.label,
value: this.value
});
return clonedCell;
}
}
/**
* A body cell used for display purposes only (e.g., action buttons, checkboxes).
* Does not contain direct data from the data source.
*
* @template Item - The type of data items in the table.
* @template Plugins - The plugins used by the table.
*/
export class DisplayBodyCell extends BodyCell {
// TODO Workaround for https://github.com/vitejs/vite/issues/9528
__display = true;
/** The display column this cell belongs to. */
column;
/** The label renderer for the cell. */
label;
/**
* Creates a new DisplayBodyCell.
*
* @param init - Initialization options.
*/
constructor({ row, column, label }) {
super({ id: column.id, row });
this.column = column;
this.label = label;
}
/**
* Renders the cell content using the label function.
*
* @returns The render configuration for displaying this cell.
* @throws Error if state reference is missing.
*/
render() {
if (this.state === undefined) {
throw new Error('Missing `state` reference');
}
return this.label(this, this.state);
}
/**
* Creates a copy of this cell.
*
* @returns A cloned DisplayBodyCell.
*/
clone() {
const clonedCell = new DisplayBodyCell({
row: this.row,
column: this.column,
label: this.label
});
return clonedCell;
}
}