@lnsy/data-table
v0.1.0
Published
A dependency-free <data-table> web component: spreadsheet, table and list modes with formulas, endpoint lookups, wikilinks and file I/O.
Maintainers
Readme
@lnsy/data-table
A dependency-free tabular data component (<data-table>) built on vanilla
JS, CSS and HTML — no jspreadsheet, no grid library.
One custom element, three presentation modes, all backed by the same data model and formula engine.
Features
Three modes, one engine
Pick a presentation with the mode attribute. The data model, formulas and
[[wikilinks]] behave identically in all three:
| Mode | Attribute | What you get |
| --- | --- | --- |
| Spreadsheet | mode="spreadsheet" (default) | Full grid with A/B/C column headers and numbered rows, a formula bar, range selection, in-cell editing, and resizable columns and rows. |
| Table | mode="table" | Searchable, sortable record table. The first data row becomes the header; click a header to sort ascending/descending (numeric-aware); a live counter shows filtered vs. total records. |
| List | mode="list" | Searchable single-column list built from the first column; empty cells are skipped. |
Spreadsheet editing
- Click to select; drag or Shift+Arrow to extend a range selection; arrow keys and Tab/Shift+Tab move the active cell.
- Double-click, press Enter, or just start typing to edit a cell. Enter commits and moves down, Tab/Shift+Tab commits and moves sideways, Escape cancels.
- Delete/Backspace clears the selection.
- Drag the edge of a column or row header to resize it; double-click the edge to snap back to the default size.
- The formula bar shows the raw contents of the active cell (formulas included), and cells that contain formulas are visually flagged.
read-onlydisables all editing while keeping selection, navigation and copy.
Formulas
Cells starting with = are evaluated by the built-in, dependency-free
formula engine (src/formula.js):
=SUM(A1:A5) =IF(A1>2, "yes", "no")
=A1&B1 =ROUND(A1*B1, 2)
=IFERROR(HTTP(…), "n/a") =CONCAT("Q", 1)- References —
A1, absolute\$B\$2, and rangesA1:B10(ranges are valid inside functions). - Operators —
+ - * / ^ %(modulo),&(string concat), and comparisons= <> < > <= >=, with parentheses and unary minus. - Literals — numbers,
"strings"(escape"as""),TRUE/FALSE.
| Category | Functions |
| --- | --- |
| Math | SUM, AVERAGE/AVG, MIN, MAX, COUNT, COUNTA, ABS, ROUND, FLOOR, CEILING/CEIL, SQRT, POWER/POW, MOD |
| Text | CONCAT/CONCATENATE, LEN, UPPER, LOWER, TRIM |
| Logic | IF, IFERROR, AND, OR, NOT |
| Date | TODAY, NOW |
IF and IFERROR short-circuit: only the taken branch is evaluated, which
matters when a branch hits the network. Circular references are detected and
reported as #CIRCULAR!; other errors surface as #SYNTAX!, #NAME?,
#HTTP! or #NOENDPOINT!.
Live data from formulas
Formulas can fetch over HTTP, with dotted-path extraction into the parsed JSON (numeric segments index arrays):
=HTTP("https://api.example.com/status")
=HTTP("https://api.frankfurter.app/latest?from=USD", "rates.EUR")
=HTTPJSON("https://…", "items.0.name")
=ENDPOINT("rates", "rates.EUR")HTTP(url)returns the response body (text, or stringified JSON); pass a second argument to extract a value from the parsed JSON.HTTPJSON(url, path)always parses the body as JSON.ENDPOINT("name", path?)reads a URL registered from JavaScript:
table.setEndpoints({ rates: 'https://api.frankfurter.app/latest?from=USD' });Responses are cached for 30 seconds and concurrent identical requests are deduplicated; failed requests are only cached for 3 seconds so retries happen quickly.
Wikilinks
[[Page Name]] and [[Page Name|label]] render as links inside any cell:
- Set
wikilink-base-url="https://…/"and links open${base}${encodeURIComponent(target)}in a new tab. - Otherwise, clicking a link dispatches a bubbling
wikilink-navigateevent with{ detail: { target } }, which the host application can handle (routing, filtering, opening a panel…).
Copy & paste
Ctrl/Cmd+C/X/V work inside the grid and interoperate with Excel, Numbers
and Google Sheets via tab-separated values. Pasting a multi-cell block
expands the grid to fit. Programmatic equivalents: selectionToTSV(),
copySelection() and pasteText(text).
File I/O
The helpers in src/file-io.js load, save, import and export an element's
data. Wire them to your own buttons or menus; they operate on any
<data-table> instance:
| Function | Direction | Format |
| --- | --- | --- |
| saveLSS(table, filename) / loadLSS(table, file) | export / import | Native .lss (JSON; keeps formulas as =… strings; reads older v1/v2 files) |
| exportCSV(table, filename) / importCSV(table, file) | export / import | CSV (export writes computed values, like spreadsheet apps) |
| exportJSON(table, filename) / importJSON(table, file) | export / import | JSON two-dimensional array |
| importSpreadsheetFile(table, file) | import | XLSX, XLS and ODS |
These helpers are the package's only code that uses an npm dependency
(xlsx, for the spreadsheet import); the component itself has none.
Theming
Styles ship as @lnsy/data-table/data-table.css, scoped to the component
(data-table, .dt-*, a.wikilink) so nothing leaks into your page. The
look is driven by CSS custom properties defined in the package's
styles/variables.css — a "dataroom" theme (warm paper, dark-red accents)
with an automatic dark scheme via prefers-color-scheme. Import that file
or define your own values for the properties (e.g. --selection-bg,
--grid-cell-height) to re-theme the component.
Single sheet by design.
Install
npm install @lnsy/data-tableUsage
Import the module (this registers the <data-table> custom element) and the
stylesheet:
import '@lnsy/data-table';
import '@lnsy/data-table/data-table.css';Then use the element anywhere in your HTML:
<data-table id="grid" mode="spreadsheet" rows="20" cols="8"></data-table>The class is also exported for programmatic use:
import DataTable from '@lnsy/data-table';Attributes
| Attribute | Values | Default |
| ------------------- | ---------------------------------- | -------------- |
| mode | spreadsheet | table | list | spreadsheet |
| read-only | boolean | off |
| rows / cols | integers (spreadsheet mode) | 50 / 12 |
| wikilink-base-url | URL prefix for [[wikilinks]] | — |
Events
| Event | Fired when | detail |
| ------------------- | ----------------------------- | ------------------------ |
| change | any cell is edited | { row, col, value } |
| selection-change | the active cell moves | { row, col } |
| wikilink-navigate | a [[wikilink]] is clicked | { target } |
Element API
const table = document.querySelector('data-table');
table.setData([['name', 'qty'], ['nails', '100']]); // replace the contents
table.getData(); // deep copy of the raw grid
table.getComputedValue(1, 1); // formula-evaluated cell value
table.setCellValue(1, 1, '=A1*2');
table.setEndpoints({ rates: 'https://api.frankfurter.app/latest?from=USD' });
await table.recalculate(); // re-run every formula and re-render
table.insertRow(); // append a row (insertRow(5) appends five)
table.insertColumn(); // append a column
table.clearSelection(); // blank every cell in the selection
table.selectionToTSV(); // TSV of the current selection
table.pasteText('a\tb\nc\td'); // write a TSV block at the active cellDevelopment
This repository is the package's development home; the npm package itself
only exports <data-table> (see src/index.js). The demo pages, command
panel wiring and file menus live in the root index.js and are not part of
the published package.
npm install # install dependencies
npm start # dev server on http://localhost:3000
npm run build # production bundle in dist/
npm run test:unit # vitest unit tests (tests/unit/)
npm test # playwright end-to-end tests (tests/e2e/)Demo pages: demo.html links to one demo per feature (endpoints, wikilinks,
copy-paste, file I/O, table/list modes, live chart).
Publishing
npm version patch # or minor / major
npm publish # prepublishOnly builds and runs the unit testsVerify the package contents with npm pack --dry-run: only src/,
styles/, README.md and LICENSE ship.
License
Unlicense — public domain.
