bo-grid 0.8.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 +254 -27
- package/dist/bo-grid.FilterMenu-BHI6rILc.js +154 -0
- package/dist/bo-grid.ToolPanel-C3u-4YKc.js +34 -0
- package/dist/bo-grid.element-DPnHUXMa.js +6623 -0
- package/dist/bo-grid.element.js +4 -0
- package/dist/charts/BarChart.svelte +50 -0
- package/dist/charts/BarChart.svelte.d.ts +16 -0
- package/dist/charts/DonutChart.svelte +54 -0
- package/dist/charts/DonutChart.svelte.d.ts +18 -0
- package/dist/charts/Legend.svelte +47 -0
- package/dist/charts/Legend.svelte.d.ts +12 -0
- package/dist/charts/LineChart.svelte +59 -0
- package/dist/charts/LineChart.svelte.d.ts +14 -0
- package/dist/charts/StackedBarChart.svelte +56 -0
- package/dist/charts/StackedBarChart.svelte.d.ts +18 -0
- package/dist/charts/chart-math.d.ts +57 -0
- package/dist/charts/chart-math.js +174 -0
- package/dist/charts/index.d.ts +8 -0
- package/dist/charts/index.js +11 -0
- package/dist/charts/palette.d.ts +4 -0
- package/dist/charts/palette.js +14 -0
- package/dist/format/format.d.ts +6 -0
- package/dist/format/format.js +41 -0
- package/dist/grid/Cell.svelte +247 -8
- package/dist/grid/Cell.svelte.d.ts +6 -0
- package/dist/grid/FilterMenu.svelte +7 -0
- package/dist/grid/Grid.svelte +307 -85
- package/dist/grid/Grid.svelte.d.ts +19 -0
- package/dist/grid/GroupRow.svelte +5 -2
- package/dist/grid/Pager.svelte +4 -0
- package/dist/grid/RowMenu.svelte +65 -2
- package/dist/grid/ToolPanel.svelte +5 -0
- package/dist/grid/column.d.ts +133 -0
- package/dist/grid/column.js +133 -4
- package/dist/grid/colvirt.d.ts +15 -0
- package/dist/grid/colvirt.js +43 -0
- package/dist/grid/export.d.ts +33 -0
- package/dist/grid/export.js +158 -2
- package/dist/grid/filtering.d.ts +5 -2
- package/dist/grid/filtering.js +5 -4
- package/dist/grid/grouping.d.ts +30 -0
- package/dist/grid/grouping.js +33 -0
- package/dist/grid/print.d.ts +17 -0
- package/dist/grid/print.js +55 -0
- package/dist/grid/theme.d.ts +15 -0
- package/dist/grid/theme.js +78 -0
- package/dist/grid/tree.d.ts +19 -7
- package/dist/grid/tree.js +16 -11
- package/dist/index.d.ts +7 -5
- package/dist/index.js +6 -4
- package/package.json +13 -2
package/dist/grid/export.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { formatCell, isNumeric } from './column';
|
|
1
|
+
import { formatCell, isNumeric, cellValue } from './column';
|
|
2
2
|
function rawValue(col, v) {
|
|
3
3
|
if (col.type === 'date')
|
|
4
4
|
return formatCell(col, v); // epoch ms isn't useful raw
|
|
@@ -19,7 +19,10 @@ export function rowsToMatrix(rows, columns, opts = {}) {
|
|
|
19
19
|
if (opts.header !== false)
|
|
20
20
|
matrix.push(cols.map((c) => c.header));
|
|
21
21
|
for (const row of rows) {
|
|
22
|
-
matrix.push(cols.map((c) =>
|
|
22
|
+
matrix.push(cols.map((c) => {
|
|
23
|
+
const v = cellValue(c, row);
|
|
24
|
+
return opts.formatted ? formatCell(c, v, row) : rawValue(c, v);
|
|
25
|
+
}));
|
|
23
26
|
}
|
|
24
27
|
return matrix;
|
|
25
28
|
}
|
|
@@ -46,3 +49,156 @@ export function download(filename, content, mime = 'text/csv;charset=utf-8') {
|
|
|
46
49
|
export function exportCSV(filename, rows, columns, opts = {}) {
|
|
47
50
|
download(filename, toCSV(rows, columns, opts));
|
|
48
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
|
+
}
|
package/dist/grid/filtering.d.ts
CHANGED
|
@@ -35,7 +35,10 @@ export declare function emptyFilter(kind: FilterKind): ColumnFilter;
|
|
|
35
35
|
export declare function isFilterActive(f: ColumnFilter | undefined | null): boolean;
|
|
36
36
|
/** Does one cell value satisfy one filter? An inactive filter passes everything. */
|
|
37
37
|
export declare function matchesFilter(value: unknown, f: ColumnFilter): boolean;
|
|
38
|
+
/** Resolve a column's value for filtering — `row[key]` by default; Grid passes a
|
|
39
|
+
computed-aware resolver so computed columns filter on their derived value. */
|
|
40
|
+
export type ValueResolver = (row: GridRow, key: string) => unknown;
|
|
38
41
|
/** AND across every active per-column filter. */
|
|
39
|
-
export declare function passesFilters(row: GridRow, filters: Record<string, ColumnFilter
|
|
42
|
+
export declare function passesFilters(row: GridRow, filters: Record<string, ColumnFilter>, valueOf?: ValueResolver): boolean;
|
|
40
43
|
/** Sorted unique string values for a column — the set-filter checklist. */
|
|
41
|
-
export declare function distinctValues(rows: readonly GridRow[], key: string): string[];
|
|
44
|
+
export declare function distinctValues(rows: readonly GridRow[], key: string, valueOf?: ValueResolver): string[];
|
package/dist/grid/filtering.js
CHANGED
|
@@ -89,19 +89,20 @@ export function matchesFilter(value, f) {
|
|
|
89
89
|
}
|
|
90
90
|
return true; // unreachable fallback
|
|
91
91
|
}
|
|
92
|
+
const byKey = (row, key) => row[key];
|
|
92
93
|
/** AND across every active per-column filter. */
|
|
93
|
-
export function passesFilters(row, filters) {
|
|
94
|
+
export function passesFilters(row, filters, valueOf = byKey) {
|
|
94
95
|
for (const key in filters) {
|
|
95
96
|
const f = filters[key];
|
|
96
|
-
if (isFilterActive(f) && !matchesFilter(row
|
|
97
|
+
if (isFilterActive(f) && !matchesFilter(valueOf(row, key), f))
|
|
97
98
|
return false;
|
|
98
99
|
}
|
|
99
100
|
return true;
|
|
100
101
|
}
|
|
101
102
|
/** Sorted unique string values for a column — the set-filter checklist. */
|
|
102
|
-
export function distinctValues(rows, key) {
|
|
103
|
+
export function distinctValues(rows, key, valueOf = byKey) {
|
|
103
104
|
const seen = new Set();
|
|
104
105
|
for (const row of rows)
|
|
105
|
-
seen.add(String(row
|
|
106
|
+
seen.add(String(valueOf(row, key) ?? ''));
|
|
106
107
|
return [...seen].sort((a, b) => a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' }));
|
|
107
108
|
}
|
package/dist/grid/grouping.d.ts
CHANGED
|
@@ -10,6 +10,19 @@ export interface GroupNode {
|
|
|
10
10
|
rows: GridRow[];
|
|
11
11
|
count: number;
|
|
12
12
|
collapsed: boolean;
|
|
13
|
+
/** Server-provided aggregate display strings, keyed by column key (lazy groups).
|
|
14
|
+
When present, the group header shows these instead of computing from `rows`. */
|
|
15
|
+
aggText?: Record<string, string>;
|
|
16
|
+
}
|
|
17
|
+
/** A server-side group summary (lazy grouping): the header data without the leaf
|
|
18
|
+
rows, which load on expand via `loadGroup`. */
|
|
19
|
+
export interface LazyGroup {
|
|
20
|
+
key: string;
|
|
21
|
+
/** Header label (defaults to `key`). */
|
|
22
|
+
label?: string;
|
|
23
|
+
count?: number;
|
|
24
|
+
/** Preformatted aggregate strings keyed by column key, shown in the header. */
|
|
25
|
+
agg?: Record<string, string>;
|
|
13
26
|
}
|
|
14
27
|
export type VisualRow = {
|
|
15
28
|
kind: 'data';
|
|
@@ -19,6 +32,9 @@ export type VisualRow = {
|
|
|
19
32
|
} | {
|
|
20
33
|
kind: 'group';
|
|
21
34
|
group: GroupNode;
|
|
35
|
+
} | {
|
|
36
|
+
kind: 'treeloading';
|
|
37
|
+
depth: number;
|
|
22
38
|
};
|
|
23
39
|
/**
|
|
24
40
|
* Flatten data rows into the visual row list the grid renders: a stream of
|
|
@@ -36,3 +52,17 @@ export declare function buildFlatRows(rows: GridRow[], groupBy: string[], collap
|
|
|
36
52
|
* nearest preceding header at each depth until the depth-0 group is found.
|
|
37
53
|
*/
|
|
38
54
|
export declare function activeGroupsAt(flat: VisualRow[], idx: number): GroupNode[];
|
|
55
|
+
/** How lazy grouping reads expand/loading state and the rows loaded per group. */
|
|
56
|
+
export interface LazyGroupAccess {
|
|
57
|
+
isExpanded: (key: string) => boolean;
|
|
58
|
+
/** Loaded leaf rows for a group, or undefined when not yet loaded. */
|
|
59
|
+
rowsOf: (key: string) => readonly GridRow[] | undefined;
|
|
60
|
+
isLoading: (key: string) => boolean;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Flatten server-side group summaries into visual rows: a group header per group,
|
|
64
|
+
* then — when expanded — its loaded leaf rows, or a single `treeloading`
|
|
65
|
+
* placeholder while they load. Aggregates come from the summary (not computed).
|
|
66
|
+
* Pure; unit-tested.
|
|
67
|
+
*/
|
|
68
|
+
export declare function buildLazyGroupRows(groups: readonly LazyGroup[], access: LazyGroupAccess): VisualRow[];
|
package/dist/grid/grouping.js
CHANGED
|
@@ -60,3 +60,36 @@ export function activeGroupsAt(flat, idx) {
|
|
|
60
60
|
}
|
|
61
61
|
return [...found.values()].sort((a, b) => a.depth - b.depth);
|
|
62
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Flatten server-side group summaries into visual rows: a group header per group,
|
|
65
|
+
* then — when expanded — its loaded leaf rows, or a single `treeloading`
|
|
66
|
+
* placeholder while they load. Aggregates come from the summary (not computed).
|
|
67
|
+
* Pure; unit-tested.
|
|
68
|
+
*/
|
|
69
|
+
export function buildLazyGroupRows(groups, access) {
|
|
70
|
+
const out = [];
|
|
71
|
+
for (const g of groups) {
|
|
72
|
+
const expanded = access.isExpanded(g.key);
|
|
73
|
+
out.push({
|
|
74
|
+
kind: 'group',
|
|
75
|
+
group: {
|
|
76
|
+
path: g.key,
|
|
77
|
+
depth: 0,
|
|
78
|
+
value: g.label ?? g.key,
|
|
79
|
+
rows: [],
|
|
80
|
+
count: g.count ?? 0,
|
|
81
|
+
collapsed: !expanded,
|
|
82
|
+
aggText: g.agg,
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
if (!expanded)
|
|
86
|
+
continue;
|
|
87
|
+
const loaded = access.rowsOf(g.key);
|
|
88
|
+
if (loaded)
|
|
89
|
+
for (const row of loaded)
|
|
90
|
+
out.push({ kind: 'data', row });
|
|
91
|
+
else if (access.isLoading(g.key))
|
|
92
|
+
out.push({ kind: 'treeloading', depth: 1 });
|
|
93
|
+
}
|
|
94
|
+
return out;
|
|
95
|
+
}
|
|
@@ -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/grid/theme.d.ts
CHANGED
|
@@ -35,3 +35,18 @@ export interface GridTheme {
|
|
|
35
35
|
export declare function themeVars(theme: GridTheme): string;
|
|
36
36
|
export declare const darkTheme: GridTheme;
|
|
37
37
|
export declare const lightTheme: GridTheme;
|
|
38
|
+
export declare const highContrastDark: GridTheme;
|
|
39
|
+
export declare const highContrastLight: GridTheme;
|
|
40
|
+
export declare const midnightTheme: GridTheme;
|
|
41
|
+
export declare const terminalTheme: GridTheme;
|
|
42
|
+
/** All built-in presets, keyed by name (handy for a theme picker). */
|
|
43
|
+
export declare const themePresets: {
|
|
44
|
+
dark: GridTheme;
|
|
45
|
+
light: GridTheme;
|
|
46
|
+
'high-contrast-dark': GridTheme;
|
|
47
|
+
'high-contrast-light': GridTheme;
|
|
48
|
+
midnight: GridTheme;
|
|
49
|
+
terminal: GridTheme;
|
|
50
|
+
};
|
|
51
|
+
/** Built-in preset name. */
|
|
52
|
+
export type ThemePreset = keyof typeof themePresets;
|
package/dist/grid/theme.js
CHANGED
|
@@ -64,3 +64,81 @@ export const lightTheme = {
|
|
|
64
64
|
selBorder: '#6366f1',
|
|
65
65
|
scheme: 'light',
|
|
66
66
|
};
|
|
67
|
+
// High-contrast dark (accessibility): pure black, white text, strong borders and
|
|
68
|
+
// vivid status colours — comfortably exceeds WCAG AA, toward AAA.
|
|
69
|
+
export const highContrastDark = {
|
|
70
|
+
bg: '#000000',
|
|
71
|
+
headerBg: '#0a0a0a',
|
|
72
|
+
rowA: '#000000',
|
|
73
|
+
rowB: '#0a0a0a',
|
|
74
|
+
rowHover: '#1c1c1c',
|
|
75
|
+
text: '#ffffff',
|
|
76
|
+
textDim: '#c8c8c8',
|
|
77
|
+
border: 'rgba(255,255,255,0.34)',
|
|
78
|
+
up: '#00e676',
|
|
79
|
+
down: '#ff5252',
|
|
80
|
+
amber: '#ffd740',
|
|
81
|
+
selFill: 'rgba(255,255,255,0.20)',
|
|
82
|
+
selBorder: '#ffffff',
|
|
83
|
+
scheme: 'dark',
|
|
84
|
+
};
|
|
85
|
+
// High-contrast light (accessibility): white, near-black text, strong borders.
|
|
86
|
+
export const highContrastLight = {
|
|
87
|
+
bg: '#ffffff',
|
|
88
|
+
headerBg: '#eeeeee',
|
|
89
|
+
rowA: '#ffffff',
|
|
90
|
+
rowB: '#f5f5f5',
|
|
91
|
+
rowHover: '#e3e3e3',
|
|
92
|
+
text: '#000000',
|
|
93
|
+
textDim: '#383838',
|
|
94
|
+
border: 'rgba(0,0,0,0.42)',
|
|
95
|
+
up: '#007a36',
|
|
96
|
+
down: '#c20000',
|
|
97
|
+
amber: '#7a5c00',
|
|
98
|
+
selFill: 'rgba(0,0,0,0.10)',
|
|
99
|
+
selBorder: '#000000',
|
|
100
|
+
scheme: 'light',
|
|
101
|
+
};
|
|
102
|
+
// Midnight: a deep navy/indigo dark theme — a calmer, "premium" alternative.
|
|
103
|
+
export const midnightTheme = {
|
|
104
|
+
bg: '#0f172a',
|
|
105
|
+
headerBg: '#0b1120',
|
|
106
|
+
rowA: '#0f172a',
|
|
107
|
+
rowB: '#121d35',
|
|
108
|
+
rowHover: '#1e293b',
|
|
109
|
+
text: '#e2e8f0',
|
|
110
|
+
textDim: '#94a3b8',
|
|
111
|
+
border: 'rgba(148,163,184,0.16)',
|
|
112
|
+
up: '#34d399',
|
|
113
|
+
down: '#fb7185',
|
|
114
|
+
amber: '#fbbf24',
|
|
115
|
+
selFill: 'rgba(129,140,248,0.22)',
|
|
116
|
+
selBorder: '#818cf8',
|
|
117
|
+
scheme: 'dark',
|
|
118
|
+
};
|
|
119
|
+
// Terminal: green phosphor on near-black — a retro fintech/console look.
|
|
120
|
+
export const terminalTheme = {
|
|
121
|
+
bg: '#0a0f0a',
|
|
122
|
+
headerBg: '#0d140d',
|
|
123
|
+
rowA: '#0a0f0a',
|
|
124
|
+
rowB: '#0e160e',
|
|
125
|
+
rowHover: '#16241a',
|
|
126
|
+
text: '#4ade80',
|
|
127
|
+
textDim: '#3f9e60',
|
|
128
|
+
border: 'rgba(74,222,128,0.20)',
|
|
129
|
+
up: '#4ade80',
|
|
130
|
+
down: '#f87171',
|
|
131
|
+
amber: '#fde047',
|
|
132
|
+
selFill: 'rgba(74,222,128,0.16)',
|
|
133
|
+
selBorder: '#4ade80',
|
|
134
|
+
scheme: 'dark',
|
|
135
|
+
};
|
|
136
|
+
/** All built-in presets, keyed by name (handy for a theme picker). */
|
|
137
|
+
export const themePresets = {
|
|
138
|
+
dark: darkTheme,
|
|
139
|
+
light: lightTheme,
|
|
140
|
+
'high-contrast-dark': highContrastDark,
|
|
141
|
+
'high-contrast-light': highContrastLight,
|
|
142
|
+
midnight: midnightTheme,
|
|
143
|
+
terminal: terminalTheme,
|
|
144
|
+
};
|
package/dist/grid/tree.d.ts
CHANGED
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
import type { GridRow } from './column';
|
|
2
2
|
import type { VisualRow } from './grouping';
|
|
3
|
-
/** Resolve a row's children (undefined/empty = leaf). */
|
|
3
|
+
/** Resolve a row's children (undefined/empty = leaf). Sync. */
|
|
4
4
|
export type GetChildren = (row: GridRow) => GridRow[] | undefined;
|
|
5
|
+
/** How the flattener reads a tree: which rows have children, which are expanded,
|
|
6
|
+
the children currently available (sync result or async cache), and which
|
|
7
|
+
expanded nodes are still loading. */
|
|
8
|
+
export interface TreeAccess {
|
|
9
|
+
/** Children available now (sync result or loaded cache); undefined = not loaded. */
|
|
10
|
+
childrenOf: (row: GridRow) => readonly GridRow[] | undefined;
|
|
11
|
+
/** Cheap predicate: does this row have children? (Drives the expand chevron.) */
|
|
12
|
+
hasChildren: (row: GridRow) => boolean;
|
|
13
|
+
isExpanded: (row: GridRow) => boolean;
|
|
14
|
+
/** Whether an expanded row's children are still loading (async trees). */
|
|
15
|
+
isLoading?: (row: GridRow) => boolean;
|
|
16
|
+
}
|
|
5
17
|
/**
|
|
6
|
-
* Flatten a tree of rows into the visible, depth-tagged
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
18
|
+
* Flatten a tree of rows into the visible, depth-tagged rows the grid renders.
|
|
19
|
+
* Pre-order DFS: each node is emitted, then — if it has children and is expanded
|
|
20
|
+
* — its loaded children one level deeper, or a single `treeloading` placeholder
|
|
21
|
+
* while they load. Pure: no row values are read, so a realtime tick never rebuilds
|
|
22
|
+
* this list.
|
|
11
23
|
*/
|
|
12
|
-
export declare function buildTreeRows(roots: readonly GridRow[],
|
|
24
|
+
export declare function buildTreeRows(roots: readonly GridRow[], access: TreeAccess): VisualRow[];
|
package/dist/grid/tree.js
CHANGED
|
@@ -1,19 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Flatten a tree of rows into the visible, depth-tagged
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
2
|
+
* Flatten a tree of rows into the visible, depth-tagged rows the grid renders.
|
|
3
|
+
* Pre-order DFS: each node is emitted, then — if it has children and is expanded
|
|
4
|
+
* — its loaded children one level deeper, or a single `treeloading` placeholder
|
|
5
|
+
* while they load. Pure: no row values are read, so a realtime tick never rebuilds
|
|
6
|
+
* this list.
|
|
7
7
|
*/
|
|
8
|
-
export function buildTreeRows(roots,
|
|
8
|
+
export function buildTreeRows(roots, access) {
|
|
9
|
+
const { childrenOf, hasChildren, isExpanded, isLoading } = access;
|
|
9
10
|
const out = [];
|
|
10
11
|
const walk = (nodes, depth) => {
|
|
11
12
|
for (const row of nodes) {
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
const has = hasChildren(row);
|
|
14
|
+
out.push({ kind: 'data', row, depth, hasChildren: has });
|
|
15
|
+
if (has && isExpanded(row)) {
|
|
16
|
+
const children = childrenOf(row);
|
|
17
|
+
if (children && children.length > 0)
|
|
18
|
+
walk(children, depth + 1);
|
|
19
|
+
else if (isLoading?.(row))
|
|
20
|
+
out.push({ kind: 'treeloading', depth: depth + 1 });
|
|
21
|
+
}
|
|
17
22
|
}
|
|
18
23
|
};
|
|
19
24
|
walk(roots, 0);
|
package/dist/index.d.ts
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
export { default as Grid } from './grid/Grid.svelte';
|
|
2
2
|
export { default as Sparkline } from './sparkline/Sparkline.svelte';
|
|
3
|
-
export type {
|
|
3
|
+
export type { LazyGroup } from './grid/grouping';
|
|
4
|
+
export type { ColumnDef, Align, GridRow, SortDir, SortState, CellEditEvent, BadgeTone, DataBarConfig, IconRule, ColorScaleConfig, } from './grid/column';
|
|
4
5
|
export type { AggKind, AggResult } from './grid/aggregate';
|
|
5
6
|
export type { ColumnFilter, FilterKind, TextOp, NumberOp, DateOp } from './grid/filtering';
|
|
6
|
-
export { fmtPrice, fmtPercent, fmtVolume, fmtDate } from './format/format';
|
|
7
|
+
export { fmtPrice, fmtPercent, fmtVolume, fmtDate, fmtCurrency, relativeTime } from './format/format';
|
|
7
8
|
export type { DateStyle } from './format/format';
|
|
8
9
|
export { aggregate } from './grid/aggregate';
|
|
9
10
|
export { heatColor } from './grid/heatmap';
|
|
10
11
|
export { pivot } from './grid/pivot';
|
|
11
12
|
export type { PivotConfig, PivotResult } from './grid/pivot';
|
|
12
|
-
export { themeVars, darkTheme, lightTheme } from './grid/theme';
|
|
13
|
-
export type { GridTheme } from './grid/theme';
|
|
13
|
+
export { themeVars, darkTheme, lightTheme, highContrastDark, highContrastLight, midnightTheme, terminalTheme, themePresets, } from './grid/theme';
|
|
14
|
+
export type { GridTheme, ThemePreset } from './grid/theme';
|
|
14
15
|
export { drawCandles, setupHiDpiCanvas } from './sparkline/sparkline-render';
|
|
15
|
-
export { toCSV, exportCSV } from './grid/export';
|
|
16
|
+
export { toCSV, exportCSV, parseCSV, parseCSVMatrix, parseTSV, parseJSON, parseRows, rowsFromObjects, } from './grid/export';
|
|
16
17
|
export { exportXLSX } from './grid/export-xlsx';
|
|
17
18
|
export type { ExportOptions } from './grid/export';
|
|
19
|
+
export { toHTMLTable, printTable, escapeHTML } from './grid/print';
|
|
18
20
|
export { createArraySource } from './grid/source';
|
|
19
21
|
export type { RowSource, RowRange, RowSourceParams, RowSourceResult, ArraySourceOptions, } from './grid/source';
|
|
20
22
|
export type { Candle } from './types';
|
package/dist/index.js
CHANGED
|
@@ -8,17 +8,19 @@
|
|
|
8
8
|
export { default as Grid } from './grid/Grid.svelte';
|
|
9
9
|
export { default as Sparkline } from './sparkline/Sparkline.svelte';
|
|
10
10
|
// Value formatters (handy when building custom cell content)
|
|
11
|
-
export { fmtPrice, fmtPercent, fmtVolume, fmtDate } from './format/format';
|
|
11
|
+
export { fmtPrice, fmtPercent, fmtVolume, fmtDate, fmtCurrency, relativeTime } from './format/format';
|
|
12
12
|
// Standalone helpers
|
|
13
13
|
export { aggregate } from './grid/aggregate';
|
|
14
14
|
export { heatColor } from './grid/heatmap';
|
|
15
15
|
export { pivot } from './grid/pivot';
|
|
16
16
|
// Theming
|
|
17
|
-
export { themeVars, darkTheme, lightTheme } from './grid/theme';
|
|
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.",
|
|
@@ -33,6 +33,14 @@
|
|
|
33
33
|
"svelte": "./dist/index.js",
|
|
34
34
|
"default": "./dist/index.js"
|
|
35
35
|
},
|
|
36
|
+
"./charts": {
|
|
37
|
+
"types": "./dist/charts/index.d.ts",
|
|
38
|
+
"svelte": "./dist/charts/index.js",
|
|
39
|
+
"default": "./dist/charts/index.js"
|
|
40
|
+
},
|
|
41
|
+
"./element": {
|
|
42
|
+
"default": "./dist/bo-grid.element.js"
|
|
43
|
+
},
|
|
36
44
|
"./package.json": "./package.json"
|
|
37
45
|
},
|
|
38
46
|
"files": [
|
|
@@ -53,12 +61,15 @@
|
|
|
53
61
|
"demo:build": "vite build",
|
|
54
62
|
"pages:build": "vite build --base=/bo-grid/ --outDir demo-dist",
|
|
55
63
|
"preview": "vite preview",
|
|
56
|
-
"package": "svelte-package -i src/lib -o dist && node scripts/clean-dist.mjs",
|
|
64
|
+
"package": "svelte-package -i src/lib -o dist && node scripts/clean-dist.mjs && pnpm run build:wc",
|
|
65
|
+
"build:wc": "vite build --config vite.wc.config.ts",
|
|
57
66
|
"prepublishOnly": "pnpm run package",
|
|
58
67
|
"check": "svelte-check --tsconfig ./tsconfig.json",
|
|
59
68
|
"size": "vite build && node scripts/size-check.mjs",
|
|
60
69
|
"size:lib": "vite build --config vite.lib.config.ts && node scripts/size-lib.mjs",
|
|
61
70
|
"smoke": "vite build --base=./ --outDir demo-dist && node scripts/smoke.mjs",
|
|
71
|
+
"smoke:wc": "node scripts/wc-smoke.mjs",
|
|
72
|
+
"check:examples": "node scripts/check-examples.mjs",
|
|
62
73
|
"ssr": "node scripts/ssr.mjs",
|
|
63
74
|
"bench": "node scripts/bench.mjs",
|
|
64
75
|
"test": "vitest run",
|