bo-grid 0.21.0 → 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -19
- package/dist/grid/export.d.ts +33 -0
- package/dist/grid/export.js +153 -0
- package/dist/grid/print.d.ts +17 -0
- package/dist/grid/print.js +55 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +4 -2
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
# bo-grid
|
|
2
2
|
|
|
3
3
|
Tiny, fast **Svelte 5** data grid for fintech UIs — canvas sparklines, batched
|
|
4
|
-
realtime cell updates, and virtual scrolling
|
|
5
|
-
A free alternative to the heavyweight
|
|
4
|
+
realtime cell updates, and virtual scrolling, with a core that gzips to ~31 KB
|
|
5
|
+
(Svelte external; unused exports tree-shake). A free alternative to the heavyweight
|
|
6
|
+
grids that paywall these features.
|
|
6
7
|
|
|
7
8
|
**[Live demo](https://bonguynvan.github.io/bo-grid/)** ·
|
|
8
9
|
**[API reference](https://bonguynvan.github.io/bo-grid/api.html)** ·
|
|
9
10
|
**[Benchmarks](./BENCHMARKS.md)** ·
|
|
10
11
|
**[Roadmap](./ROADMAP.md)**
|
|
11
12
|
|
|
12
|
-
The demo is a
|
|
13
|
-
|
|
14
|
-
**
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
from a synthetic source — switch between them with the tabs.
|
|
13
|
+
The demo is a gallery of grid types — a realtime **Trading desk**, a grouped
|
|
14
|
+
**Portfolio** with subtotals and pivot, an editable **Spreadsheet**, a live
|
|
15
|
+
**Order book**, a **Correlation** heatmap, a **Dashboard** with in-cell charts, a
|
|
16
|
+
**Wide** 60-column grid, a server-backed **Lazy tree**, and more — switch between
|
|
17
|
+
them with the tabs.
|
|
18
18
|
|
|
19
19
|
> **Status: actively developed.** Working: config-driven columns, virtual scroll,
|
|
20
20
|
> sort (single / multi / controlled), filtering (global, per-column row, header
|
|
@@ -37,13 +37,12 @@ from a synthetic source — switch between them with the tabs.
|
|
|
37
37
|
| Price | $$$ / dev / year | Free (MIT) |
|
|
38
38
|
| Sparklines | paid tier | built in |
|
|
39
39
|
| Realtime cell updates | DIY / complex | built-in primitive |
|
|
40
|
-
| Bundle | hundreds of KB | **~
|
|
40
|
+
| Bundle | hundreds of KB | **~31 KB gzip core** ([benchmarks](./BENCHMARKS.md)) |
|
|
41
41
|
| Svelte | wrapper | native Svelte 5 |
|
|
42
42
|
|
|
43
|
-
bo-grid ships most of
|
|
44
|
-
tree data, master-detail, range selection, Excel export,
|
|
45
|
-
|
|
46
|
-
what it doesn't do) to decide.
|
|
43
|
+
bo-grid ships most of the features other grids put behind a **paid (Enterprise)**
|
|
44
|
+
tier — grouping, pivot, tree data, master-detail, range selection, Excel export,
|
|
45
|
+
sparklines — for free, and runs in any framework via a [custom element](./docs/frameworks.md).
|
|
47
46
|
|
|
48
47
|
## Install
|
|
49
48
|
|
|
@@ -56,7 +55,8 @@ Works with **SvelteKit / SSR** out of the box — `<Grid>` server-renders to HTM
|
|
|
56
55
|
without touching `window`/`document`/`localStorage` (a CI gate, `pnpm ssr`,
|
|
57
56
|
proves it). The package is `sideEffects: false`, so unused exports tree-shake
|
|
58
57
|
away. See the **[SvelteKit guide](./docs/sveltekit.md)** for `load`-function data,
|
|
59
|
-
realtime feeds,
|
|
58
|
+
server-side / lazy loading, realtime feeds, import helpers, charts, printing, and
|
|
59
|
+
layout persistence.
|
|
60
60
|
|
|
61
61
|
## Usage
|
|
62
62
|
|
|
@@ -111,7 +111,8 @@ el.config = { columns, rows, theme: 'dark', height: 520 };
|
|
|
111
111
|
```
|
|
112
112
|
|
|
113
113
|
It works in React, Vue, Angular and plain HTML — see
|
|
114
|
-
**[docs/frameworks.md](./docs/frameworks.md)** for per-framework recipes
|
|
114
|
+
**[docs/frameworks.md](./docs/frameworks.md)** for per-framework recipes and
|
|
115
|
+
**[examples/](./examples/)** for runnable, build-free starters. (Custom
|
|
115
116
|
`cell`/`detail` snippets are Svelte-only; use built-in types, `format`, or computed
|
|
116
117
|
`value` from other frameworks. Native Svelte users should import `Grid` directly —
|
|
117
118
|
smaller, and snippets work.)
|
|
@@ -710,17 +711,38 @@ const columns = [
|
|
|
710
711
|
];
|
|
711
712
|
```
|
|
712
713
|
|
|
713
|
-
## Export
|
|
714
|
+
## Export & import
|
|
714
715
|
|
|
715
|
-
CSV export
|
|
716
|
+
CSV export — and import — are dependency-free:
|
|
716
717
|
|
|
717
718
|
```ts
|
|
718
|
-
import { exportCSV, toCSV } from 'bo-grid';
|
|
719
|
+
import { exportCSV, toCSV, parseCSV } from 'bo-grid';
|
|
719
720
|
|
|
720
721
|
exportCSV('tickers.csv', rows, columns); // triggers a download
|
|
721
722
|
const text = toCSV(rows, columns, { formatted: true }); // or get the string
|
|
723
|
+
const rows = parseCSV(text, columns); // …and back to rows (round-trip)
|
|
722
724
|
```
|
|
723
725
|
|
|
726
|
+
`parseCSV` is RFC4180-aware (quoted fields, embedded commas/quotes/newlines), maps
|
|
727
|
+
headers to columns, coerces numeric/`date` columns, and stamps `id` + flash fields
|
|
728
|
+
so the result drops straight into `<Grid rows={…}>`. There's also `parseTSV` (tab-
|
|
729
|
+
separated — what Ctrl/⌘+C copies), and for JSON/API data, `rowsFromObjects(objects)`
|
|
730
|
+
/ `parseJSON(text)`:
|
|
731
|
+
|
|
732
|
+
```ts
|
|
733
|
+
import { rowsFromObjects } from 'bo-grid';
|
|
734
|
+
const rows = rowsFromObjects(await (await fetch('/api/rows')).json());
|
|
735
|
+
```
|
|
736
|
+
|
|
737
|
+
Not sure what you'll get? **`parseRows(text, columns?)`** auto-detects JSON / TSV /
|
|
738
|
+
CSV — perfect for a paste handler:
|
|
739
|
+
|
|
740
|
+
```svelte
|
|
741
|
+
<div onpaste={(e) => (rows = parseRows(e.clipboardData.getData('text'), columns))}>…</div>
|
|
742
|
+
```
|
|
743
|
+
|
|
744
|
+
See the **CSV import** demo (CSV / TSV / JSON / Auto-detect).
|
|
745
|
+
|
|
724
746
|
Excel export loads SheetJS via **dynamic import**, so it lands in its own lazy
|
|
725
747
|
chunk and never bloats your core bundle. `xlsx` is an **optional peer dependency**
|
|
726
748
|
— install it only if you use this:
|
|
@@ -734,11 +756,23 @@ Sparkline columns are skipped; numeric columns export as raw numbers so
|
|
|
734
756
|
spreadsheets can compute on them (pass `{ formatted: true }` for display strings).
|
|
735
757
|
Ctrl/⌘+C still copies the current selection as TSV.
|
|
736
758
|
|
|
759
|
+
**Printing.** The grid virtualizes, so printing it directly drops off-screen rows.
|
|
760
|
+
`printTable(rows, columns, { title })` opens a print window with **all** rows as a
|
|
761
|
+
clean table (Save-as-PDF from the dialog); `toHTMLTable(rows, columns)` returns
|
|
762
|
+
that table as an HTML string to embed. See the **Print** demo.
|
|
763
|
+
|
|
764
|
+
```ts
|
|
765
|
+
import { printTable } from 'bo-grid';
|
|
766
|
+
printTable(rows, columns, { title: 'Sales report' });
|
|
767
|
+
```
|
|
768
|
+
|
|
737
769
|
## Also exported
|
|
738
770
|
|
|
739
771
|
`Sparkline` component · `drawCandles` / `setupHiDpiCanvas` (draw on your own
|
|
740
772
|
canvas) · `fmtPrice` / `fmtPercent` / `fmtVolume` / `fmtDate` · `heatColor` ·
|
|
741
|
-
`Selection` · `aggregate` · `toCSV` / `exportCSV` / `exportXLSX` / `rowsToMatrix
|
|
773
|
+
`Selection` · `aggregate` · `toCSV` / `exportCSV` / `exportXLSX` / `rowsToMatrix` ·
|
|
774
|
+
`parseCSV` / `parseCSVMatrix` / `parseTSV` / `parseJSON` / `parseRows` /
|
|
775
|
+
`rowsFromObjects` · `toHTMLTable` / `printTable`.
|
|
742
776
|
|
|
743
777
|
## Pivot tables
|
|
744
778
|
|
package/dist/grid/export.d.ts
CHANGED
|
@@ -16,4 +16,37 @@ export declare function toCSV(rows: readonly GridRow[], columns: readonly Column
|
|
|
16
16
|
/** Trigger a browser download of text content. No-op outside the browser. */
|
|
17
17
|
export declare function download(filename: string, content: string, mime?: string): void;
|
|
18
18
|
export declare function exportCSV(filename: string, rows: readonly GridRow[], columns: readonly ColumnDef[], opts?: ExportOptions): void;
|
|
19
|
+
/** Parse RFC4180 CSV text into a 2-D string matrix. Pure; inverse of `rowsToMatrix`. */
|
|
20
|
+
export declare function parseCSVMatrix(text: string): string[][];
|
|
21
|
+
/**
|
|
22
|
+
* Parse CSV text into grid rows. The first line is the header (mapped to columns
|
|
23
|
+
* by `header` or `key`); numeric columns coerce to numbers, `date` columns to
|
|
24
|
+
* epoch ms. Rows get `id` + flash fields so they're `GridRow`-ready. Inverse of
|
|
25
|
+
* `toCSV` — round-trips. Pure; unit-tested.
|
|
26
|
+
*/
|
|
27
|
+
export declare function parseCSV(text: string, columns?: readonly ColumnDef[]): GridRow[];
|
|
28
|
+
/** Parse TAB-separated text into grid rows (same mapping as `parseCSV`). Handy for
|
|
29
|
+
spreadsheet/clipboard data — `Ctrl/⌘+C` copies the selection as TSV. */
|
|
30
|
+
export declare function parseTSV(text: string, columns?: readonly ColumnDef[]): GridRow[];
|
|
31
|
+
/**
|
|
32
|
+
* Adapt plain objects (e.g. a JSON API response) to grid rows: stamp `id` (the
|
|
33
|
+
* object's own `id`, else the index) and `flashSeq`/`flashDir` if absent, keeping
|
|
34
|
+
* all fields. The cheapest path from `await res.json()` to `<Grid rows>`.
|
|
35
|
+
*/
|
|
36
|
+
export declare function rowsFromObjects(objects: readonly Record<string, unknown>[]): GridRow[];
|
|
37
|
+
/** Parse a JSON array of objects into grid rows (`JSON.parse` + `rowsFromObjects`).
|
|
38
|
+
Throws on invalid JSON or a non-array top level. */
|
|
39
|
+
export declare function parseJSON(text: string): GridRow[];
|
|
40
|
+
/**
|
|
41
|
+
* Smart import: detect the format of `text` — a JSON array, TSV, or CSV — and
|
|
42
|
+
* parse it into grid rows. Ideal for a paste handler where the source is unknown:
|
|
43
|
+
*
|
|
44
|
+
* el.addEventListener('paste', (e) => {
|
|
45
|
+
* rows = parseRows(e.clipboardData.getData('text'), columns);
|
|
46
|
+
* });
|
|
47
|
+
*
|
|
48
|
+
* Leading `[` → JSON (falls through to delimited if it isn't valid JSON); a tab in
|
|
49
|
+
* the first line → TSV; otherwise CSV. Pure; unit-tested.
|
|
50
|
+
*/
|
|
51
|
+
export declare function parseRows(text: string, columns?: readonly ColumnDef[]): GridRow[];
|
|
19
52
|
export {};
|
package/dist/grid/export.js
CHANGED
|
@@ -49,3 +49,156 @@ export function download(filename, content, mime = 'text/csv;charset=utf-8') {
|
|
|
49
49
|
export function exportCSV(filename, rows, columns, opts = {}) {
|
|
50
50
|
download(filename, toCSV(rows, columns, opts));
|
|
51
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* Parse delimited text (CSV/TSV) into a 2-D string matrix — handles quoted fields
|
|
54
|
+
* with embedded delimiters, doubled quotes and newlines, and CRLF or LF endings.
|
|
55
|
+
*/
|
|
56
|
+
function parseDelimited(text, delim) {
|
|
57
|
+
const rows = [];
|
|
58
|
+
let row = [];
|
|
59
|
+
let field = '';
|
|
60
|
+
let inQuotes = false;
|
|
61
|
+
let i = 0;
|
|
62
|
+
const endField = () => {
|
|
63
|
+
row.push(field);
|
|
64
|
+
field = '';
|
|
65
|
+
};
|
|
66
|
+
const endRow = () => {
|
|
67
|
+
endField();
|
|
68
|
+
rows.push(row);
|
|
69
|
+
row = [];
|
|
70
|
+
};
|
|
71
|
+
while (i < text.length) {
|
|
72
|
+
const c = text[i];
|
|
73
|
+
if (inQuotes) {
|
|
74
|
+
if (c === '"') {
|
|
75
|
+
if (text[i + 1] === '"') {
|
|
76
|
+
field += '"';
|
|
77
|
+
i += 2;
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
inQuotes = false;
|
|
81
|
+
i++;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
field += c;
|
|
86
|
+
i++;
|
|
87
|
+
}
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (c === '"') {
|
|
91
|
+
inQuotes = true;
|
|
92
|
+
i++;
|
|
93
|
+
}
|
|
94
|
+
else if (c === delim) {
|
|
95
|
+
endField();
|
|
96
|
+
i++;
|
|
97
|
+
}
|
|
98
|
+
else if (c === '\n') {
|
|
99
|
+
endRow();
|
|
100
|
+
i++;
|
|
101
|
+
}
|
|
102
|
+
else if (c === '\r') {
|
|
103
|
+
i++; // CRLF: the \n ends the row
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
field += c;
|
|
107
|
+
i++;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (field !== '' || row.length > 0)
|
|
111
|
+
endRow(); // trailing line without a newline
|
|
112
|
+
return rows;
|
|
113
|
+
}
|
|
114
|
+
/** Parse RFC4180 CSV text into a 2-D string matrix. Pure; inverse of `rowsToMatrix`. */
|
|
115
|
+
export function parseCSVMatrix(text) {
|
|
116
|
+
return parseDelimited(text, ',');
|
|
117
|
+
}
|
|
118
|
+
// Header-row matrix → grid rows: map each header to a column (by `header` or
|
|
119
|
+
// `key`), coerce numeric columns to numbers and `date` columns to epoch ms, leave
|
|
120
|
+
// blanks/unparseable as-is, and stamp id + flash fields. Drops blank lines.
|
|
121
|
+
function matrixToRows(matrix, columns) {
|
|
122
|
+
const m = matrix.filter((r) => !(r.length === 1 && r[0] === ''));
|
|
123
|
+
if (m.length < 1)
|
|
124
|
+
return [];
|
|
125
|
+
const headers = m[0];
|
|
126
|
+
const cols = headers.map((h) => columns.find((c) => c.header === h || c.key === h));
|
|
127
|
+
return m.slice(1).map((cells, i) => {
|
|
128
|
+
const row = { id: i, flashSeq: 0, flashDir: 'up' };
|
|
129
|
+
headers.forEach((h, c) => {
|
|
130
|
+
const col = cols[c];
|
|
131
|
+
const raw = cells[c] ?? '';
|
|
132
|
+
const key = col ? col.key : h;
|
|
133
|
+
if (raw === '') {
|
|
134
|
+
row[key] = '';
|
|
135
|
+
}
|
|
136
|
+
else if (col?.type === 'date') {
|
|
137
|
+
const ms = Date.parse(raw);
|
|
138
|
+
row[key] = Number.isFinite(ms) ? ms : raw;
|
|
139
|
+
}
|
|
140
|
+
else if (col && isNumeric(col)) {
|
|
141
|
+
const n = Number(raw);
|
|
142
|
+
row[key] = Number.isFinite(n) ? n : raw;
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
row[key] = raw;
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
return row;
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Parse CSV text into grid rows. The first line is the header (mapped to columns
|
|
153
|
+
* by `header` or `key`); numeric columns coerce to numbers, `date` columns to
|
|
154
|
+
* epoch ms. Rows get `id` + flash fields so they're `GridRow`-ready. Inverse of
|
|
155
|
+
* `toCSV` — round-trips. Pure; unit-tested.
|
|
156
|
+
*/
|
|
157
|
+
export function parseCSV(text, columns = []) {
|
|
158
|
+
return matrixToRows(parseDelimited(text, ','), columns);
|
|
159
|
+
}
|
|
160
|
+
/** Parse TAB-separated text into grid rows (same mapping as `parseCSV`). Handy for
|
|
161
|
+
spreadsheet/clipboard data — `Ctrl/⌘+C` copies the selection as TSV. */
|
|
162
|
+
export function parseTSV(text, columns = []) {
|
|
163
|
+
return matrixToRows(parseDelimited(text, '\t'), columns);
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Adapt plain objects (e.g. a JSON API response) to grid rows: stamp `id` (the
|
|
167
|
+
* object's own `id`, else the index) and `flashSeq`/`flashDir` if absent, keeping
|
|
168
|
+
* all fields. The cheapest path from `await res.json()` to `<Grid rows>`.
|
|
169
|
+
*/
|
|
170
|
+
export function rowsFromObjects(objects) {
|
|
171
|
+
return objects.map((o, i) => ({ id: i, flashSeq: 0, flashDir: 'up', ...o }));
|
|
172
|
+
}
|
|
173
|
+
/** Parse a JSON array of objects into grid rows (`JSON.parse` + `rowsFromObjects`).
|
|
174
|
+
Throws on invalid JSON or a non-array top level. */
|
|
175
|
+
export function parseJSON(text) {
|
|
176
|
+
const data = JSON.parse(text);
|
|
177
|
+
if (!Array.isArray(data))
|
|
178
|
+
throw new Error('parseJSON: expected a JSON array of objects');
|
|
179
|
+
return rowsFromObjects(data);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Smart import: detect the format of `text` — a JSON array, TSV, or CSV — and
|
|
183
|
+
* parse it into grid rows. Ideal for a paste handler where the source is unknown:
|
|
184
|
+
*
|
|
185
|
+
* el.addEventListener('paste', (e) => {
|
|
186
|
+
* rows = parseRows(e.clipboardData.getData('text'), columns);
|
|
187
|
+
* });
|
|
188
|
+
*
|
|
189
|
+
* Leading `[` → JSON (falls through to delimited if it isn't valid JSON); a tab in
|
|
190
|
+
* the first line → TSV; otherwise CSV. Pure; unit-tested.
|
|
191
|
+
*/
|
|
192
|
+
export function parseRows(text, columns = []) {
|
|
193
|
+
if (text.trimStart().startsWith('[')) {
|
|
194
|
+
try {
|
|
195
|
+
return parseJSON(text);
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
// Not valid JSON after all — treat it as delimited text below.
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
const nl = text.indexOf('\n');
|
|
202
|
+
const firstLine = nl === -1 ? text : text.slice(0, nl);
|
|
203
|
+
return firstLine.includes('\t') ? parseTSV(text, columns) : parseCSV(text, columns);
|
|
204
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ColumnDef, GridRow } from './column';
|
|
2
|
+
/** Escape a string for safe HTML text/attribute interpolation. */
|
|
3
|
+
export declare function escapeHTML(s: string): string;
|
|
4
|
+
/**
|
|
5
|
+
* Render rows to a semantic `<table>` HTML string (formatted via the column
|
|
6
|
+
* formatters; numeric columns right-aligned; sparkline/custom columns skipped).
|
|
7
|
+
* Values are HTML-escaped. Pure; unit-tested. Embed it, or use `printTable`.
|
|
8
|
+
*/
|
|
9
|
+
export declare function toHTMLTable(rows: readonly GridRow[], columns: readonly ColumnDef[]): string;
|
|
10
|
+
/**
|
|
11
|
+
* Open a print window with all rows as a clean table and trigger the print
|
|
12
|
+
* dialog. No-op outside the browser (or if a popup is blocked). For PDFs, users
|
|
13
|
+
* "Save as PDF" from the print dialog.
|
|
14
|
+
*/
|
|
15
|
+
export declare function printTable(rows: readonly GridRow[], columns: readonly ColumnDef[], opts?: {
|
|
16
|
+
title?: string;
|
|
17
|
+
}): void;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { formatCell, cellValue, isNumeric } from './column';
|
|
2
|
+
const ESC = {
|
|
3
|
+
'&': '&',
|
|
4
|
+
'<': '<',
|
|
5
|
+
'>': '>',
|
|
6
|
+
'"': '"',
|
|
7
|
+
"'": ''',
|
|
8
|
+
};
|
|
9
|
+
/** Escape a string for safe HTML text/attribute interpolation. */
|
|
10
|
+
export function escapeHTML(s) {
|
|
11
|
+
return s.replace(/[&<>"']/g, (c) => ESC[c]);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Render rows to a semantic `<table>` HTML string (formatted via the column
|
|
15
|
+
* formatters; numeric columns right-aligned; sparkline/custom columns skipped).
|
|
16
|
+
* Values are HTML-escaped. Pure; unit-tested. Embed it, or use `printTable`.
|
|
17
|
+
*/
|
|
18
|
+
export function toHTMLTable(rows, columns) {
|
|
19
|
+
const cols = columns.filter((c) => c.type !== 'sparkline' && c.type !== 'custom');
|
|
20
|
+
const align = (c) => (isNumeric(c) ? ' style="text-align:right"' : '');
|
|
21
|
+
const head = cols.map((c) => `<th${align(c)}>${escapeHTML(c.header)}</th>`).join('');
|
|
22
|
+
const body = rows
|
|
23
|
+
.map((row) => '<tr>' +
|
|
24
|
+
cols.map((c) => `<td${align(c)}>${escapeHTML(formatCell(c, cellValue(c, row), row))}</td>`).join('') +
|
|
25
|
+
'</tr>')
|
|
26
|
+
.join('');
|
|
27
|
+
return `<table><thead><tr>${head}</tr></thead><tbody>${body}</tbody></table>`;
|
|
28
|
+
}
|
|
29
|
+
const PRINT_CSS = `
|
|
30
|
+
*{box-sizing:border-box}
|
|
31
|
+
body{font:12px -apple-system,system-ui,sans-serif;color:#111;margin:24px}
|
|
32
|
+
h1{font-size:16px;margin:0 0 12px}
|
|
33
|
+
table{border-collapse:collapse;width:100%}
|
|
34
|
+
th,td{padding:4px 8px;border:1px solid #ddd;white-space:nowrap;text-align:left}
|
|
35
|
+
thead th{background:#f4f4f4;font-weight:600}
|
|
36
|
+
tbody tr:nth-child(even){background:#fafafa}
|
|
37
|
+
@media print{body{margin:0}}`;
|
|
38
|
+
/**
|
|
39
|
+
* Open a print window with all rows as a clean table and trigger the print
|
|
40
|
+
* dialog. No-op outside the browser (or if a popup is blocked). For PDFs, users
|
|
41
|
+
* "Save as PDF" from the print dialog.
|
|
42
|
+
*/
|
|
43
|
+
export function printTable(rows, columns, opts = {}) {
|
|
44
|
+
if (typeof window === 'undefined' || typeof window.open !== 'function')
|
|
45
|
+
return;
|
|
46
|
+
const win = window.open('', '_blank');
|
|
47
|
+
if (!win)
|
|
48
|
+
return; // popup blocked
|
|
49
|
+
const title = opts.title ?? 'Grid';
|
|
50
|
+
win.document.write(`<!doctype html><html><head><meta charset="utf-8"><title>${escapeHTML(title)}</title>` +
|
|
51
|
+
`<style>${PRINT_CSS}</style></head><body><h1>${escapeHTML(title)}</h1>` +
|
|
52
|
+
`${toHTMLTable(rows, columns)}` +
|
|
53
|
+
`<script>window.onload=function(){window.print()}<\/script></body></html>`);
|
|
54
|
+
win.document.close();
|
|
55
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -13,9 +13,10 @@ export type { PivotConfig, PivotResult } from './grid/pivot';
|
|
|
13
13
|
export { themeVars, darkTheme, lightTheme, highContrastDark, highContrastLight, midnightTheme, terminalTheme, themePresets, } from './grid/theme';
|
|
14
14
|
export type { GridTheme, ThemePreset } from './grid/theme';
|
|
15
15
|
export { drawCandles, setupHiDpiCanvas } from './sparkline/sparkline-render';
|
|
16
|
-
export { toCSV, exportCSV } from './grid/export';
|
|
16
|
+
export { toCSV, exportCSV, parseCSV, parseCSVMatrix, parseTSV, parseJSON, parseRows, rowsFromObjects, } from './grid/export';
|
|
17
17
|
export { exportXLSX } from './grid/export-xlsx';
|
|
18
18
|
export type { ExportOptions } from './grid/export';
|
|
19
|
+
export { toHTMLTable, printTable, escapeHTML } from './grid/print';
|
|
19
20
|
export { createArraySource } from './grid/source';
|
|
20
21
|
export type { RowSource, RowRange, RowSourceParams, RowSourceResult, ArraySourceOptions, } from './grid/source';
|
|
21
22
|
export type { Candle } from './types';
|
package/dist/index.js
CHANGED
|
@@ -17,8 +17,10 @@ export { pivot } from './grid/pivot';
|
|
|
17
17
|
export { themeVars, darkTheme, lightTheme, highContrastDark, highContrastLight, midnightTheme, terminalTheme, themePresets, } from './grid/theme';
|
|
18
18
|
// Sparkline canvas primitives (draw candlesticks on your own canvas)
|
|
19
19
|
export { drawCandles, setupHiDpiCanvas } from './sparkline/sparkline-render';
|
|
20
|
-
// Export (CSV is dependency-free; XLSX dynamic-imports the optional `xlsx` peer)
|
|
21
|
-
export { toCSV, exportCSV } from './grid/export';
|
|
20
|
+
// Export + import (CSV is dependency-free; XLSX dynamic-imports the optional `xlsx` peer)
|
|
21
|
+
export { toCSV, exportCSV, parseCSV, parseCSVMatrix, parseTSV, parseJSON, parseRows, rowsFromObjects, } from './grid/export';
|
|
22
22
|
export { exportXLSX } from './grid/export-xlsx';
|
|
23
|
+
// Printable / export HTML (renders ALL rows, unlike the virtualized grid)
|
|
24
|
+
export { toHTMLTable, printTable, escapeHTML } from './grid/print';
|
|
23
25
|
// Server-side / windowed data source
|
|
24
26
|
export { createArraySource } from './grid/source';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bo-grid",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "Tiny, fast Svelte 5 data grid: canvas sparklines, batched realtime cell updates, and virtual scrolling. A free, fintech-focused alternative to heavyweight grids.",
|
|
@@ -69,6 +69,7 @@
|
|
|
69
69
|
"size:lib": "vite build --config vite.lib.config.ts && node scripts/size-lib.mjs",
|
|
70
70
|
"smoke": "vite build --base=./ --outDir demo-dist && node scripts/smoke.mjs",
|
|
71
71
|
"smoke:wc": "node scripts/wc-smoke.mjs",
|
|
72
|
+
"check:examples": "node scripts/check-examples.mjs",
|
|
72
73
|
"ssr": "node scripts/ssr.mjs",
|
|
73
74
|
"bench": "node scripts/bench.mjs",
|
|
74
75
|
"test": "vitest run",
|