bo-grid 0.21.0 → 1.0.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 +146 -31
- package/dist/{bo-grid.FilterMenu-BHI6rILc.js → bo-grid.FilterMenu-BBxlxrnZ.js} +1 -1
- package/dist/{bo-grid.ToolPanel-C3u-4YKc.js → bo-grid.ToolPanel-CCBi982x.js} +1 -1
- package/dist/bo-grid.element-BZGnfKB_.js +6898 -0
- package/dist/bo-grid.element.d.ts +17 -0
- package/dist/bo-grid.element.js +3 -2
- package/dist/grid/Cell.svelte +61 -9
- package/dist/grid/Grid.svelte +213 -19
- package/dist/grid/Grid.svelte.d.ts +21 -1
- package/dist/grid/Pager.svelte +45 -0
- package/dist/grid/Pager.svelte.d.ts +3 -0
- package/dist/grid/column.d.ts +36 -3
- package/dist/grid/column.js +17 -0
- 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 +8 -2
- package/dist/index.js +4 -3
- package/package.json +4 -2
- package/dist/bo-grid.element-DPnHUXMa.js +0 -6623
package/dist/grid/Pager.svelte
CHANGED
|
@@ -6,12 +6,20 @@
|
|
|
6
6
|
pageCount,
|
|
7
7
|
total,
|
|
8
8
|
onGoto,
|
|
9
|
+
pageSize,
|
|
10
|
+
pageSizeOptions,
|
|
11
|
+
onPageSize,
|
|
9
12
|
}: {
|
|
10
13
|
page: number;
|
|
11
14
|
pageCount: number;
|
|
12
15
|
total: number;
|
|
13
16
|
onGoto: (page: number) => void;
|
|
17
|
+
pageSize?: number;
|
|
18
|
+
pageSizeOptions?: number[];
|
|
19
|
+
onPageSize?: (size: number) => void;
|
|
14
20
|
} = $props();
|
|
21
|
+
|
|
22
|
+
const showSizes = $derived(!!pageSizeOptions && pageSizeOptions.length > 0);
|
|
15
23
|
</script>
|
|
16
24
|
|
|
17
25
|
<div class="pager" role="navigation" aria-label="Pagination">
|
|
@@ -20,6 +28,20 @@
|
|
|
20
28
|
<span class="pageinfo">Page {page + 1} of {pageCount} · {total.toLocaleString()} rows</span>
|
|
21
29
|
<button type="button" class="pg" disabled={page >= pageCount - 1} onclick={() => onGoto(page + 1)}>Next ›</button>
|
|
22
30
|
<button type="button" class="pg" disabled={page >= pageCount - 1} aria-label="Last page" onclick={() => onGoto(pageCount - 1)}>»</button>
|
|
31
|
+
{#if showSizes}
|
|
32
|
+
<label class="pgsize">
|
|
33
|
+
Rows
|
|
34
|
+
<select
|
|
35
|
+
aria-label="Rows per page"
|
|
36
|
+
value={pageSize}
|
|
37
|
+
onchange={(e) => onPageSize?.(Number((e.currentTarget as HTMLSelectElement).value))}
|
|
38
|
+
>
|
|
39
|
+
{#each pageSizeOptions as opt (opt)}
|
|
40
|
+
<option value={opt}>{opt}</option>
|
|
41
|
+
{/each}
|
|
42
|
+
</select>
|
|
43
|
+
</label>
|
|
44
|
+
{/if}
|
|
23
45
|
</div>
|
|
24
46
|
|
|
25
47
|
<style>
|
|
@@ -60,4 +82,27 @@
|
|
|
60
82
|
color: var(--bo-text-dim);
|
|
61
83
|
font-variant-numeric: tabular-nums;
|
|
62
84
|
}
|
|
85
|
+
.pgsize {
|
|
86
|
+
display: inline-flex;
|
|
87
|
+
align-items: center;
|
|
88
|
+
gap: 5px;
|
|
89
|
+
margin-left: auto;
|
|
90
|
+
font-family: var(--bo-mono);
|
|
91
|
+
font-size: 11px;
|
|
92
|
+
color: var(--bo-text-dim);
|
|
93
|
+
}
|
|
94
|
+
.pgsize select {
|
|
95
|
+
font: inherit;
|
|
96
|
+
font-size: 11px;
|
|
97
|
+
color: var(--bo-text);
|
|
98
|
+
background: transparent;
|
|
99
|
+
border: 0.5px solid var(--bo-border);
|
|
100
|
+
border-radius: 6px;
|
|
101
|
+
padding: 2px 4px;
|
|
102
|
+
cursor: pointer;
|
|
103
|
+
}
|
|
104
|
+
.pgsize select:focus-visible {
|
|
105
|
+
outline: 2px solid var(--bo-sel-border);
|
|
106
|
+
outline-offset: 1px;
|
|
107
|
+
}
|
|
63
108
|
</style>
|
|
@@ -3,6 +3,9 @@ type $$ComponentProps = {
|
|
|
3
3
|
pageCount: number;
|
|
4
4
|
total: number;
|
|
5
5
|
onGoto: (page: number) => void;
|
|
6
|
+
pageSize?: number;
|
|
7
|
+
pageSizeOptions?: number[];
|
|
8
|
+
onPageSize?: (size: number) => void;
|
|
6
9
|
};
|
|
7
10
|
declare const Pager: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
8
11
|
type Pager = ReturnType<typeof Pager>;
|
package/dist/grid/column.d.ts
CHANGED
|
@@ -28,6 +28,13 @@ interface ColBase {
|
|
|
28
28
|
cellClass?: string | ((value: unknown, row: GridRow) => string | undefined);
|
|
29
29
|
/** Extra class(es) for this column's header. Target via `:global`. */
|
|
30
30
|
headerClass?: string;
|
|
31
|
+
/** Show a styled tooltip on the column header (the same themed bubble as cell
|
|
32
|
+
`tooltip`) — describe what the column means, units, caveats. Hovering
|
|
33
|
+
anywhere on the header reveals it. */
|
|
34
|
+
headerTooltip?: string;
|
|
35
|
+
/** Render a small ⓘ info icon in the header as a visible cue that a
|
|
36
|
+
`headerTooltip` is available. Ignored without `headerTooltip`. */
|
|
37
|
+
headerInfo?: boolean;
|
|
31
38
|
/** Amber flash on value change (drives off the row's flashSeq/flashDir). */
|
|
32
39
|
flash?: boolean;
|
|
33
40
|
/** Set false to disable header-click sorting on this column. */
|
|
@@ -48,13 +55,26 @@ interface ColBase {
|
|
|
48
55
|
/** Editable choices: when set, editing renders a `<select>` of these options
|
|
49
56
|
instead of a text input (enum/status columns). */
|
|
50
57
|
options?: string[];
|
|
51
|
-
/**
|
|
52
|
-
when content
|
|
53
|
-
|
|
58
|
+
/** Show a styled floating tooltip on each cell (themed, instant — replaces the
|
|
59
|
+
native `title`). `true` shows the full formatted value (handy when content
|
|
60
|
+
truncates); a function returns custom text per value/row (any column type,
|
|
61
|
+
including `custom`). Return '' to suppress for a given cell. */
|
|
62
|
+
tooltip?: boolean | ((value: unknown, row: GridRow) => string);
|
|
63
|
+
/** Let long content wrap to multiple lines instead of truncating with an
|
|
64
|
+
ellipsis. Pair with a taller `rowHeight`. Default false (single-line +
|
|
65
|
+
ellipsis). */
|
|
66
|
+
wrap?: boolean;
|
|
54
67
|
/** Custom display formatter, overriding the built-in type formatter. Applies
|
|
55
68
|
to display, tooltip, copy and (formatted) export. `row` is absent for
|
|
56
69
|
aggregate cells. */
|
|
57
70
|
format?: (value: unknown, row?: GridRow) => string;
|
|
71
|
+
/** JS cell renderer — the framework-agnostic alternative to the `cell`
|
|
72
|
+
snippet, for React/Vue/vanilla (and the `<bo-grid>` web component). Return
|
|
73
|
+
an `HTMLElement`/`Node` (safe — appended as-is) or an HTML **string**
|
|
74
|
+
(inserted as innerHTML — YOU must sanitize any untrusted data; prefer
|
|
75
|
+
returning a Node for that). Overrides the cell's display only — sort,
|
|
76
|
+
filter, tooltip, copy and export still use the value/`format`. */
|
|
77
|
+
render?: (ctx: CellRenderContext) => string | Node | null | undefined;
|
|
58
78
|
/** Set false to disable drag-to-resize on this column (default on). */
|
|
59
79
|
resizable?: boolean;
|
|
60
80
|
/** Parent header label. Consecutive columns sharing a `group` render under a
|
|
@@ -117,6 +137,12 @@ export interface DataBarGeom {
|
|
|
117
137
|
/** The value is negative (use the bar's `negative` colour). */
|
|
118
138
|
negative: boolean;
|
|
119
139
|
}
|
|
140
|
+
/** Context passed to a column's JS `render` hook. */
|
|
141
|
+
export interface CellRenderContext {
|
|
142
|
+
value: unknown;
|
|
143
|
+
row: GridRow;
|
|
144
|
+
column: ColumnDef;
|
|
145
|
+
}
|
|
120
146
|
export interface CellEditEvent {
|
|
121
147
|
row: GridRow;
|
|
122
148
|
column: ColumnDef;
|
|
@@ -204,6 +230,13 @@ export interface GridRow {
|
|
|
204
230
|
*/
|
|
205
231
|
export declare function cellValue(col: ColumnDef, row: GridRow): unknown;
|
|
206
232
|
export declare function formatCell(col: ColumnDef, value: unknown, row?: GridRow): string;
|
|
233
|
+
/**
|
|
234
|
+
* Resolve a cell's tooltip text for the styled floating tooltip. A boolean
|
|
235
|
+
* `tooltip` shows the formatted value (skipped for sparkline/custom — no text
|
|
236
|
+
* form); a function returns custom text for any column type. Empty/whitespace →
|
|
237
|
+
* no tooltip. Pure; unit-tested.
|
|
238
|
+
*/
|
|
239
|
+
export declare function tooltipText(col: ColumnDef, value: unknown, row: GridRow): string | undefined;
|
|
207
240
|
export declare function isSortable(col: ColumnDef): boolean;
|
|
208
241
|
export declare function isEditable(col: ColumnDef): boolean;
|
|
209
242
|
export declare function compareRows(a: GridRow, b: GridRow, sort: SortState, col?: ColumnDef): number;
|
package/dist/grid/column.js
CHANGED
|
@@ -39,6 +39,23 @@ export function formatCell(col, value, row) {
|
|
|
39
39
|
return value == null ? '' : String(value);
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Resolve a cell's tooltip text for the styled floating tooltip. A boolean
|
|
44
|
+
* `tooltip` shows the formatted value (skipped for sparkline/custom — no text
|
|
45
|
+
* form); a function returns custom text for any column type. Empty/whitespace →
|
|
46
|
+
* no tooltip. Pure; unit-tested.
|
|
47
|
+
*/
|
|
48
|
+
export function tooltipText(col, value, row) {
|
|
49
|
+
const t = col.tooltip;
|
|
50
|
+
if (!t)
|
|
51
|
+
return undefined;
|
|
52
|
+
const text = typeof t === 'function'
|
|
53
|
+
? t(value, row)
|
|
54
|
+
: col.type === 'sparkline' || col.type === 'custom'
|
|
55
|
+
? ''
|
|
56
|
+
: formatCell(col, value, row);
|
|
57
|
+
return text && text.trim() ? text : undefined;
|
|
58
|
+
}
|
|
42
59
|
export function isSortable(col) {
|
|
43
60
|
return col.type !== 'sparkline' && col.sortable !== false;
|
|
44
61
|
}
|
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
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
import type { ComponentProps } from 'svelte';
|
|
2
|
+
import type GridComponent from './grid/Grid.svelte';
|
|
1
3
|
export { default as Grid } from './grid/Grid.svelte';
|
|
2
4
|
export { default as Sparkline } from './sparkline/Sparkline.svelte';
|
|
5
|
+
/** Every `<Grid>` prop, as one object type. Use it to type a config object —
|
|
6
|
+
especially the `<bo-grid>` web component's `config` (see `bo-grid/element`). */
|
|
7
|
+
export type GridProps = ComponentProps<typeof GridComponent>;
|
|
3
8
|
export type { LazyGroup } from './grid/grouping';
|
|
4
|
-
export type { ColumnDef, Align, GridRow, SortDir, SortState, CellEditEvent, BadgeTone, DataBarConfig, IconRule, ColorScaleConfig, } from './grid/column';
|
|
9
|
+
export type { ColumnDef, Align, GridRow, SortDir, SortState, CellEditEvent, CellRenderContext, BadgeTone, DataBarConfig, IconRule, ColorScaleConfig, } from './grid/column';
|
|
5
10
|
export type { AggKind, AggResult } from './grid/aggregate';
|
|
6
11
|
export type { ColumnFilter, FilterKind, TextOp, NumberOp, DateOp } from './grid/filtering';
|
|
7
12
|
export { fmtPrice, fmtPercent, fmtVolume, fmtDate, fmtCurrency, relativeTime } from './format/format';
|
|
@@ -13,9 +18,10 @@ export type { PivotConfig, PivotResult } from './grid/pivot';
|
|
|
13
18
|
export { themeVars, darkTheme, lightTheme, highContrastDark, highContrastLight, midnightTheme, terminalTheme, themePresets, } from './grid/theme';
|
|
14
19
|
export type { GridTheme, ThemePreset } from './grid/theme';
|
|
15
20
|
export { drawCandles, setupHiDpiCanvas } from './sparkline/sparkline-render';
|
|
16
|
-
export { toCSV, exportCSV } from './grid/export';
|
|
21
|
+
export { toCSV, exportCSV, parseCSV, parseCSVMatrix, parseTSV, parseJSON, parseRows, rowsFromObjects, } from './grid/export';
|
|
17
22
|
export { exportXLSX } from './grid/export-xlsx';
|
|
18
23
|
export type { ExportOptions } from './grid/export';
|
|
24
|
+
export { toHTMLTable, printTable, escapeHTML } from './grid/print';
|
|
19
25
|
export { createArraySource } from './grid/source';
|
|
20
26
|
export type { RowSource, RowRange, RowSourceParams, RowSourceResult, ArraySourceOptions, } from './grid/source';
|
|
21
27
|
export type { Candle } from './types';
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
// This surface is intentionally small — every export is a compatibility promise.
|
|
5
5
|
// Internal helpers (layout, sorting, grouping, selection internals) are NOT
|
|
6
6
|
// exported; they can change freely between versions.
|
|
7
|
-
// Components
|
|
8
7
|
export { default as Grid } from './grid/Grid.svelte';
|
|
9
8
|
export { default as Sparkline } from './sparkline/Sparkline.svelte';
|
|
10
9
|
// Value formatters (handy when building custom cell content)
|
|
@@ -17,8 +16,10 @@ export { pivot } from './grid/pivot';
|
|
|
17
16
|
export { themeVars, darkTheme, lightTheme, highContrastDark, highContrastLight, midnightTheme, terminalTheme, themePresets, } from './grid/theme';
|
|
18
17
|
// Sparkline canvas primitives (draw candlesticks on your own canvas)
|
|
19
18
|
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';
|
|
19
|
+
// Export + import (CSV is dependency-free; XLSX dynamic-imports the optional `xlsx` peer)
|
|
20
|
+
export { toCSV, exportCSV, parseCSV, parseCSVMatrix, parseTSV, parseJSON, parseRows, rowsFromObjects, } from './grid/export';
|
|
22
21
|
export { exportXLSX } from './grid/export-xlsx';
|
|
22
|
+
// Printable / export HTML (renders ALL rows, unlike the virtualized grid)
|
|
23
|
+
export { toHTMLTable, printTable, escapeHTML } from './grid/print';
|
|
23
24
|
// Server-side / windowed data source
|
|
24
25
|
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": "1.0.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.",
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
"default": "./dist/charts/index.js"
|
|
40
40
|
},
|
|
41
41
|
"./element": {
|
|
42
|
+
"types": "./dist/bo-grid.element.d.ts",
|
|
42
43
|
"default": "./dist/bo-grid.element.js"
|
|
43
44
|
},
|
|
44
45
|
"./package.json": "./package.json"
|
|
@@ -61,7 +62,7 @@
|
|
|
61
62
|
"demo:build": "vite build",
|
|
62
63
|
"pages:build": "vite build --base=/bo-grid/ --outDir demo-dist",
|
|
63
64
|
"preview": "vite preview",
|
|
64
|
-
"package": "svelte-package -i src/lib -o dist && node scripts/clean-dist.mjs && pnpm run build:wc",
|
|
65
|
+
"package": "svelte-package -i src/lib -o dist && node scripts/clean-dist.mjs && pnpm run build:wc && node scripts/emit-element-dts.mjs",
|
|
65
66
|
"build:wc": "vite build --config vite.wc.config.ts",
|
|
66
67
|
"prepublishOnly": "pnpm run package",
|
|
67
68
|
"check": "svelte-check --tsconfig ./tsconfig.json",
|
|
@@ -69,6 +70,7 @@
|
|
|
69
70
|
"size:lib": "vite build --config vite.lib.config.ts && node scripts/size-lib.mjs",
|
|
70
71
|
"smoke": "vite build --base=./ --outDir demo-dist && node scripts/smoke.mjs",
|
|
71
72
|
"smoke:wc": "node scripts/wc-smoke.mjs",
|
|
73
|
+
"check:examples": "node scripts/check-examples.mjs",
|
|
72
74
|
"ssr": "node scripts/ssr.mjs",
|
|
73
75
|
"bench": "node scripts/bench.mjs",
|
|
74
76
|
"test": "vitest run",
|