Repository navigation
Releases: casoon/opengrid
Release list
0.9.0
Screen-reader pairings tested: none yet (issue #5).
Browsers: Chromium (the whole e2e suite, 1053 passed) and WebKit (Playwright's, not Safari:
519 passed, 10 skipped). Firefox: not run.
The pivot grows: two column dimensions with a named header, and limits set by a measurement of
what a browser draws as a table in about a second.
What breaks:
- A pivot over the new budget of 131 072 cells is an error now, though its rows and columns are
each inside their limits — 2 000 rows × 256 columns was allowed before (E39). - Rust:
PivotLimitshas a new field,max_cells.
Module sizes (brotli, just measure-modules): the elements 224.0 → 224.8 KiB, the engine
118.8 KiB, both inside their bounds.
Fixed
- An answer no longer takes the focus away in
<opengrid-pivot>(#124).
Every answer redraws the pivot; a reader on a toolbar control, a sort or fold button, or
typing into the filter field lost the focus and the typed text. The focused control is found
again after a redraw, and the field keeps its text — until the expression is added.
Changed
- The pivot's limits follow a measurement (#122, E39).
A browser draws a native table in time proportional to its cells, whatever their shape
(just measure-pivot: 128 000 cells in 0.7 s in Chromium, 1.0 s in WebKit; 512 000 in 3.1 s
and 4.7 s). So a pivot now has a budget of 131 072 cells (rows × generated columns,
PivotLimits::max_cells, reported byGET /source), the row limit rises from 2 000 to
10 000 and the column limit stays 256. What breaks: a pivot wider than the budget —
2 000 rows × 256 columns was allowed — is an error with a sentence now. Rust:PivotLimitshas
a new field.
Added
- Two column dimensions in
<opengrid-pivot>(#120).
columns="ordered_year,status"gives a header row per dimension, the outer value spanning its
inner ones, then the measures. With two, the pivot is a complex table: every header cell has an
idand every value cellheadersnaming its row and column headers. The engine's limit
(PivotLimits::max_column_dimensions, reported byGET /source) is 2 now; three is an error
with a sentence. The toolbar offers "+ Column" until there are two;columnFullreads "Two
column fields already". Conformance cases with two column dimensions run on every path.
0.8.1
Screen-reader pairings tested: none yet (issue #5).
Browsers: Chromium, the whole e2e suite. A fix to the types; nothing breaks.
Fixed
get_viewno longer widens a grid's view (#117).
0.8.0 made it generic, and where its result flowed into a typed place TypeScript inferred
View | PivotView. It is two overloads now: without a type argument the grid'sView,
with one (get_view<PivotView>(pivot)) the pivot's.
0.8.0
Screen-reader pairings tested: none yet (issue #5).
Browsers: Chromium (the whole e2e suite, 1047 passed) and WebKit (Playwright's, not Safari:
516 passed, 10 skipped). Firefox: not run.
The pivot on the grid's level: titles and formats, a view and events, sorting, folding, choosing
the fields and filtering — each from the keyboard, each part of the view.
What breaks:
<opengrid-pivot>'s row-dimension headers — and its measure headers without a column
dimension — hold a sort button now, and a group's subtotal header a fold button; the direction
and fold marks arearia-hiddentext beside the names. A page that read a header's
textContentreads the mark too; read the button's firstspan, or what a reader hears.- Rust:
PivotQueryandValidatedPivotQueryhave new fields (sort;orders,sort), so a
struct literal needs them. The JSON form is unchanged for a pivot withoutsort.
Module sizes (brotli, just measure-modules): the elements 206.2 → 224.0 KiB, the engine
115.2 → 118.8 KiB; both bounds raised with their reasons in scripts/module-budget.txt.
Added
- Titles and formats in
<opengrid-pivot>(#104).
set_columnsgives a row dimension or a measure itstitle, andset_formatsformats
dimension values in the headers and measure cells, by field name or measure alias — as at the
grid and the table. NULL and the empty string keep their words in a header. Any option but
titleis reported in the status line.get_pivotstill exports raw names and values. - A view and events for
<opengrid-pivot>(#106).
get_view/set_viewread and write{ rows, columns, values }— the three attributes as one
value, set in one query — andopengrid-view-changereports every change of them.
opengrid-querynow fires after every pivot answer too.OpengridPivot'sview,
defaultViewandonViewChangeare typedPivotViewin React, Vue and Svelte;
get_view<PivotView>(pivot)in TypeScript.ViewChangeDetailandConnectiontake a type
parameter whose default is the grid's, so existing code compiles unchanged. - Sorting in
<opengrid-pivot>(#108).
Each level is ordered by its own values or by a measure over the whole row, ascending or
descending; subtotals stay after their group (new rule P9, with conformance cases on the local
engine, WebAssembly, PostgreSQL and the export). The pivot query takessort: [{ field, by?, direction }]— sent only when there is one — and the element asortattribute
that is part of its view. Row-dimension headers, and measure headers without a column
dimension, are sort buttons witharia-sort. On PostgreSQL a sorted pivot runs one query per
level instead of oneGROUPING SETSstatement. - Folding groups in
<opengrid-pivot>(#110).
With two row dimensions or more, a group's subtotal row header is a button with
aria-expandedthat folds the group's rows away and opens them again, without a new query.
The status line says it (groupCollapsed/groupExpanded); the folded groups are a
collapsedattribute, part of the view. New partsgroup-toggleandgroup-mark. - Choosing the fields of
<opengrid-pivot>(#112).
With thetoolbarattribute the reader chooses rows, the column and measures: per axis a
group of chips — move earlier/later, remove — and an add menu ("+ Row", "+ Column",
"+ Measure") offering what the page offers in the newfieldsandmeasuresattributes. No
dragging; the menu has the grid's keyboard. Each change is one view, one query. New parts
field-group,add-field,field-menu,chip-move, and twelve text keys (pivotToolbar,
addRow,fieldRemove, …). - Filtering
<opengrid-pivot>(#114).
Afilterattribute takes the wire's filter JSON and is part of the view; the filter acts on
the raw rows, before the pivot. Withtoolbarthe reader gets a "Filters" group: a field for an
expression in the grid's language (country = DE and qty ≥ 3) and a removable chip per clause,
plus "Remove all". The types come from a pivot the provider already answers (the pivot has no
schema of its own). Three new text keys:pivotFilters,pivotFilterLabel,pivotFilterHint.
0.7.2
Screen-reader pairings tested: none yet (issue #5).
Browsers: Chromium, the whole e2e suite. A fix to the toolbar's column list; nothing breaks.
Fixed
- "Columns" stays open while columns are ticked (#101).
Unticking one column closed the list and put the focus into the table header, because each
tick rebuilds the grid. The list is now a panel under its button, like "+ Filter": it stays
open across the rebuild with the focus on the ticked checkbox, and closes onEscape(focus
back to the button), a click outside or the button. It no longer opens inline and pushes the
toolbar aside.
npm: npm install @casoon/opengrid@0.7.2 · Live demos: https://og-vanilla.casoon.dev
0.7.1
Screen-reader pairings tested: none yet (issue #5).
Browsers: Chromium, the whole e2e suite. A fix to the filter row of 0.7.0; nothing breaks.
Fixed
- An unfiltered column keeps its default comparison after a view (#96). Applying a view
wrote the placeholderisinto every column it did not filter, so a text column showed
is instead of contains. It is back on its type's default now, as after a reset.
npm: npm install @casoon/opengrid@0.7.1 · Live demos: https://og-vanilla.casoon.dev
0.7.0
Screen-reader pairings tested: none yet — the VoiceOver pass is prepared and not yet run
(issue #5). Browsers: Chromium and
WebKit (Playwright's, not Safari), the whole e2e suite each. Firefox: not run: Playwright's
Firefox does not start on the release machine.
What breaks: the filter row's filter-operator is a <button> now, not a <select> (#96).
Module sizes (brotli, just measure-modules): the elements 204.1 → 206.2 KiB (the operator
menu), the engine unchanged at 115.2 KiB.
Changed
- The filter row is one field per column (#96).
It had two controls per column, a comparison<select>and a value field, and at ordinary
widths both were cut off. Now the value field takes the column's width and the comparison is
a small button inside it: it shows the comparison as a sign (∗,=,≥, …), names it in
words (customer operator: contains) and opens a menu (operator-menu) of what the type
allows, with the column menu's keys. Typing filters with the type's default — contains for
text, is for numbers and dates; a comparison picked while the field has a value filters at
once. A boolean keeps its any / yes / no choice, without a button.
What breaks:filter-operatoris a<button>now, not a<select>; a page script that
set itsvaluesets nothing. The new partoperator-menuis the menu.
npm: npm install @casoon/opengrid@0.7.0 · Live demos: https://og-vanilla.casoon.dev
0.6.0
Screen-reader pairings tested: none yet — the VoiceOver pass is prepared and not yet run
(issue #5). Browsers: Chromium, the whole
e2e suite. WebKit (Playwright's, not Safari): the whole suite; its one failure, the plain-DOM
stand-in counting a second table, is fixed in this release (#93). Firefox: not run:
Playwright's Firefox does not start on the release machine.
What breaks (details in the entries below):
- the pivot's
headerpart moved from its<thead>to each column header cell (#29); - in the Rust crates, the opengrid types no longer implement
serde— read and write them
withopengrid_json(#41). The npm package's JavaScript API is unchanged apart from
additions.
Module sizes (brotli, just measure-modules): the elements 215.8 → 204.1 KiB, the engine
124.9 → 115.2 KiB — serde left both modules (#41), and the engine now answers pivots (#28).
Added
- Data without JavaScript (#84). A page
can write its data as a plain<table>inside an element. Without script, that's what
readers and crawlers see; once the element has rendered, its shadow root replaces it, so
nothing is read twice. When the module can't load, the plain-DOM stand-in now shows the
page's table (through a<slot>) instead of hiding it behind an empty skeleton. The
skeleton only shows when there is no table of its own. - The pivot in the browser (#28).
createLocalProviderandcreateWorkerProvideranswer<opengrid-pivot>: the engine runs
the pivot's grouping sets over the data the page loaded, with the server's limits
(Engine.pivot,Engine.pivot_columns, apivotmessage in the worker). The element calls
a provider's new optionalpivot(json)and falls back toexecute, so providers of a page's
own keep working;createPivotProviderhaspivottoo.examples/pivot-demo/browser.html
shows it without a server. - XLSX export on the server (#72).
POST /export/{source}?format=xlsxwrites an Excel workbook, andexportRows(rest, query, { format: "xlsx" })asks for it throughcreateRestProvider— with any other provider it is
aTypeError. Values are Excel types where Excel holds them exactly (numbers, dates,
date-times in UTC, booleans) and text where it cannot (integers past 2⁵³, decimals past 15
digits, sub-millisecond timestamps), so no value changes; Excel's row and cell limits are
errors, never a shortened file. New server-side dependency:rust_xlsxwriter, behind the
featurexlsxofopengrid-export, so the browser modules do not carry it. <opengrid-table>and<opengrid-pivot>can be styled, and the table takes formats
(#29). Both render into a shadow root, so
no page rule reached their cells. They now carry parts named like the grid's —table,
caption,header,row,cell, the table'ssort-button, the pivot'srow-headerand
total-row— andexamples/pivot-demoandexamples/table-demostyle them through
::part. The table shows its values as the grid does:formatsapply to its cells, and
presentation(set_columns) gives a column itstitle,align,mono,emphasisand
muted; the grid'swidth,aggregateandfacetare refused for a table with an alert
rather than ignored. A number is right-aligned by its type, as in the grid.
What breaks: the pivot'sheaderpart was on its<thead>; it is now on each column
header cell, like the grid's.
Changed
-
One JSON codec for browser and server, and no
serdein the browser
(#41).opengrid-jsonreads and writes
every JSON form — query, schema, view, texts, errors, the JSON result — on both sides,
so the two cannot read the same bytes two ways.serdeandserde_jsonare gone from
both browser modules. Against 0.5.0, with everything else in this release (just measure-modules, brotli): the elements 215.8 → 204.1 KiB, the engine 124.9 → 115.2 KiB,
although the engine now answers pivots too. What is read and refused is whatserde_jsonread
and refused (checked against it as an oracle); what is written is what it wrote, keys
aside — an object now keeps the order it is written in instead of sorting its keys —
and one rare case: where a float has two shortest spellings, the one closer to the
value is written. Both read back to the same number.What breaks (Rust crates only; the npm package and its JavaScript API are
unchanged): the opengrid types no longer implementserde::Serialize/Deserialize.
Read and write them withopengrid_json(from_str,to_string,FromJson,ToJson);
Value::deserialize_typedisValue::from_json_typed, and a filter literal is an
opengrid_json::Json.
Fixed
<opengrid-table>says it is loading (#86).
Until its first answer it was a table with a caption and no rows, which a screen reader
couldn't tell from an empty one. The waiting table now carriesaria-busy="true", and
the table with its answer doesn't.
npm: npm install @casoon/opengrid@0.6.0 · Live demos: https://og-vanilla.casoon.dev
v0.1.0
First release of opengrid — npm install @casoon/opengrid: a portable Rust/WASM data and query engine with an accessible data grid, table and pivot as web components — and adapters for React, Vue and Svelte.
Not yet verified by a screen reader. Everything here is built and tested — keyboard, focus, announcements, axe-core in every state — but no screen reader has been run over it yet. The passes follow this release (#5).
One query model. A JSON AST — never SQL from a browser — whose semantics are pinned by 53 query and 5 pivot conformance cases: NULL ordering, binary collation, exact decimals, non-finite floats, microsecond timestamps.
Three ways to run it, proven to agree. In the tab over Apache Arrow compiled to WebAssembly; on a server against PostgreSQL as one compiled statement; or split between the two, with a planner deciding from what the source declares it can do. All three answer every conformance case identically.
Three elements. <opengrid-table> for display, <opengrid-grid> for work (keyboard navigation under the WAI-ARIA grid pattern, virtualization over 100,000 rows, filtering, multi-sort, selection, editing, movable/resizable/hideable columns, paging), and <opengrid-pivot>.
Configurable views. The schema from the source is the truth; set_columns narrows it and refuses, in words, what the type contradicts. The reader's view — sort, filters, columns, density, grouping, facets — is one value (get_view, set_view, opengrid-view-change), so saved views are a few lines of page code. Grouping by up to two columns stays virtualized and becomes a treegrid, with aggregates, ranges and a grand total; a search field takes free text or a filter written out; facets count without their own restriction; a toolbar shows the active filters as chips; a column menu is a second way to every column action. Eighteen --og-* custom properties theme it, with system colours as defaults.
The engine in the package. @casoon/opengrid ships the in-browser query engine under engine/ (335 KiB brotli, next to 194 KiB for the elements); createWorkerProvider() without options uses it.
Frameworks. connect(host, options) supplies an element from one object and keeps it supplied. On top of it: @casoon/opengrid-react (React 18 and 19), @casoon/opengrid-vue (Vue 3.3+) and @casoon/opengrid-svelte (Svelte 5), each rendered on the server and hydrated in tests; Angular through a documented directive.
Export. What the reader sees, every match of it, as CSV or JSON in raw values: get_query(host) gives the view's query, exportRows(provider, query, options) fetches it in pieces through any provider — or in one streamed request from POST /export/{source} on the server, under the same token, field allowlist and tenant filter as a query. CSV per RFC 4180 with a guard against formula injection; a pivot exports as shown with get_pivot(host). See the export guide.
A gateway. opengrid-server with bearer tokens, a mandatory row filter a client cannot opt out of, a field allowlist, explicit CORS origins, and bounded, streamed exports.
Errors a page can branch on. A refused request carries status, code and path on the Error; the codes are a closed list, typed and frozen with the API.
Accessibility
Every feature is keyboard-operable, nothing requires dragging, and one polite live region carries every state. 872 end-to-end test runs — most specs on a desktop and a narrow viewport — with axe-core in every state including open menus and all five looks of the design prototype, a spec that records the status line's successive states so an announcement made twice, never, or too early fails the build, and checks of the language of every leaf node and accessible name in the real DOM. A grid never takes the focus it did not have.
Screen-reader pairings tested: none yet (#5). Browsers tested: Chromium, Firefox and WebKit through the end-to-end suite (Firefox and WebKit at a desktop viewport); Safari itself, iOS and Edge not tested.
Known limitations
- No calculated fields beyond date parts (year, month).
- One column dimension per pivot; at most 256 generated columns and 2,000 rows.
- Paging and virtualization are mutually exclusive, and so are paging and grouping.
- At most two levels of grouping; while grouped, rows can be neither selected nor edited.
<opengrid-pivot>needs a server — the browser engine has no pivot export.- The Rust crates,
opengrid-serverincluded, are not on crates.io; the server runs from the repository. - Under a production bundler, a worker needs
engine/andworker.jsserved by the page and passed asmoduleUrlandworkerUrl— the same rule as for the element module (frameworks guide). - No CDN build. Not tested as whole applications: SvelteKit, Nuxt, Next.js, Astro; React 18 is not rendered on the server in tests. A Vite production build needs
loadOpengrid({ moduleUrl }).