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.
Files changed (51) hide show
  1. package/README.md +254 -27
  2. package/dist/bo-grid.FilterMenu-BHI6rILc.js +154 -0
  3. package/dist/bo-grid.ToolPanel-C3u-4YKc.js +34 -0
  4. package/dist/bo-grid.element-DPnHUXMa.js +6623 -0
  5. package/dist/bo-grid.element.js +4 -0
  6. package/dist/charts/BarChart.svelte +50 -0
  7. package/dist/charts/BarChart.svelte.d.ts +16 -0
  8. package/dist/charts/DonutChart.svelte +54 -0
  9. package/dist/charts/DonutChart.svelte.d.ts +18 -0
  10. package/dist/charts/Legend.svelte +47 -0
  11. package/dist/charts/Legend.svelte.d.ts +12 -0
  12. package/dist/charts/LineChart.svelte +59 -0
  13. package/dist/charts/LineChart.svelte.d.ts +14 -0
  14. package/dist/charts/StackedBarChart.svelte +56 -0
  15. package/dist/charts/StackedBarChart.svelte.d.ts +18 -0
  16. package/dist/charts/chart-math.d.ts +57 -0
  17. package/dist/charts/chart-math.js +174 -0
  18. package/dist/charts/index.d.ts +8 -0
  19. package/dist/charts/index.js +11 -0
  20. package/dist/charts/palette.d.ts +4 -0
  21. package/dist/charts/palette.js +14 -0
  22. package/dist/format/format.d.ts +6 -0
  23. package/dist/format/format.js +41 -0
  24. package/dist/grid/Cell.svelte +247 -8
  25. package/dist/grid/Cell.svelte.d.ts +6 -0
  26. package/dist/grid/FilterMenu.svelte +7 -0
  27. package/dist/grid/Grid.svelte +307 -85
  28. package/dist/grid/Grid.svelte.d.ts +19 -0
  29. package/dist/grid/GroupRow.svelte +5 -2
  30. package/dist/grid/Pager.svelte +4 -0
  31. package/dist/grid/RowMenu.svelte +65 -2
  32. package/dist/grid/ToolPanel.svelte +5 -0
  33. package/dist/grid/column.d.ts +133 -0
  34. package/dist/grid/column.js +133 -4
  35. package/dist/grid/colvirt.d.ts +15 -0
  36. package/dist/grid/colvirt.js +43 -0
  37. package/dist/grid/export.d.ts +33 -0
  38. package/dist/grid/export.js +158 -2
  39. package/dist/grid/filtering.d.ts +5 -2
  40. package/dist/grid/filtering.js +5 -4
  41. package/dist/grid/grouping.d.ts +30 -0
  42. package/dist/grid/grouping.js +33 -0
  43. package/dist/grid/print.d.ts +17 -0
  44. package/dist/grid/print.js +55 -0
  45. package/dist/grid/theme.d.ts +15 -0
  46. package/dist/grid/theme.js +78 -0
  47. package/dist/grid/tree.d.ts +19 -7
  48. package/dist/grid/tree.js +16 -11
  49. package/dist/index.d.ts +7 -5
  50. package/dist/index.js +6 -4
  51. package/package.json +13 -2
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 in a package that gzips to ~20 KB.
5
- A free alternative to the heavyweight grids that paywall these features.
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 small gallery of nine grid types — a realtime **Trading desk**, a
13
- grouped **Portfolio** with subtotals and pivot, a general-purpose editable
14
- **Spreadsheet**, a live **Order book** depth ladder, a **Correlation** heatmap
15
- matrix, a **Leaderboard** with rank medals and score bars, a **Tree** file
16
- explorer, a drag-to-reorder **Tasks** list, and a **1M-row** trade tape windowed
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 | **~20 KB gzip** ([benchmarks](./BENCHMARKS.md)) |
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 AG Grid's **paid (Enterprise)** features — grouping, pivot,
44
- tree data, master-detail, range selection, Excel export, sparklines — for free.
45
- See the honest **[bo-grid vs AG Grid](./docs/vs-ag-grid.md)** comparison (including
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, and layout persistence.
58
+ server-side / lazy loading, realtime feeds, import helpers, charts, printing, and
59
+ layout persistence.
60
60
 
61
61
  ## Usage
62
62
 
@@ -98,9 +98,52 @@ Only on-screen rows render DOM, so off-screen updates cost nothing until they
98
98
  scroll into view. Batch bursty feeds into a `requestAnimationFrame` flush to
99
99
  keep frames smooth.
100
100
 
101
+ ### React, Vue, Angular & vanilla
102
+
103
+ bo-grid also ships a framework-agnostic **custom element**. Import it and drive
104
+ the whole API through a `config` property:
105
+
106
+ ```js
107
+ import 'bo-grid/element'; // registers <bo-grid>, injects styles
108
+
109
+ const el = document.querySelector('bo-grid');
110
+ el.config = { columns, rows, theme: 'dark', height: 520 };
111
+ ```
112
+
113
+ It works in React, Vue, Angular and plain HTML — see
114
+ **[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
+
101
120
  ## Column types
102
121
 
103
- `text` · `price` · `percent` · `volume` · `number` · `date` · `heatmap` · `sparkline` · `custom`
122
+ **Data:** `text` · `price` · `percent` · `volume` · `number` · `date` · `currency` ·
123
+ `relative` · `heatmap` · `sparkline`
124
+ **Rich:** `progress` · `rating` · `tags` · `badge` · `boolean` · `avatar` · `link`
125
+ **Escape hatch:** `custom`
126
+
127
+ Rich types render value as a widget — handy well beyond fintech (CRM, projects,
128
+ admin, content). All are themed from the design tokens:
129
+
130
+ ```ts
131
+ const columns: ColumnDef[] = [
132
+ { type: 'avatar', key: 'name', header: 'Member', sub: 'role' },
133
+ { type: 'badge', key: 'status', header: 'Status',
134
+ tones: { Active: 'up', Away: 'amber', Offline: 'neutral' } },
135
+ { type: 'progress', key: 'done', header: 'Progress', min: 0, max: 100 },
136
+ { type: 'rating', key: 'score', header: 'Rating', max: 5 },
137
+ { type: 'tags', key: 'skills', header: 'Skills' }, // value: string[]
138
+ { type: 'boolean', key: 'remote', header: 'Remote', trueLabel: 'Remote', falseLabel: 'Office' },
139
+ { type: 'link', key: 'email', header: 'Email', href: (r) => `mailto:${r.email}` },
140
+ { type: 'relative', key: 'seen', header: 'Last seen' }, // value: epoch ms → "3h ago"
141
+ { type: 'currency', key: 'rate', header: 'Rate', currency: 'USD' },
142
+ ];
143
+ ```
144
+
145
+ `link` sanitizes its href (`javascript:`/`data:` are blocked); `relative` formats
146
+ an epoch-ms value as relative time; `currency` localizes via `Intl.NumberFormat`.
104
147
 
105
148
  Sizing: `width` (px) or `flex` (grow weight). See `ColumnDef` for per-type options.
106
149
 
@@ -117,6 +160,94 @@ buttons, links. The snippet receives `{ row, column, value }`:
117
160
  <Grid {rows} {columns} {cell} height={640} />
118
161
  ```
119
162
 
163
+ ## Conditional formatting
164
+
165
+ Paint analytics cues straight into numeric cells — no custom snippet needed.
166
+
167
+ **Data bars** (`dataBar`): an in-cell bar behind the value, scaled across the
168
+ column's range. The range auto-computes over the current view, or set `min`/`max`
169
+ (`min: 0` gives absolute proportional bars). When the range spans negatives, bars
170
+ diverge left/right around a zero baseline. `color`/`negative` override the default
171
+ up/down theme colours.
172
+
173
+ **Icon sets** (`icons`): an icon beside the value, chosen by the highest threshold
174
+ `at` that is ≤ the value. Each rule carries a semantic `tone` (`up` · `down` ·
175
+ `amber` · `info` · `neutral`) for its colour.
176
+
177
+ **Colour scales** (`colorScale`): tint the cell background across the value range —
178
+ a soft, themed heat ramp. Auto-ranges over the view (or set `min`/`max`); pass
179
+ `mid` for a 3-stop diverging scale; `colors` overrides the stops. Works on any
180
+ numeric column (the fixed `heatmap` type still exists for an absolute ramp).
181
+
182
+ ```ts
183
+ const columns: ColumnDef[] = [
184
+ // Proportional bar from zero:
185
+ { type: 'volume', key: 'marketValue', header: 'Mkt Value', dataBar: { min: 0 } },
186
+ // Diverging bar (auto-ranged) + an icon keyed by sign:
187
+ { type: 'number', key: 'pnl', header: 'P&L', decimals: 0,
188
+ dataBar: {},
189
+ icons: [
190
+ { at: -Infinity, icon: '▼', tone: 'down' },
191
+ { at: 0, icon: '▲', tone: 'up' },
192
+ ] },
193
+ // Diverging colour scale around zero (auto-ranged):
194
+ { type: 'number', key: 'pnlPct', header: 'P&L %', decimals: 1, colorScale: { mid: 0 } },
195
+ ];
196
+ ```
197
+
198
+ All three compose with flashing/live cells and add nothing to the core for grids
199
+ that don't use them. (In-memory auto-ranging; pass explicit `min`/`max` in source
200
+ mode.)
201
+
202
+ ## Computed columns
203
+
204
+ Derive a column's value from the whole row with `value: (row) => …` — KPIs,
205
+ ratios, deltas. The derived value flows through display, sort, filter, group/footer
206
+ aggregation, conditional formatting, export and copy, just like a real column.
207
+ `key` still names the column (and is the sort/filter key) but need not be a real
208
+ field. Computed columns aren't editable (there's no field to write back).
209
+
210
+ ```ts
211
+ const columns: ColumnDef[] = [
212
+ { type: 'number', key: 'qty', header: 'Qty' },
213
+ { type: 'price', key: 'price', header: 'Price' },
214
+ // No `total` field on the row — derived, and still sortable/filterable/exportable:
215
+ { type: 'price', key: 'total', header: 'Total', value: (row) => row.qty * row.price,
216
+ groupAgg: 'sum' },
217
+ ];
218
+ ```
219
+
220
+ In-memory mode (a server `source` owns its own derivations). Keep `value()` cheap
221
+ and pure — it's called during sort and filter.
222
+
223
+ ## Charts (companion)
224
+
225
+ For dashboards, `bo-grid/charts` ships tiny, dependency-free SVG charts —
226
+ `LineChart`, `BarChart`, `DonutChart`, `StackedBarChart` (stacked or `grouped`
227
+ multi-series), and a `Legend`. They're a **separate import**, so they add nothing
228
+ to the grid core (~3 KB gzip on their own). Use them standalone, or inside a grid
229
+ cell via a `custom` column. Bar/stacked/donut elements carry an SVG `<title>`, so
230
+ hovering shows the value (accessible, zero-JS).
231
+
232
+ ```svelte
233
+ <script>
234
+ import { LineChart, BarChart, DonutChart, StackedBarChart, Legend } from 'bo-grid/charts';
235
+ </script>
236
+
237
+ <LineChart data={[3, 5, 4, 8, 6, 9]} width={160} height={40} area />
238
+ <BarChart data={[4, 8, 6, 9, 7]} color="var(--up)" />
239
+ <DonutChart data={[{ value: 5, label: 'A' }, { value: 3, label: 'B' }]} />
240
+
241
+ <!-- data[series][category]; stacked by default, `grouped` for side-by-side -->
242
+ <StackedBarChart data={[[3, 5, 2], [4, 1, 6]]} seriesLabels={['Q1', 'Q2']} />
243
+ <Legend items={[{ label: 'Q1' }, { label: 'Q2' }]} />
244
+ ```
245
+
246
+ Theme them with `color` / `colors` props, or by setting `--boc-color` and
247
+ `--boc-1`…`--boc-6` CSS vars on any ancestor. The geometry helpers (`linePoints`,
248
+ `barRects`, `donutArcs`, …) are exported too, for rolling your own SVG charts. See
249
+ the **Dashboard** example for charts inside grid cells.
250
+
120
251
  ## Row height
121
252
 
122
253
  Uniform 36px by default. Pass `rowHeight` as a number for a different density, or
@@ -269,6 +400,26 @@ Group headers are the same height as data rows, so virtual scrolling stays smoot
269
400
  over very large grouped sets. Subtotals recompute live as the feed ticks, and the
270
401
  current group's header stays pinned to the top as you scroll within it.
271
402
 
403
+ ### Server-side grouping
404
+
405
+ When the data is grouped on the server (or too large to fetch upfront), pass
406
+ **`lazyGroups`** (the group summaries) and **`loadGroup`** (load a group's rows on
407
+ expand). Headers show the server-provided count and preformatted aggregates; rows
408
+ load lazily with a loading row, then cache. See the **Server groups** example.
409
+
410
+ ```svelte
411
+ <Grid
412
+ rows={[]}
413
+ {columns}
414
+ lazyGroups={[
415
+ { key: 'North America', count: 142, agg: { amount: '$2.4M' } },
416
+ { key: 'Europe', count: 98, agg: { amount: '$1.8M' } },
417
+ ]}
418
+ loadGroup={(key) => fetch(`/api/orders?group=${key}`).then((r) => r.json())}
419
+ height={520}
420
+ />
421
+ ```
422
+
272
423
  ## Theming
273
424
 
274
425
  Dark-first and self-contained — no CSS import required. Use the `theme` prop with
@@ -279,9 +430,20 @@ a built-in preset or a custom token map:
279
430
  <Grid {rows} {columns} theme={{ bg: '#0b1020', up: '#22d3ee' }} height={640} />
280
431
  ```
281
432
 
282
- Built-in `darkTheme` / `lightTheme` are exported (`GridTheme`). Or set any
283
- `--bo-grid-*` custom property on an ancestor the prop is just a convenience over
284
- these:
433
+ Six built-in presets are exported (`GridTheme`): `darkTheme`, `lightTheme`,
434
+ `highContrastDark`, `highContrastLight`, `midnightTheme`, `terminalTheme`plus a
435
+ `themePresets` name→preset map (and a `ThemePreset` type) for a theme picker:
436
+
437
+ ```svelte
438
+ <script>
439
+ import { Grid, themePresets, type ThemePreset } from 'bo-grid';
440
+ let preset: ThemePreset = 'midnight';
441
+ </script>
442
+ <Grid {rows} {columns} theme={themePresets[preset]} height={640} />
443
+ ```
444
+
445
+ Or set any `--bo-grid-*` custom property on an ancestor — the prop is just a
446
+ convenience over these:
285
447
 
286
448
  ```css
287
449
  .my-app {
@@ -344,6 +506,18 @@ render as skeletons.
344
506
  the same interface (handy for testing the path or client-side data). Grouping is
345
507
  client-only, so it's not applied in source mode.
346
508
 
509
+ ### Wide grids (column virtualization)
510
+
511
+ Rows are virtualized vertically by default. For very wide grids (100+ columns),
512
+ add **`virtualizeColumns`** to also virtualize horizontally — only the columns in
513
+ the scroll window (+ overscan) render, so a 60-column grid costs about the same as
514
+ a handful. It switches the grid to fixed-width horizontal scroll; **pinned columns
515
+ always render**. See the **Wide** example.
516
+
517
+ ```svelte
518
+ <Grid {rows} {columns} virtualizeColumns height={520} />
519
+ ```
520
+
347
521
  ## Column reorder
348
522
 
349
523
  Pass `onRowReorder(from, to)` to enable **drag-to-reorder rows** via a handle in
@@ -418,6 +592,23 @@ In tree mode the grid renders the tree directly (filter/sort/group/paginate are
418
592
  not applied to it). Nodes are keyboard-accessible: **→** expands a collapsed node,
419
593
  **←** collapses an expanded one, and rows expose `aria-level` / `aria-expanded`.
420
594
 
595
+ ### Lazy (server-backed) trees
596
+
597
+ For hierarchies too large to ship upfront, use **`loadChildren`** (async) instead
598
+ of `getChildren`. Children load on first expand — the grid shows a loading row,
599
+ then caches them. Pair it with **`hasChildren`** (a cheap predicate) so the chevron
600
+ shows without loading. See the **Lazy tree** example.
601
+
602
+ ```svelte
603
+ <Grid
604
+ {rows}
605
+ {columns}
606
+ height={520}
607
+ hasChildren={(r) => r.kind === 'folder'}
608
+ loadChildren={(r) => fetch(`/api/children/${r.id}`).then((res) => res.json())}
609
+ />
610
+ ```
611
+
421
612
  ## Master-detail
422
613
 
423
614
  Pass a `detail` snippet to render an expandable panel under each row — the grid
@@ -520,17 +711,38 @@ const columns = [
520
711
  ];
521
712
  ```
522
713
 
523
- ## Export
714
+ ## Export & import
524
715
 
525
- CSV export is dependency-free:
716
+ CSV export and import — are dependency-free:
526
717
 
527
718
  ```ts
528
- import { exportCSV, toCSV } from 'bo-grid';
719
+ import { exportCSV, toCSV, parseCSV } from 'bo-grid';
529
720
 
530
721
  exportCSV('tickers.csv', rows, columns); // triggers a download
531
722
  const text = toCSV(rows, columns, { formatted: true }); // or get the string
723
+ const rows = parseCSV(text, columns); // …and back to rows (round-trip)
724
+ ```
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());
532
735
  ```
533
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
+
534
746
  Excel export loads SheetJS via **dynamic import**, so it lands in its own lazy
535
747
  chunk and never bloats your core bundle. `xlsx` is an **optional peer dependency**
536
748
  — install it only if you use this:
@@ -544,11 +756,23 @@ Sparkline columns are skipped; numeric columns export as raw numbers so
544
756
  spreadsheets can compute on them (pass `{ formatted: true }` for display strings).
545
757
  Ctrl/⌘+C still copies the current selection as TSV.
546
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
+
547
769
  ## Also exported
548
770
 
549
771
  `Sparkline` component · `drawCandles` / `setupHiDpiCanvas` (draw on your own
550
772
  canvas) · `fmtPrice` / `fmtPercent` / `fmtVolume` / `fmtDate` · `heatColor` ·
551
- `Selection` · `aggregate` · `toCSV` / `exportCSV` / `exportXLSX` / `rowsToMatrix`.
773
+ `Selection` · `aggregate` · `toCSV` / `exportCSV` / `exportXLSX` / `rowsToMatrix` ·
774
+ `parseCSV` / `parseCSVMatrix` / `parseTSV` / `parseJSON` / `parseRows` /
775
+ `rowsFromObjects` · `toHTMLTable` / `printTable`.
552
776
 
553
777
  ## Pivot tables
554
778
 
@@ -585,11 +809,14 @@ the real dimensions and positions so assistive tech isn't misled:
585
809
  - `aria-activedescendant` tracks the focused cell for screen readers.
586
810
  - Sparkline cells carry a text `aria-label`; sticky/skeleton duplicates are
587
811
  `aria-hidden`; the aggregation bar is an `aria-live` status region.
588
- - Fully keyboard-operable (see [Selection & keyboard](#selection--keyboard) and
589
- inline editing) and respects `prefers-reduced-motion`.
590
-
591
- A formal WCAG 2.1 AA audit is on the roadmap; the above is a deliberate pass, not
592
- a certification.
812
+ - Fully keyboard-operable: one tab stop with arrow-key navigation, APG-pattern
813
+ menus (focus moves in, arrow-navigable, returns focus on close), a keyboard path
814
+ to filtering (column menu → **Filter…**), and visible `:focus-visible` rings on
815
+ every reachable control. Respects `prefers-reduced-motion`.
816
+
817
+ bo-grid targets **WCAG 2.1 AA**. See **[ACCESSIBILITY.md](./ACCESSIBILITY.md)** for
818
+ the full keyboard map, roles, measured contrast ratios, and conformance notes from
819
+ the audit.
593
820
 
594
821
  ## Develop
595
822
 
@@ -0,0 +1,154 @@
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";
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
+ const Pe = {
4
+ hash: "svelte-f7i4av",
5
+ code: `.bo-filtermenu.svelte-f7i4av {position:fixed;z-index:30;display:flex;flex-direction:column;gap:6px;width:200px;padding:10px;background:var(--bo-header-bg);border:0.5px solid var(--bo-border);border-radius:8px;box-shadow:0 10px 30px rgba(0, 0, 0, 0.35);font-size:12px;color:var(--bo-text);}.bo-fm-head.svelte-f7i4av {font-weight:600;color:var(--bo-text-dim);padding-bottom:2px;}.bo-fm-op.svelte-f7i4av,
6
+ .bo-fm-in.svelte-f7i4av {width:100%;padding:5px 7px;font:inherit;color:var(--bo-text);background:var(--bo-bg);border:0.5px solid var(--bo-border);border-radius:5px;}.bo-fm-setbar.svelte-f7i4av {display:flex;gap:12px;}.bo-fm-link.svelte-f7i4av {padding:0;font:inherit;font-size:11px;color:var(--bo-up);background:none;border:0;cursor:pointer;}.bo-fm-link.svelte-f7i4av:hover {text-decoration:underline;}.bo-fm-list.svelte-f7i4av {display:flex;flex-direction:column;max-height:180px;overflow-y:auto;border:0.5px solid var(--bo-border);border-radius:5px;}.bo-fm-opt.svelte-f7i4av {display:flex;align-items:center;gap:7px;padding:4px 7px;cursor:pointer;white-space:nowrap;}.bo-fm-opt.svelte-f7i4av:hover {background:var(--bo-row-hover);}.bo-fm-opt.svelte-f7i4av span:where(.svelte-f7i4av) {overflow:hidden;text-overflow:ellipsis;}.bo-fm-actions.svelte-f7i4av {display:flex;justify-content:flex-end;gap:6px;margin-top:2px;}.bo-fm-btn.svelte-f7i4av {padding:5px 12px;font:inherit;font-size:11px;color:var(--bo-text-dim);background:transparent;border:0.5px solid var(--bo-border);border-radius:5px;cursor:pointer;}.bo-fm-btn.svelte-f7i4av:hover {color:var(--bo-text);}
7
+ /* Visible keyboard focus (WCAG 2.4.7) for the menu's custom buttons. */.bo-fm-btn.svelte-f7i4av:focus-visible,
8
+ .bo-fm-link.svelte-f7i4av:focus-visible {outline:2px solid var(--bo-sel-border);outline-offset:1px;border-radius:5px;}.bo-fm-apply.svelte-f7i4av {color:#0a0a0a;background:var(--bo-up);border-color:var(--bo-up);}`
9
+ };
10
+ function Ve(K, n) {
11
+ ce(n, !0), xe(K, Pe);
12
+ let Q = me(n, "values", 19, () => []);
13
+ const $ = [
14
+ { op: "contains", label: "Contains" },
15
+ { op: "notContains", label: "Not contains" },
16
+ { op: "equals", label: "Equals" },
17
+ { op: "starts", label: "Starts with" },
18
+ { op: "ends", label: "Ends with" }
19
+ ], ee = [
20
+ { op: "eq", label: "=" },
21
+ { op: "ne", label: "≠" },
22
+ { op: "lt", label: "<" },
23
+ { op: "le", label: "≤" },
24
+ { op: "gt", label: ">" },
25
+ { op: "ge", label: "≥" },
26
+ { op: "between", label: "Between" }
27
+ ], ae = [
28
+ { op: "on", label: "On" },
29
+ { op: "before", label: "Before" },
30
+ { op: "after", label: "After" },
31
+ { op: "between", label: "Between" }
32
+ ], R = (a) => a ? Date.parse(`${a}T00:00:00Z`) : NaN, W = (a) => Number.isFinite(a) ? new Date(a).toISOString().slice(0, 10) : "", e = _e(() => n.filter);
33
+ let z = x(_((e == null ? void 0 : e.kind) === "text" ? e.op : "contains")), P = x(_((e == null ? void 0 : e.kind) === "text" ? e.q : "")), E = x(_((e == null ? void 0 : e.kind) === "number" ? e.op : "eq")), T = x(_((e == null ? void 0 : e.kind) === "number" && Number.isFinite(e.a) ? e.a : null)), V = x(_((e == null ? void 0 : e.kind) === "number" && e.b != null && Number.isFinite(e.b) ? e.b : null)), q = x(_((e == null ? void 0 : e.kind) === "date" ? e.op : "on")), M = x(_((e == null ? void 0 : e.kind) === "date" ? W(e.a) : "")), B = x(_((e == null ? void 0 : e.kind) === "date" && e.b != null ? W(e.b) : "")), O = x(_(new Set((e == null ? void 0 : e.kind) === "set" ? e.excluded : []))), j = x("");
34
+ const te = Oe(() => Q().filter((a) => a.toLowerCase().includes(t(j).trim().toLowerCase())));
35
+ function le(a) {
36
+ const s = new Set(t(O));
37
+ s.has(a) ? s.delete(a) : s.add(a), f(O, s, !0);
38
+ }
39
+ function oe() {
40
+ let a;
41
+ return n.kind === "number" ? a = {
42
+ kind: "number",
43
+ op: t(E),
44
+ a: t(T) ?? NaN,
45
+ b: t(V) ?? void 0
46
+ } : n.kind === "date" ? a = {
47
+ kind: "date",
48
+ op: t(q),
49
+ a: R(t(M)),
50
+ b: t(B) ? R(t(B)) : void 0
51
+ } : n.kind === "set" ? a = { kind: "set", excluded: [...t(O)] } : a = { kind: "text", op: t(z), q: t(P) }, Ae(a) ? a : null;
52
+ }
53
+ function X() {
54
+ n.onApply(oe());
55
+ }
56
+ function re() {
57
+ n.onApply(null);
58
+ }
59
+ function ie(a) {
60
+ a.key === "Enter" ? X() : a.key === "Escape" && n.onClose();
61
+ }
62
+ var g = ze(), I = h(g), ne = h(I, !0);
63
+ d(I);
64
+ var Z = p(I, 2);
65
+ {
66
+ var se = (a) => {
67
+ var s = Ce(), i = D(s);
68
+ F(i, 21, () => ee, (l) => l.op, (l, o) => {
69
+ var r = G(), y = h(r, !0);
70
+ d(r);
71
+ var c = {};
72
+ N(() => {
73
+ C(y, t(o).label), c !== (c = t(o).op) && (r.value = (r.__value = t(o).op) ?? "");
74
+ }), u(l, r);
75
+ }), d(i);
76
+ var v = p(i, 2);
77
+ A(v);
78
+ var b = p(v, 2);
79
+ {
80
+ var m = (l) => {
81
+ var o = Ne();
82
+ A(o), S(o, () => t(V), (r) => f(V, r)), u(l, o);
83
+ };
84
+ L(b, (l) => {
85
+ t(E) === "between" && l(m);
86
+ });
87
+ }
88
+ U(i, () => t(E), (l) => f(E, l)), S(v, () => t(T), (l) => f(T, l)), u(a, s);
89
+ }, ve = (a) => {
90
+ var s = qe(), i = D(s);
91
+ F(i, 21, () => ae, (l) => l.op, (l, o) => {
92
+ var r = G(), y = h(r, !0);
93
+ d(r);
94
+ var c = {};
95
+ N(() => {
96
+ C(y, t(o).label), c !== (c = t(o).op) && (r.value = (r.__value = t(o).op) ?? "");
97
+ }), u(l, r);
98
+ }), d(i);
99
+ var v = p(i, 2);
100
+ A(v);
101
+ var b = p(v, 2);
102
+ {
103
+ var m = (l) => {
104
+ var o = Ee();
105
+ A(o), S(o, () => t(B), (r) => f(B, r)), u(l, o);
106
+ };
107
+ L(b, (l) => {
108
+ t(q) === "between" && l(m);
109
+ });
110
+ }
111
+ U(i, () => t(q), (l) => f(q, l)), S(v, () => t(M), (l) => f(M, l)), u(a, s);
112
+ }, be = (a) => {
113
+ var s = De(), i = D(s);
114
+ A(i);
115
+ var v = p(i, 2), b = h(v), m = p(b, 2);
116
+ d(v);
117
+ var l = p(v, 2);
118
+ F(l, 20, () => t(te), (o) => o, (o, r) => {
119
+ var y = Be(), c = h(y);
120
+ A(c);
121
+ var Y = p(c, 2), fe = h(Y, !0);
122
+ d(Y), d(y), N(
123
+ (ue) => {
124
+ ye(c, ue), C(fe, r === "" ? "(blank)" : r);
125
+ },
126
+ [() => !t(O).has(r)]
127
+ ), w("change", c, () => le(r)), u(o, y);
128
+ }), d(l), S(i, () => t(j), (o) => f(j, o)), w("click", b, () => f(O, /* @__PURE__ */ new Set(), !0)), w("click", m, () => f(O, new Set(Q()), !0)), u(a, s);
129
+ }, de = (a) => {
130
+ var s = Fe(), i = D(s);
131
+ F(i, 21, () => $, (b) => b.op, (b, m) => {
132
+ var l = G(), o = h(l, !0);
133
+ d(l);
134
+ var r = {};
135
+ N(() => {
136
+ C(o, t(m).label), r !== (r = t(m).op) && (l.value = (l.__value = t(m).op) ?? "");
137
+ }), u(b, l);
138
+ }), d(i);
139
+ var v = p(i, 2);
140
+ A(v), we(v), U(i, () => t(z), (b) => f(z, b)), S(v, () => t(P), (b) => f(P, b)), u(a, s);
141
+ };
142
+ L(Z, (a) => {
143
+ n.kind === "number" ? a(se) : n.kind === "date" ? a(ve, 1) : n.kind === "set" ? a(be, 2) : a(de, -1);
144
+ });
145
+ }
146
+ var H = p(Z, 2), J = h(H), pe = p(J, 2);
147
+ d(H), d(g), N(() => {
148
+ he(g, "aria-label", `Filter ${n.header ?? ""}`), ke(g, `left:${n.x ?? ""}px;top:${n.y ?? ""}px;`), C(ne, n.header);
149
+ }), w("pointerdown", g, (a) => a.stopPropagation()), w("keydown", g, ie), w("click", J, re), w("click", pe, X), u(K, g), ge();
150
+ }
151
+ Se(["pointerdown", "keydown", "click", "change"]);
152
+ export {
153
+ Ve as default
154
+ };
@@ -0,0 +1,34 @@
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";
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
+ const I = {
4
+ hash: "svelte-7xdno",
5
+ code: ".bo-toolpanel.svelte-7xdno {position:fixed;z-index:30;display:flex;flex-direction:column;gap:7px;width:200px;padding:10px;background:var(--bo-header-bg);border:0.5px solid var(--bo-border);border-radius:8px;box-shadow:0 10px 30px rgba(0, 0, 0, 0.35);font-size:12px;color:var(--bo-text);}.bo-tp-head.svelte-7xdno {display:flex;align-items:baseline;justify-content:space-between;font-weight:600;color:var(--bo-text-dim);}.bo-tp-link.svelte-7xdno {padding:0;font:inherit;font-size:11px;color:var(--bo-up);background:none;border:0;cursor:pointer;}.bo-tp-link.svelte-7xdno:hover {text-decoration:underline;}.bo-tp-link.svelte-7xdno:focus-visible {outline:2px solid var(--bo-sel-border);outline-offset:1px;border-radius:4px;}.bo-tp-search.svelte-7xdno {width:100%;padding:5px 7px;font:inherit;color:var(--bo-text);background:var(--bo-bg);border:0.5px solid var(--bo-border);border-radius:5px;}.bo-tp-list.svelte-7xdno {display:flex;flex-direction:column;max-height:220px;overflow-y:auto;}.bo-tp-opt.svelte-7xdno {display:flex;align-items:center;gap:7px;padding:4px 4px;cursor:pointer;white-space:nowrap;}.bo-tp-opt.svelte-7xdno:hover {background:var(--bo-row-hover);}.bo-tp-opt.svelte-7xdno span:where(.svelte-7xdno) {overflow:hidden;text-overflow:ellipsis;}"
6
+ };
7
+ function K(c, o) {
8
+ z(o, !0), S(c, I);
9
+ let p = B("");
10
+ const y = D(() => o.columns.filter((e) => e.header.toLowerCase().includes(s(p).trim().toLowerCase())));
11
+ var t = H(), x = d(t), m = r(d(x), 2);
12
+ l(x);
13
+ var b = r(x, 2);
14
+ f(b);
15
+ var u = r(b, 2);
16
+ j(u, 21, () => s(y), (e) => e.key, (e, a) => {
17
+ var v = G(), n = d(v);
18
+ f(n);
19
+ var h = r(n, 2), _ = d(h, !0);
20
+ l(h), l(v), g(
21
+ (C) => {
22
+ L(n, C), P(_, s(a).header);
23
+ },
24
+ [() => !o.hidden.includes(s(a).key)]
25
+ ), i("change", n, () => o.onToggle(s(a).key)), w(e, v);
26
+ }), l(u), l(t), g(() => T(t, `left:${o.x ?? ""}px;top:${o.y ?? ""}px;`)), i("pointerdown", t, (e) => e.stopPropagation()), i("keydown", t, (e) => e.key === "Escape" && o.onClose()), i("click", m, function(...e) {
27
+ var a;
28
+ (a = o.onShowAll) == null || a.apply(this, e);
29
+ }), q(b, () => s(p), (e) => A(p, e)), w(c, t), E();
30
+ }
31
+ F(["pointerdown", "keydown", "click", "change"]);
32
+ export {
33
+ K as default
34
+ };