Skip to content

Releases: casoon/opengrid

0.9.0

Choose a tag to compare

@casoon casoon released this 01 Oct 07:17
30eab2e

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: PivotLimits has 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 by GET /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: PivotLimits has
    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
    id and every value cell headers naming its row and column headers. The engine's limit
    (PivotLimits::max_column_dimensions, reported by GET /source) is 2 now; three is an error
    with a sentence. The toolbar offers "+ Column" until there are two; columnFull reads "Two
    column fields already". Conformance cases with two column dimensions run on every path.

0.8.1

Choose a tag to compare

@casoon casoon released this 30 Sep 18:31
8ee071f

Screen-reader pairings tested: none yet (issue #5).
Browsers: Chromium, the whole e2e suite. A fix to the types; nothing breaks.

Fixed

  • get_view no 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's View,
    with one (get_view<PivotView>(pivot)) the pivot's.

0.8.0

Choose a tag to compare

@casoon casoon released this 30 Sep 17:45
f86cce9

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 are aria-hidden text beside the names. A page that read a header's
    textContent reads the mark too; read the button's first span, or what a reader hears.
  • Rust: PivotQuery and ValidatedPivotQuery have new fields (sort; orders, sort), so a
    struct literal needs them. The JSON form is unchanged for a pivot without sort.

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_columns gives a row dimension or a measure its title, and set_formats formats
    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
    title is reported in the status line. get_pivot still exports raw names and values.
  • A view and events for <opengrid-pivot> (#106).
    get_view/set_view read and write { rows, columns, values } — the three attributes as one
    value, set in one query — and opengrid-view-change reports every change of them.
    opengrid-query now fires after every pivot answer too. OpengridPivot's view,
    defaultView and onViewChange are typed PivotView in React, Vue and Svelte;
    get_view<PivotView>(pivot) in TypeScript. ViewChangeDetail and Connection take 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 takes sort: [{ field, by?, direction }] — sent only when there is one — and the element a sort attribute
    that is part of its view. Row-dimension headers, and measure headers without a column
    dimension, are sort buttons with aria-sort. On PostgreSQL a sorted pivot runs one query per
    level instead of one GROUPING SETS statement.
  • Folding groups in <opengrid-pivot> (#110).
    With two row dimensions or more, a group's subtotal row header is a button with
    aria-expanded that 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
    collapsed attribute, part of the view. New parts group-toggle and group-mark.
  • Choosing the fields of <opengrid-pivot> (#112).
    With the toolbar attribute 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 new fields and measures attributes. 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).
    A filter attribute takes the wire's filter JSON and is part of the view; the filter acts on
    the raw rows, before the pivot. With toolbar the 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

Choose a tag to compare

@casoon casoon released this 30 Sep 11:25
2fe8a47

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 on Escape (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

Choose a tag to compare

@casoon casoon released this 30 Sep 09:29
75024ac

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 placeholder is into 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

Choose a tag to compare

@casoon casoon released this 30 Sep 08:48
8e76bff

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-operator is a <button> now, not a <select>; a page script that
    set its value sets nothing. The new part operator-menu is the menu.

npm: npm install @casoon/opengrid@0.7.0 · Live demos: https://og-vanilla.casoon.dev

0.6.0

Choose a tag to compare

@casoon casoon released this 30 Sep 06:52
9261157

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 header part 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
    with opengrid_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).
    createLocalProvider and createWorkerProvider answer <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, a pivot message in the worker). The element calls
    a provider's new optional pivot(json) and falls back to execute, so providers of a page's
    own keep working; createPivotProvider has pivot too. examples/pivot-demo/browser.html
    shows it without a server.
  • XLSX export on the server (#72).
    POST /export/{source}?format=xlsx writes an Excel workbook, and exportRows(rest, query, { format: "xlsx" }) asks for it through createRestProvider — with any other provider it is
    a TypeError. 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
    feature xlsx of opengrid-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's sort-button, the pivot's row-header and
    total-row — and examples/pivot-demo and examples/table-demo style them through
    ::part. The table shows its values as the grid does: formats apply to its cells, and
    presentation (set_columns) gives a column its title, align, mono, emphasis and
    muted; the grid's width, aggregate and facet are 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's header part 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 serde in the browser
    (#41). opengrid-json reads 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. serde and serde_json are 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 what serde_json read
    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 implement serde::Serialize/Deserialize.
    Read and write them with opengrid_json (from_str, to_string, FromJson, ToJson);
    Value::deserialize_typed is Value::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 carries aria-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

Choose a tag to compare

@casoon casoon released this 27 Sep 07:04

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-server included, are not on crates.io; the server runs from the repository.
  • Under a production bundler, a worker needs engine/ and worker.js served by the page and passed as moduleUrl and workerUrl — 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 }).