bo-grid 0.25.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 CHANGED
@@ -1,7 +1,7 @@
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, with a core that gzips to ~31 KB
4
+ realtime cell updates, and virtual scrolling, with a core that gzips to ~32 KB
5
5
  (Svelte external; unused exports tree-shake). A free alternative to the heavyweight
6
6
  grids that paywall these features.
7
7
 
@@ -13,8 +13,8 @@ grids that paywall these features.
13
13
  The demo is a gallery of grid types — a realtime **Trading desk**, a grouped
14
14
  **Portfolio** with subtotals and pivot, an editable **Spreadsheet**, a live
15
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.
16
+ **Wide** 60-column grid, a server-backed **Lazy tree**, and more — all on one
17
+ page, each grid lazy-mounting as you scroll (jump between them from the side rail).
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,7 +37,7 @@ 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 | **~31 KB gzip core** ([benchmarks](./BENCHMARKS.md)) |
40
+ | Bundle | hundreds of KB | **~32 KB gzip core** ([benchmarks](./BENCHMARKS.md)) |
41
41
  | Svelte | wrapper | native Svelte 5 |
42
42
 
43
43
  bo-grid ships most of the features other grids put behind a **paid (Enterprise)**
@@ -100,22 +100,26 @@ keep frames smooth.
100
100
 
101
101
  ### React, Vue, Angular & vanilla
102
102
 
103
- bo-grid also ships a framework-agnostic **custom element**. Import it and drive
104
- the whole API through a `config` property:
103
+ bo-grid also ships a framework-agnostic **custom element**, fully typed. Import it
104
+ and drive the whole API through a `config` property:
105
105
 
106
- ```js
107
- import 'bo-grid/element'; // registers <bo-grid>, injects styles
106
+ ```ts
107
+ import { createBoGrid } from 'bo-grid/element'; // registers <bo-grid>, injects styles
108
+ import type { BoGridConfig } from 'bo-grid/element';
108
109
 
109
- const el = document.querySelector('bo-grid');
110
- el.config = { columns, rows, theme: 'dark', height: 520 };
110
+ const config: BoGridConfig = { columns, rows, theme: 'dark', height: 520 };
111
+ document.body.append(createBoGrid(config)); // or set `el.config` on an existing element
111
112
  ```
112
113
 
113
- It works in React, Vue, Angular and plain HTML — see
114
+ `bo-grid/element` exports `BoGridConfig` (every `<Grid>` prop), a typed
115
+ `BoGridElement`, and the `createBoGrid` helper. `config` is safe to set **after**
116
+ the element attaches (the React `ref` + `useEffect` pattern won't crash). It works
117
+ in React, Vue, Angular and plain HTML — see
114
118
  **[docs/frameworks.md](./docs/frameworks.md)** for per-framework recipes and
115
- **[examples/](./examples/)** for runnable, build-free starters. (Custom
116
- `cell`/`detail` snippets are Svelte-only; use built-in types, `format`, or computed
117
- `value` from other frameworks. Native Svelte users should import `Grid` directly —
118
- smaller, and snippets work.)
119
+ **[examples/](./examples/)** for runnable, build-free starters. (Svelte `cell`/
120
+ `detail` snippets are Svelte-only; from other frameworks use a column's
121
+ `render(ctx)` hook, built-in types, or `format`. Native Svelte users should import
122
+ `Grid` directly — smaller, and snippets work.)
119
123
 
120
124
  ## Column types
121
125
 
@@ -160,6 +164,52 @@ buttons, links. The snippet receives `{ row, column, value }`:
160
164
  <Grid {rows} {columns} {cell} height={640} />
161
165
  ```
162
166
 
167
+ **From plain JS (React/Vue/vanilla, `<bo-grid>`):** snippets are Svelte-only, so
168
+ give a column a **`render(ctx)`** function instead. Return an `HTMLElement`/`Node`
169
+ (appended as-is — safe) or an HTML **string** (inserted as innerHTML — sanitize
170
+ any untrusted data yourself; prefer a Node for that). `ctx` is `{ value, row,
171
+ column }`. Display only — sort, filter, tooltip, copy and export still use the
172
+ value / `format`:
173
+
174
+ ```js
175
+ { type: 'custom', key: 'trend', header: 'Trend', render: ({ row }) => {
176
+ const el = document.createElement('span');
177
+ el.textContent = row.changePct > 0 ? '▲' : '▼';
178
+ el.style.color = row.changePct > 0 ? 'var(--bo-grid-up)' : 'var(--bo-grid-down)';
179
+ return el;
180
+ } }
181
+ ```
182
+
183
+ ### Tooltips & truncation
184
+
185
+ Long cell values truncate with an **ellipsis** by default. Set `tooltip` on a
186
+ column to reveal the full text in a **styled floating tooltip** on hover (themed,
187
+ instant — not the native `title`). `tooltip: true` shows the formatted value;
188
+ pass a function for custom text built from the whole row (works for any type,
189
+ including `custom`):
190
+
191
+ ```ts
192
+ const columns: ColumnDef[] = [
193
+ { type: 'text', key: 'note', header: 'Notes', width: 220, tooltip: true },
194
+ { type: 'badge', key: 'status', header: 'Status',
195
+ tooltip: (value, row) => `${value} · ${row.role}` }, // custom text
196
+ { type: 'text', key: 'bio', header: 'Bio', flex: 1, wrap: true }, // wrap, no ellipsis
197
+ ];
198
+ ```
199
+
200
+ Set `wrap: true` to let a column wrap onto multiple lines instead of truncating
201
+ (pair with a taller `rowHeight`).
202
+
203
+ **Header tooltips:** `headerTooltip` shows the same styled bubble on the column
204
+ header (describe the metric, units, caveats); add `headerInfo: true` for a small
205
+ ⓘ cue:
206
+
207
+ ```ts
208
+ { type: 'progress', key: 'workload', header: 'Workload', min: 0, max: 100,
209
+ headerTooltip: 'Share of capacity allocated this sprint (0–100%).',
210
+ headerInfo: true }
211
+ ```
212
+
163
213
  ## Conditional formatting
164
214
 
165
215
  Paint analytics cues straight into numeric cells — no custom snippet needed.
@@ -261,13 +311,28 @@ a function for variable per-row heights (in-memory mode):
261
311
  Variable heights use a prefix-sum + binary-search virtualizer, so scrolling stays
262
312
  O(log n). Source mode is uniform-only (unloaded row heights aren't known).
263
313
 
314
+ ### Sizing the grid
315
+
316
+ `height` as a **number** is the scroll viewport's pixel height (the element is
317
+ that plus header/pager/footer chrome). Pass a **CSS string** to size the whole
318
+ element instead and let the viewport auto-fit the space left over — ideal for
319
+ filling a flex/grid cell:
320
+
321
+ ```svelte
322
+ <Grid {rows} {columns} height={640} /> <!-- 640px viewport -->
323
+ <Grid {rows} {columns} height="100%" /> <!-- fill a sized parent -->
324
+ <Grid {rows} {columns} height="80vh" />
325
+ ```
326
+
264
327
  ## Pagination
265
328
 
266
329
  Prefer pages over one long scroll? Set `pageSize` (> 0) for a paged view with a
267
- first/prev/next/last pager; rows still virtualize within each page. In-memory mode.
330
+ first/prev/next/last pager; rows still virtualize within each page. Add
331
+ `pageSizeOptions` for a **rows-per-page dropdown** in the pager (observe changes
332
+ via `onPageSizeChange`). In-memory mode.
268
333
 
269
334
  ```svelte
270
- <Grid {rows} {columns} height={640} pageSize={25} />
335
+ <Grid {rows} {columns} height={640} pageSize={25} pageSizeOptions={[25, 50, 100]} />
271
336
  ```
272
337
 
273
338
  ## Sort & filter
@@ -330,6 +395,22 @@ value(s) across the extended range (editable columns; multi-cell selections tile
330
395
  Edits, paste and fill are **undoable** with <kbd>Ctrl/⌘</kbd>+<kbd>Z</kbd> (redo
331
396
  with <kbd>Ctrl/⌘</kbd>+<kbd>Y</kbd>). A paste or fill undoes as a single step.
332
397
 
398
+ For a read-only / display grid, set `cellSelection={false}` to drop the blue
399
+ range-selection highlight (and fill handle) entirely — clicks pass straight
400
+ through to `onRowClick` / `onCellClick`:
401
+
402
+ ```svelte
403
+ <Grid {rows} {columns} height={640} cellSelection={false} onRowClick={open} />
404
+ ```
405
+
406
+ For a **list-detail / master-detail** layout, drive `selectedRowId` (keyed by
407
+ `getRowId`) to highlight the active row — independent of the checkbox selection:
408
+
409
+ ```svelte
410
+ <Grid {rows} {columns} height={640}
411
+ selectedRowId={active?.id} onRowClick={(r) => (active = r)} />
412
+ ```
413
+
333
414
  When more than one cell is selected, a footer bar shows live **Sum / Avg / Count /
334
415
  Min / Max** over the numeric cells in the range — and it keeps updating as a
335
416
  realtime feed ticks. Choose which stats to show:
@@ -1,4 +1,4 @@
1
- import { p as ce, a as xe, v as me, w as _e, n as x, x as _, s as p, y as L, t as N, z as he, j as ke, f as C, h as w, i as u, m as ge, c as h, A as D, e as F, g as t, B as U, k as S, d as ye, l as f, C as we, o as k, D as Ae, u as Oe, q as Se, r as d, b as A } from "./bo-grid.element-DPnHUXMa.js";
1
+ import { p as ce, a as xe, v as me, w as _e, n as x, x as _, s as p, y as L, t as N, z as he, j as ke, f as C, h as w, i as u, m as ge, c as h, A as D, e as F, g as t, B as U, k as S, d as ye, l as f, C as we, o as k, D as Ae, u as Oe, q as Se, r as d, b as A } from "./bo-grid.element-BZGnfKB_.js";
2
2
  var G = k("<option> </option>"), Ne = k('<input class="bo-fm-in svelte-f7i4av" type="number" placeholder="and" aria-label="Upper value"/>'), Ce = k('<select class="bo-fm-op svelte-f7i4av" aria-label="Operator"></select> <input class="bo-fm-in svelte-f7i4av" type="number" placeholder="value" aria-label="Value"/> <!>', 1), Ee = k('<input class="bo-fm-in svelte-f7i4av" type="date" aria-label="End date"/>'), qe = k('<select class="bo-fm-op svelte-f7i4av" aria-label="Operator"></select> <input class="bo-fm-in svelte-f7i4av" type="date" aria-label="Date"/> <!>', 1), Be = k('<label class="bo-fm-opt svelte-f7i4av"><input type="checkbox"/> <span class="svelte-f7i4av"> </span></label>'), De = k('<input class="bo-fm-in svelte-f7i4av" type="search" placeholder="search…" aria-label="Search values"/> <div class="bo-fm-setbar svelte-f7i4av"><button type="button" class="bo-fm-link svelte-f7i4av">All</button> <button type="button" class="bo-fm-link svelte-f7i4av">None</button></div> <div class="bo-fm-list svelte-f7i4av"></div>', 1), Fe = k('<select class="bo-fm-op svelte-f7i4av" aria-label="Operator"></select> <input class="bo-fm-in svelte-f7i4av" type="text" placeholder="filter…" aria-label="Value"/>', 1), ze = k('<div class="bo-filtermenu svelte-f7i4av" role="dialog" tabindex="-1"><div class="bo-fm-head svelte-f7i4av"> </div> <!> <div class="bo-fm-actions svelte-f7i4av"><button class="bo-fm-btn svelte-f7i4av" type="button">Clear</button> <button class="bo-fm-btn bo-fm-apply svelte-f7i4av" type="button">Apply</button></div></div>');
3
3
  const Pe = {
4
4
  hash: "svelte-f7i4av",
@@ -1,4 +1,4 @@
1
- import { p as z, a as S, c as d, s as r, r as l, b as f, e as j, t as g, g as s, d as L, f as P, h as i, i as w, j as T, k as q, l as A, m as E, n as B, u as D, o as k, q as F } from "./bo-grid.element-DPnHUXMa.js";
1
+ import { p as z, a as S, c as d, s as r, r as l, b as f, e as j, t as g, g as s, d as L, f as P, h as i, i as w, j as T, k as q, l as A, m as E, n as B, u as D, o as k, q as F } from "./bo-grid.element-BZGnfKB_.js";
2
2
  var G = k('<label class="bo-tp-opt svelte-7xdno"><input type="checkbox"/> <span class="svelte-7xdno"> </span></label>'), H = k('<div class="bo-toolpanel svelte-7xdno" role="dialog" tabindex="-1" aria-label="Columns"><div class="bo-tp-head svelte-7xdno"><span>Columns</span> <button type="button" class="bo-tp-link svelte-7xdno">Show all</button></div> <input class="bo-tp-search svelte-7xdno" type="search" placeholder="search…" aria-label="Search columns"/> <div class="bo-tp-list svelte-7xdno"></div></div>');
3
3
  const I = {
4
4
  hash: "svelte-7xdno",