- Dependency refresh for the web suite — upgraded the npm workspace to the latest framework and build stack, including Angular 22, React 19.2, TypeScript 6, Vite 8, Vitest 4.1, jsdom 29, tsup 8.5, ng-packagr 22, and current supporting type/test packages. Root and package-local lockfiles were regenerated for the 1.0.8 release.
- Node 26 toolchain — local package metadata and GitHub Actions workflows now target Node 26.4.0 with npm 11.17.0, satisfying Angular 22's runtime floor and keeping CI aligned with the publish toolchain.
- Looser published framework peers —
@ornery/ui-gridnow peers on@angular/common/@angular/core >=17.3.0andrxjs ^6.5.3 || ^7.4.0, matching the wrapper's actualinput()/output()API floor instead of the repo's current Angular dev version.@ornery/ui-grid-reactnow peers onreact >=18.0.0andreact-dom >=18.0.0so React consumers can install newer framework majors without package-manager conflicts. - TypeScript 6 config migration — removed deprecated
baseUrlusage from project tsconfigs and made root path mappings explicitly relative. Core and vanilla declaration builds now usetsc --emitDeclarationOnlyinstead of tsup's DTS worker, avoiding tsup's internal deprecatedbaseUrlinjection while preserving the existing JS bundles.
- Package builds under TypeScript 6 —
@ornery/ui-grid-coreand@ornery/ui-grid-vanillanow emit declarations cleanly with explicitrootDir/ declaration configs. The Angular package build also continues to emit FESM and DTS bundles successfully under the upgraded toolchain.
- Rust dependency refresh — updated the Cargo workspace to current crate versions, including
anyhow1.0.103,js-sys0.3.103,wasm-bindgen0.2.126,wasm-bindgen-test0.3.76,regex1.12.4,rmp-serde1.3.1,serde_json1.0.150,jiff0.2.31, and the egui stack (egui,egui_extras,eframe) 0.35. - Rust release metadata aligned to 1.0.8 — workspace crate manifests and internal crate dependency versions were synced so
ui-grid-core,ui-grid-egui,ui-grid-wasm, andui-grid-c-abipublish against the 1.0.8 crate set.
- egui 0.35 deprecation cleanup — replaced deprecated
Panel::show_inside/CentralPanel::show_insidecalls withshowin the egui adapter and demo socargo clippy --workspace --all-targets --all-features -- -D warningspasses with the upgraded egui stack.
- No-bundler vanilla browser bundle —
@ornery/ui-grid-vanillanow emitsdist/browser/ui-grid-element.js, a browser ESM file that bundles the core runtime and auto-registers<ui-grid-element>for static HTML hosts. The Web Component docs now distinguish this direct browser artifact from the packagedist/index.jsCommonJS entry and packagedist/index.mjsESM entry.
- Wasm parity merge — the
wasm-parityfeature branch is merged into main. The Rust/WASM core now reaches functional parity with the TypeScript engine across sorting (includingSortKind::DeferToHostfor JSsortingAlgorithmcallbacks), tree building (withrowIdentitycallback bridging), filtering (with prepared-filter fast path and wildcard-fallback correctness), validation (withHostValidatorMarkerfor consumer-registered validators), and row-searcher condition reporting. Wasm parity specs lock the contract so any future Rust-side drift is caught against TS. - Angular 21.2.17 — all
@angular/*framework and dev-tool packages bumped to latest 21.2.x patches, resolving 8 security advisories (DoS, XSS, information leak). - Build dependency hardening — npm
overridesforce fixed versions of@babel/core(7.29.7),esbuild(0.28.1),piscina(5.2.0),undici(7.28.0), andvite(7.3.5) to resolve transitive build-time advisories. All are build-only deps; none ship to production.
- SSR
renderApplicationnow passesallowedHosts: ['localhost']— required by Angular 21.2.17's host validation. - Missing
@testing-library/dompeer dependency added to@ornery/ui-grid-reactdevDependencies. - Rust row-searcher
parsed_condition_tagcorrectly reports"contains"(instead of"regex") for auto-detected literal contains and wildcard-fallback filter conditions, matching TypeScript semantics.
Rust core + ui-grid-egui adapter reach functional parity with the canonical TypeScript engine and the vanilla web component. The web suite (Angular / React / Web Components) is unchanged; this release scopes to ui-grid-core, ui-grid-egui, and ui-grid-c-abi. Wasm-parity specs lock the contract in so any future Rust-side drift is caught against TS.
rowIdentityJS callback bridge — closures can't cross the wasm boundary via serde, sobuild_grid_rows_js,build_pipeline_js, andresolve_grid_row_id_jsnow pluck the JS callback off the liveJsValueand pre-resolve identities (recursively, for tree mode) intorow_identity_overridesbefore deserialization. Rust drops therow_id_fieldlookup entirely; TS drops the bridge bypass ingrid.core.wasm-bridge.ts.- Identity-cached
buildGridRows— mirrors the TSrowsCachekeyed on(data, options, hidden, expanded, rowSize)reference identity. Public surface:get_cached_grid_pipeline_rows,clear_grid_pipeline_rows_cache. Cache is cleared onmark_dirty()and at the start ofrun_grid_benchmarkso stale entries from recycled allocator addresses can't cause false hits. - Prepared-filter fast path —
prepare_grid_column_filters(per-column, once) +matches_grid_row_prepared_filters(per-row, no allocation), with wasm shims so JS callers can opt in. Pipeline now uses the prepared variant. - Async validators —
RunGridCellValidatorsRunner::{Pending, Ready}andrun_grid_cell_validators_asyncmirror the TSPromise<string[]>shape without pulling in a Rust async runtime. Hosts step the runner manually or callrun_to_completion. GridValidatorRegistry.set_validator/get_validatorbridged through wasm withHostValidatorMarker+RegisteredValidatorMessage. Closures stay JS-side; the bridge calls back into JS for non-built-in validators while Rust's built-inrequired/minLength/maxLengthshort-circuit locally.KeyOverrideSpec— Rust analog of TSGridKeyEventOverride.GridOptions::key_down_overrides: Vec<KeyOverrideSpec>lets consumers opt out of any built-in keydown (Ctrl+A, F2, Home/End, Space, etc.). Match supportskey_code,key, and modifier combinations.SortKind::DeferToHost—guess_sort_kindreturns this variant for columns withsortingAlgorithm. The wasm bridge falls back to TS sort for those columns only; columns without a custom algorithm continue sorting in Rust.- Generic exporter registry — new
crates/ui-grid-core/src/exporter_registry.rswithregister_grid_exporter/unregister_grid_exporter/export_gridand a thread-safeOnceLock<RwLock<HashMap<String, Arc<dyn GridExporter>>>>.GridExportScope::{Visible, All, Selected},GridRegisteredExportContext,GridExportResult,GridExportertrait. Built-in CSV exporter auto-registered viainit_default_grid_exporterssoexport_grid("csv", ctx)works without setup. run_grid_benchmark— host-agnostic Rust analog of TSgridApi.core.benchmark(iterations) -> Promise<GridBenchmarkResult>. Returns{iterations, total_ms, average_ms, visible_rows, rendered_items}. Egui demo wires it to a Benchmark button.resolve_exporter_filename,build_grid_header_context,format_grid_header_display_valueported from TS so Rust hosts (egui) compute exporter filenames and templated header values identically to the web suite.- Column-width persistence in
GridSavedState—column_width_overrides: BTreeMap<String, String>mirrors the TScolumnWidthOverrides(gated bysaveWidths). Save / restore round-trips the user's resize state across hosts. has_sorting_algorithmflag onGridColumnDef— set by the wasm bridge so Rust can detect sorting-algorithm columns without seeing the JS callback.
- Row selection chrome — synthetic
selectionRowHeaderColinjected whenenable_row_selection+enable_row_header_selectionare on. Per-row checkbox + select-all header. Width auto-scales to2 × icon_width(theme-driven) so the column stays narrow regardless of egui style overrides. Synthetic column is excluded from drag-reorder and the data-column min-width floor. - Selection interactions — Ctrl/Cmd-click adds to selection (additive); plain click is single-select; Shift-click extends to range.
Ctrl+Aselects all selectable rows;Spacetoggles the focused row. Drag-paint multi-row selection: press a row, drag across other rows — every row whose rect contains the pointer becomes the endpoint, the anchor→endpoint range is selected.enableFullRowSelection,noUnselect,modifierKeysToMultiSelect,enableSelectionBatchEventhonored. - Validation chrome — invalid cells (rows whose
$$invalid<col>flag is set byrunGridCellValidators) get a red border + tinted background. Hover shows the joined error messages tooltip viaget_grid_cell_error_messages.enable_select_allhonoured; the select-all checkbox auto-disables when no rows are selectable. - Row-edit lifecycle decoration —
mark_row_dirty,mark_row_saving,mark_row_clean,mark_row_errorpaint the row with theme-supplied tints (row_dirty_background,row_saving_background,row_error_background). Mutually-exclusive priority: error > saving > dirty. - Grid-level renderer hooks:
with_group_row_renderer(fn)— caller paints the entire group row, returnsGridGroupRowAction(defaults:None, returnsTogglewhen chevron is clicked).with_expandable_row_renderer(fn)— caller paints the entire detail row.with_empty_state_renderer(fn)— caller paints the empty state when filters knock all rows out.with_selection_checkbox_renderer(fn)— caller replaces both the per-row and select-all checkboxes (ctx.row = Some(...)for per-row,ctx.is_header = truefor select-all).
- Per-column hooks on
EguiColumnExt:with_filter_renderer(fn)— replace the default filterTextEditwith any control (dropdown, range slider, etc.). Mutate&mut Stringand returntrueto re-run the pipeline.with_header_controls_renderer(fn)— caller paints the sort / group / pin / move chrome; signals intent by pushingEguiHeaderAction::{ToggleGrouping, CycleSort, PinLeft, PinRight, Unpin, MoveLeft, MoveRight}.with_cell_editor(fn)— replace the default editor; returnstruewhen the value changed.with_cell_renderer(fn)— full custom cell paint.with_formatter(fn)— string formatter applied before the default label paints.
- Editor input-type switching — date columns use
egui_extras::DatePickerButton(jiffDate), boolean columns use a checkbox, numeric columns use a numericTextEdit. Hosts can still override viawith_cell_editor. - Keyboard navigation — Arrow keys / Tab / Shift-Tab move focus; Enter and
F2begin edit;Home/Endjump to row start/end;Ctrl+Home/Ctrl+Endto first/last row; first-character keypress on a focused cell begins edit pre-seeded with that character. - Pagination chrome — range label
M – N of total, page-size combo readspaginationPageSizesfrom options, prev/next visually disabled at edges (in addition to the click guard). - Column auto-fit on resize-handle double-click — double-clicking the resize gripper measures the header label + visible cell content via
egui::Fonts::layout_no_wrapand writes the result intocolumn_widths. Reserves 28px per visible header control (sort / group / pin) so the title isn't clobbered. column_widthsoverrides synced from drag-resize — each header cell's actual rendered width is captured back into the sharedcolumn_widthsmap after the user releases the pointer, so the outer scroll-area / pinned-region width math tracks the real table width and there's no empty scrollable space.- Pin / unpin preserves column widths — pre-fit pass on first paint measures every column without a declared width or override and stores the result in the shared
column_widthsmap; all four table layouts (unpinned single + pinned left / center / right) consult the same map, so widths survive pin/unpin. - Group / expand / tree chevron lands on the first data column —
primary_data_column_indexskips the synthetic selection column so the leading control doesn't paint inside the checkbox cell when row selection + expandable / tree are on at the same time. - Header label truncation — labels render inside a rect bounded by the controls' left edge, with
Label::truncate()so titles ellipsise instead of being clobbered by sort / group / pin chrome. - Filter input "clear" button — inline ✕ when the active filter is non-empty.
- Save / restore state with
column_widths—EguiGrid::column_width_overrides()reader +set_column_width_overridewriter; round-trips throughGridSavedState. - Theme fields for selection + validation + row-edit chrome —
GridThemegainedrow_selected_background,row_selected_indicator,cell_invalid_border,cell_invalid_background,row_dirty_background,row_saving_background,row_error_background. Populated across all four presets (default light/dark, wireframe light/dark). Mirrors the TS--ui-grid-*CSS variables. - Custom selection-checkbox context — new
GridSelectionCheckboxContext { row, options, theme, enabled, is_header }for the new selection-checkbox renderer hook. - Group / expandable / empty-state contexts —
GridGroupRowContext,GridExpandableRowContext,GridEmptyStateContextgive renderer closures access to the row/group, options, columns, theme, and (for groups) the collapsed flag. - Header / cell / filter contexts —
GridHeaderControlsContext,GridCellContext,GridFilterContextgive per-column renderer closures access to the column, labels, icons, theme, and (for headers) sort direction / pin direction / capability flags.
- Stable C-facing ABI — opaque engine lifecycle + JSON / MessagePack transport helpers + projection / state APIs; native hosts can drive the shared engine without embedding Rust types directly.
- C++ wrapper —
ui-grid-cppwith RAII engine management, typed sort/group/pin command builders, JSON and MessagePack helpers. - LVGL native prototype —
ui-grid-lvgladapter with theme/column extension headers and an SDL-backed demo.
expand_detailicon default flipped tochevronRight— collapsed → right / expanded → down, matching the tree-toggle convention. The C-ABI fixtureprojection-envelope-v0.1.0.jsonwas regenerated.GridSavedStateround-trips — emptyVec/BTreeMap/Optionfields are now skipped during serialization so empty-input round-trips produce{}instead of{sort: null, pagination: null, columnOrder: [], …}. Matches TS shape.row_state/editreturn shapes aligned with TS:ToggleGridRowExpandedResult/ToggleGridTreeRowExpandedResultare named-field structs (was tuples →[bool, {…}]).clear_grid_edit_sessionreturns aClearGridEditSessionResultstruct (was a tuple).find_next_grid_cellreturnsFindNextGridCellResult { row, column }(was justGridCellPosition; TS callers expect resolved row + column).enable_cell_edit_on_focusis nowOption<bool>so a column withSome(false)correctly opts out even when grid-level focus-edit is on.
row_searcher::guess_conditionnow emits a literal-substring regex for the default contains case. Previously emittedComparator(Contains), butrun_column_filterhad noContainsarm in the comparator branch — it fell through to_ => true, so the filter silently matched every row. Tree filtering, pipeline filtering, and the row-searcher contains test were all visibly wrong before.tree::resolve_row_idnow honoursoptions.row_identity_overrides, the hidden serde field populated by the wasm bridge from the JSrowIdentitycallback.- Demo app expanded — exercises every new feature behind a toolbar toggle: row selection, validation, row-edit save/discard, grouping, tree view, expandable, pinning, infinite scroll, theme switching across all four presets, generic exporter registry (placeholder PDF), benchmark probe button, auto-fit button, custom group / expandable / empty-state renderers.
tree::resolve_row_idignored therowIdentitycallback in tree mode; the bridge now seedsrow_identity_overridesand tree resolution honours it.row-sortersilently fell through forsortingAlgorithmcolumns;SortKind::DeferToHostnow flags those for the wasm bridge to handle.stateshape drift between Rust and TS round-trips (above).- Wasm-parity coverage — added 14 new
*.wasm-parity.spec.tsfiles (tree, grouping, pagination, pinning, filtering, sorting, viewmodel, identity, row-state, edit, pipeline, state, infinite-scroll, display) so any future Rust-side bug is caught against the canonical TS implementation.
- Pinning no longer grows the last column — the 3-table pinned layout (left / center / right) sized each region from
count × 176pxand the center table's last column usedColumn::remainder(), which silently expanded to fill leftover space whenever a column was pinned. Region widths now come from the actual declared column widths, anddraw_tableaccepts afill_remainderflag that'strueonly for the unpinned single-table layout. - Pin / unpin no longer resets columns to a 120px scrunched fallback — egui_extras keeps its column widths inside a private per-table
TableStatekeyed byid_salt, so widths measured in the unpinned table were lost when a column moved into the pinned region (and vice versa). The pre-fit pass on first paint stores measurements in the sharedcolumn_widthsmap, so all four table layouts agree on widths. - Drag-resize syncs back to the outer layout — egui_extras' built-in resize handle widened the header cell but the outer scroll-area's
min_width(computed fromcolumn_widths) didn't follow. The grid now captures each header cell's actual rendered width back intocolumn_widthson pointer release. - Double-click on the resize handle now auto-fits the column — egui_extras 0.34.1's built-in dblclick check reads the response under
ui.id().with("resize_column").with(i)but the actual handle is registered understate_id.with("resize_column").with(i), so the IDs never match. We read the response under the correct id, run our ownauto_fit_column, and callTableBuilder::reset()so the new override takes effect immediately. - Drag-paint multi-row selection across rows — egui only fires
response.dragged()on the cell that received the press, so dragging across rows never expanded the selection past the anchor. Replaced with a pointer-rect hit test on every cell during an active drag-paint session. - Selection chrome no longer hides the expand chevron under selection + expandable — leading-controls path now uses
primary_data_column_indexso the chevron lands on the first data column instead of the synthetic checkbox cell. - Header label truncates with ellipsis instead of being clobbered by controls — controls render right-to-left first; the label renders inside a rect bounded by the controls' left edge with
Label::truncate(). - Synthetic selection column drag-reorder disabled — the column is now excluded from both drag-source and drop-target paths.
- Selection checkbox centred on every theme — replaced the hardcoded 30px column with
2 × icon_widthso theme overrides toStyle::spacing.icon_widthflow through, and centred the checkbox viascope_builder + centered_and_justifiedinstead ofhorizontal_centered.
- Rust core feature parity with the canonical TypeScript engine —
@ornery/ui-grid-core's WASM build now mirrors all fourteen ported pipeline modules (viewmodel, identity, row-state, edit, display, infinite-scroll, pinning, pagination, sorting, filtering, tree, grouping, state, pipeline) and a new wasm-parity spec suite locks the contract in. Each TS module has a subprocess-driven parity test that runs the wasm shim and asserts byte-identical output for the same input fixtures. - Rust ↔ JS callback bridge for
rowIdentity— closures can't cross the wasm boundary via serde, so the bridge now plucks the JSrowIdentitycallback off the liveJsValueand pre-resolves identities (recursively, for tree mode) before serde deserializes.build_grid_rows_js/build_pipeline_jsandresolve_grid_row_id_jsnow use the wasm path consistently. - WebAssembly performance pass — cleaner wasm-bridge plumbing reduces per-call overhead on hot paths, and demo apps disable wasm module auto-registration so consumers opt in explicitly.
- Vitest config across the workspace + CI coverage reporting — added a shared vitest config and brought CI coverage reporting back online so test gates run consistently across packages.
- Row selection on the expandable harness across all wrappers — the Angular, React, and Web Components "expandable" demos now also enable
enableRowSelection, so the selection checkbox column appears alongside the expand toggle. Live demos exercise the same UX surface across all three frameworks. - Egui parity plan — added a planning doc describing how the egui adapter will reach parity with the web-suite features (selection / validate / row-edit, keys / filters / pagination, new-core APIs).
- Date-picker slot template on the Web Components templated harness — the vanilla demo now renders an interactive
<input type="date">for the renewal column via a slot template, with a delegated change handler that walks back to the body cell, mutates the row in place, and re-publishes throughsetData()so picked values persist.
- Expand/tree toggle leads the cell content instead of trailing it — the disclosure chevron now sits on the leading edge of the primary cell, in front of the row label, matching the tree-toggle convention. The
margin-inline-start: autooverride that previously pushed the expand toggle to the trailing edge has been removed; both initial render and patch paths emit the toggle before.cell-content. - Host element establishes its own stacking + paint context —
:hostnow setsisolation: isolateandcontain: layout paint, so sibling repaints in the surrounding page (e.g. button hover transforms on the demo pages) don't invalidate the grid's sticky filter strip. Fixes the filter-row flicker observed on the React and vanilla demos while Angular was already flicker-free via its component view encapsulation. - Viewport-width measurement under-estimates by 2px — the body viewport's
overflow-x: autoplus sub-pixel rounding on the host box made tracks resolve ~1–2px wider than the actual renderable width, surfacing a spurious default horizontal scrollbar.setViewportWidthnow floors and subtracts a 2px safety margin. - Demo button hover effects no longer use
transform— the web-components and React docs pages droppedtransform: translateY(-1px)on.scenario-button:hover(and friends) in favour of a plain background gradient shift.transform/filterpromote the button to its own compositing layer; combined with the host isolation fix this removes both the trigger and the surface for filter-row repaint flicker. - Per-row patch fingerprint cache for selection / focus / expand toggles —
patchExistingRowsnow fingerprints each row's visual state (selection, focus, expand, dirty/saving/error, tree level, per-column validity, entity reference) and skipspatchBodyCellwhen nothing relevant changed. Selection / focus / expand toggles now repaint only the affected row plus the previously- and newly-focused cells, instead ofO(rows × cols). Caches are cleared on full re-mount, slow-path innerHTML rebuild,setFrameworkRenderedSlots, and templateMutationObserverfiring. setFrameworkRenderedSlotsexposed onVanillaUiGridElement— external consumers no longer need to cast the element to call it.controller.getEditingCellKey()is also exposed for the patch path.
- Framework-slot wrappers (Angular embedded views, React portals) survive across patch passes — the patch path's per-row fingerprint short-circuit was skipping
stageCellfor unchanged rows, soframeworkSlots.flush()diffed an empty pending set against a full last set and emitted a spuriouscellSlotsChanged removedfor every cell. Custom-rendered cells were torn down on first paint and only reappeared after a scroll triggered a virtual-body rebuild that re-staged everything. AddedFrameworkSlotBridge.carryRowCells(rowId)and call it frompatchExistingRowswhencanSkipis true so the row's known slots carry forward and the diff is a no-op. - Cell selection retention bug across shadow boundaries — click handlers used
closest('.body-cell')which stops at shadow boundaries, so clicks on framework-projected light-DOM cell content (Angularng-template, React render props) never resolved to the body cell. The previously focused cell kept itscell-focusedclass, making multiple cells appear "selected" simultaneously. Replaced everyclosest('.body-cell')call inevents.tswith a newbodyCellFromEvent()helper that walkscomposedPath(), which crosses the shadow boundary correctly. - Expand toggle no longer attaches to the row-selection checkbox cell —
isGridPrimaryColumnreturned true forvisibleColumns[0], but when row selection is enabled the syntheticselectionRowHeaderColis prepended at index 0, so the tree/expand toggle and indent calc attached to the checkbox cell instead of the first data column. Skip theselectionRowHeaderColsentinel inisGridPrimaryColumn(TS and Rust core, kept identical so wasm-parity specs still hold). - React demo page double scrollbar on Linux —
.react-demo-framehadoverflow-x: auto. Per the CSS spec, when one axis is non-visible the other is coerced fromvisibletoauto, so the frame grew its own vertical scrollbar in addition to the grid's body-viewport scrollbar. Switched tooverflow: visible; horizontal overflow is owned by the grid's body viewport internally. - Vanilla
MutationObserverinfinite-loop with framework wrappers — the observer fired on every slot mutation, including the<span slot="cell-…">projection wrappers Angular and React append for cell rendering. Each mutation triggeredensureController→setOptions→refresh→cellSlotsChanged, which made the wrapper append more spans, ad infinitum. The observer now filters to<template>-typed slot mutations (the consumer-template surface it actually cares about). React tests also moved to happy-dom now that the loop is gone. - Rust core ↔ TS core divergences — fixed a set of silently-different outputs that the new parity specs caught:
row_state::ToggleGridRowExpandedResult/ToggleGridTreeRowExpandedResultnow serialize as named-field structs instead of tuples (which produced[bool, {...}]instead of{expanded, nextExpandedRows}).edit::clear_grid_edit_sessionnow returns aClearGridEditSessionResultstruct (was a tuple).edit::find_next_grid_cellnow returnsFindNextGridCellResult { row, column }(was justGridCellPosition; TS callers expect resolved row + column).edit::should_grid_edit_on_focus:GridColumnDef.enable_cell_edit_on_focusis nowOption<bool>so a column withSome(false)correctly opts out even whenGridOptions.enable_cell_edit_on_focusisSome(true)— TS uses??coalescing, so an explicitfalsewins over options-level true.state::GridSavedStatenow skips serializing emptyVec/BTreeMap/Optionfields so empty-input round-trips produce{}rather than{sort: null, pagination: null, columnOrder: [], …}.row_searcher::guess_conditionnow emits a literal-substring regex for the default contains case. Previously emittedComparator(Contains), butrun_column_filterhad noContainsarm in the comparator branch — it fell through to_ => true, so the filter silently matched every row. Tree filtering, pipeline filtering, and the row-searcher contains test were all visibly wrong before.tree::resolve_row_idnow honoursoptions.row_identity_overrides, the hidden serde field populated by the wasm bridge from the JSrowIdentitycallback.c-abifixture regenerated to drop the now-skippedenableCellEditOnFocusdefault-emit so the deterministic engine round-trip stays stable.
- Per-column header icons now hide on opt-out instead of rendering disabled — columns with
sortable: false,enableSorting: false, orenableGrouping: falseno longer render their sort or group buttons in the header, matching the legacy ui-gridng-ifhide pattern and the existing pin behavior. The grid-levelenableSorting/enableGroupingmaster flags still gate the entire feature. - npm publish pipeline now mirrors the runtime dependency graph — the web suite publishes as
core → vanilla → {react, angular}, with the React and Angular jobs waiting onpublish-vanillaand polling the registry for the vanilla tarball before building.
- Per-column
enableCellEdit: falseis now honored when the grid is globally enabled —VanillaGridController.beginCellEditguards onisCellEditable, so opted-out columns can no longer enter edit mode via double-click, F2, Enter, printable keys, or the post-Tab/EnterresumeEdithop. Brings the vanilla and Angular hosts in line with the React wrapper, which already enforced this.
- Major web-suite feature expansion across Angular, React, and Web Components — the shared grid runtime now ships row selection, keyboard cell navigation, infinite scroll, row edit workflows, cell validation, CSV/JSON import, CSV/Excel/PDF export, pagination, richer i18n coverage, and custom component/template integration across the web hosts.
- Full declarative and imperative Web Component surface —
@ornery/ui-grid-vanillanow exposes a much broader framework-free custom-element API, including stronger attribute/property bridging, standalone mounting helpers, better template projection, and documentation/examples that cover both declarative markup-first usage and programmatic control. - Expanded docs and live examples for the new grid capabilities — the docs app now includes dedicated user-facing guides and demos for exporting, importing, selection, save state, validation, pagination, infinite scroll, keyboard navigation, pinning, column moving, and custom components.
- 1.0.0 unifies the web packages around one shared runtime — Angular and React now act as thin framework bridges over the same vanilla custom-element engine, giving all three web surfaces closer feature parity and a more consistent behavior model.
- Angular package setup is lighter for consumers —
@ornery/ui-gridnow wraps the shared vanilla runtime directly, reducing Angular-specific package surface and aligning the Angular package more closely with the cross-framework architecture. - React package positioning moved to the shared web-component runtime —
@ornery/ui-grid-reactnow depends on the vanilla package as part of its public integration model, so React consumers inherit the same underlying grid engine and slot/template behavior as the other web hosts.
- Stable C-facing ABI over the Rust grid core — added
ui-grid-c-abi, exposing opaque engine lifecycle, JSON and MessagePack transport helpers, projection/state APIs, C smoke examples, and a projection benchmark so native hosts can drive the shared grid engine without embedding Rust types directly. - C++ wrapper for the native ABI — added
ui-grid-cppwith RAII engine management, typed sort/group/pin command builders, JSON and MessagePack projection helpers, and smoke examples for a higher-level native integration surface. - Initial LVGL native C prototype — added
ui-grid-lvgl, including the first LVGL-backed adapter, theme/column extension headers, and an SDL-backed demo app that proves the native widget path end to end. - Native adapter roadmap documentation — added a dedicated plan for the Rust C ABI, C++, Go, and follow-on native adapter work so the non-web host strategy is documented alongside the prototype.
- Version sync now covers the full monorepo with one command — the release sync script now updates npm package manifests, npm lockfiles, Cargo workspace versioning, crate-to-crate Rust dependency versions, Cargo.lock, and internal peer metadata cleanup in one pass.
- Project docs and positioning now include native C/LVGL support explicitly — refreshed the root README and native adapter docs so the repo presents Angular, React, Web Components, Rust/egui, and native C/LVGL as first-class delivery targets.
- Column resizing behavior is now consistent across Angular, React, and vanilla hosts — all three web surfaces now support the same drag-resize, double-click auto-fit, hover indicator, and container-filling demo behavior, including smoother virtualized resizing paths that avoid per-mousemove full rerenders.
- Cross-suite resize polish and parity issues — fixed missing React resize handle styling, removed header scrollbar artifacts during resize, and aligned the Angular, React, and vanilla demos so the column resize affordances behave the same across the web suite.
- Angular and Rust CI follow-up regressions — corrected the Angular custom-element CI issue and refreshed the ABI fixture data needed to keep the Rust/native test path green after the new adapter work landed.
- Angular Elements declarative surface parity —
@ornery/ui-gridnow supports declarative HTML attributes, mirrored JS properties, and attribute-to-options synchronization so the Angular-backed custom element can be configured markup-first like the vanilla build. - Custom-element coverage for declarative and imperative mixing — added Angular and vanilla tests that verify declarative
data/column-defsrendering and confirm later imperative augmentation does not discard attribute-derived state. - Cross-framework renderer/template docs — expanded the docs app and Web Component docs with Angular
TemplateRefcontext tables, vanilla slot-template token references, and React cell/expandable renderer context examples. - Release version-sync tooling — added a dedicated version-sync workflow for package manifests and npm lockfiles to keep the
0.1.9release metadata aligned across the repo.
- Web Component documentation now distinguishes the two shipped outputs clearly — refreshed the README, Getting Started, and Web Component docs so Angular Elements and vanilla custom elements are described separately, with their shared declarative surface and their differing runtime expectations called out explicitly.
- Angular home demos now showcase both host surfaces — the home page now has separate Angular Native and Angular Element modes, with dedicated Angular Element scenario harnesses for expandable, tree, templated, pinning, and trading demos.
- Web Components demo host moved to declarative attribute-driven setup — the primary and scenario grids on the web-components page are now configured from declarative attributes first, with template injection and imperative bridge wiring layered on top only where needed.
- Release metadata refreshed for
0.1.9— regenerated the root and package-local npm lockfiles after syncing internal package versions to the next intended web release version.
- Vanilla custom-element option augmentation no longer drops declarative data — reading
grid.options, spreading it, and writing it back now preserves attribute-deriveddataandcolumnDefsinstead of resetting the declarative surface. - Angular Element trading terminal now live-updates correctly — the home-page Angular-backed custom element trading harness now drives live ticks and applies the intended colorized cell templates for price and change columns.
- Vanilla trading demo now reattaches slot templates and resumes ticking after mode switches — the web-components Trading scenario correctly reinjects its slot templates and restarts the live-update loop when switching between scenarios.
- Declarative web-component docs and demos now match runtime behavior — the live examples, code snippets, and docs pages were brought back into sync for attribute-driven setup, template slots, and high-frequency data updates.
- Public theming tokens work directly again across the web packages — normalized the runtime styles in the shared core and React wrapper so consumer overrides can target the documented
--ui-grid-*CSS custom properties again, while--app-ui-grid-*aliases continue to work as well. - Theming docs now match the shipped runtime behavior — simplified the README and theming guide back to the supported public token surface
- Trading terminal demos across Angular, React, and vanilla docs surfaces — added a shared market-data generator plus live trading-terminal demos to the Angular home page, the React docs page, the Web Components page, and the browser harness so all three web hosts now showcase the same high-frequency update scenario next to the existing pinning demos.
- Declarative vanilla custom-element API —
@ornery/ui-grid-vanillanow supports a full declarative configuration surface on<ui-grid-element>, including observed HTML attributes, mirrored JS properties, and attribute-to-options synchronization for framework-free usage. - Custom header rendering for non-Angular hosts — added
headerRenderersupport through the shared core model and export helpers, with React and vanilla integrations plus tests so wrapper consumers can override header content without forking the grid.
- Shipped wrappers now render only the grid — moved hero/metrics/toolbar/benchmark chrome out of the React wrapper, Angular component template, and vanilla custom element and into the demo hosts instead, so published package consumers get the grid surface only.
- Benchmark presentation is aligned across the web suite — Angular, React, and vanilla primary demos now expose the same benchmark controls, visible-row metrics, grouping/virtualization summary, and benchmark-average readout for side-by-side comparison.
- Docs and examples refreshed for the new API surface — updated
README.md, Getting Started, API Reference, Web Component, and Rust/WASM documentation to cover the declarative vanilla API, the benchmark/demo host split, and the latest wrapper usage patterns.
- Vanilla trading scroll performance — the Web Components trading demo no longer replaces the entire grid render tree on every price tick; it now uses incremental data refresh and virtualization-aware body updates so scrolling stays smooth under live updates.
- React styles packaging and web-suite bundling — fixed package output so
@ornery/ui-grid-react/stylesresolves to an emitted CSS asset and cleaned up bundling/packaging issues across the web suite demo surfaces and library outputs.
- egui trading terminal demo — the native Rust demo now includes a dedicated trading example with live-ticking sample data and trading-specific columns, expanding the egui showcase beyond the existing customer-grid scenarios.
- Rust demo app refreshed around the new trading surface — updated the egui example app and shared demo data wiring so the native showcase can present the new trading workflow cleanly alongside the existing grid capabilities.
- Rust docs reorganized around the two Rust delivery paths — refreshed
docs/rust.md,docs/rust-egui.md, and the egui README so Rust/WASM browser usage and native egui usage are documented separately with clearer install, usage, and demo guidance.
- egui demo/widget polish — applied the supporting widget and example cleanup needed for the new demo flow, including the small
grid_widget.rsadjustments and Rust hygiene follow-up (cargo fmt/ clippy) that keep the native adapter aligned with the current shared grid behavior.
- Angular / web components / vanilla horizontal scroll layout —
.grid-framewas usingdisplay: flex; flex-direction: columnwith.grid-tableas aflex: 1 1 autochild. When scrolled to the far right, the flex sizing model caused column tracks to misalign with their headers. Changed both selectors ingrid.core.styles.scssto match the React wrapper's simpler approach:.grid-framekeeps onlyoverflow: hiddenand.grid-tableusesdisplay: grid. All three non-React host types (Angular, web components, vanilla) share this file, so all three are fixed by the single change. - React demo
mountUiGridimport (TS2305) —src/types/ornery-ui-grid-react.d.tscontained an ambientdeclare moduleblock that overrode the tsconfigpathsalias and resolved@ornery/ui-grid-reactto the staledist/index.d.mts, which did not exportmountUiGrid. Deleted the ambient declaration file so thepathsalias (projects/ui-grid-react/src/index.ts) resolves correctly. Added"jsx": "react-jsx"totsconfig.jsonand included the React source intsconfig.app.jsonincludes so the React demo page compiles without errors. - GitHub Pages broken image — The egui screenshot on the
/rustpage used an absolute path (/docs/screenshots/pinning-100k.png) which does not resolve under the Pages sub-path. Changed to a root-relative path (docs/screenshots/pinning-100k.png) and copied the asset topublic/docs/screenshots/so it is bundled with the Angular build output.
/rusttop-level page — new hero page at/rustwith a switchable tab layout: Rust / WASM (browser delivery) and egui Native (desktop/native delivery). Replaces the previous docs-embedded Rust landing and presents both delivery paths as first-class options.- egui pinned-grid screenshot —
docs/screenshots/pinning-100k.pngadded to the egui tab of the Rust page, showing fixed headers and pinned-column layout at 100 k rows.
- Brought eGui widget into feature partiy with angular, pinnable columns, save load state, CSV/overridable export, overridable controls, a11y, 118n, and more...
- Added a new top-level Rust landing page at
/rustwith a hero header and switchable Rust/WASM and egui tabs. - Expanded the Rust / egui docs with install, minimal usage, feature recipes, pinning, save/restore state, CSV export, column extensions, and a native demo workflow.
- Added a pinned-grid screenshot at the top of the egui section to show the native desktop layout and fixed-header / pinned-column behavior.
- Refreshed the Rust docs to present Rust/WASM and egui as complementary delivery paths for browser and native hosts.
@ornery/ui-grid-corepackage — shared logic (grid engine, state, filtering, sorting, grouping, pagination, pinning, infinite scroll, tree, WASM bridge) is now published as a standalone package with zero Angular and zero React peer dependencies. React, Angular, and the new Vanilla/Web Component builds all consume it.- Vanilla / Web Component build (
@ornery/ui-grid-vanilla) — full-featured grid delivered as native Custom Elements with no framework runtime requirement. Reaches feature parity with the Angular and React wrappers. - Drag-and-drop column reordering — column headers in the React wrapper can now be dragged to reorder columns interactively.
- New CI workflows for Vanilla (
ci-vanilla.yml) and Rust (rust.yml). The Rust workflow triggers only on changes to Rust source files. - Single unified release workflow (
publish.yml) triggered by av*tag: publishesui-grid-corefirst, then React, Vanilla, and Web in parallel once the core version is available on the npm registry.
- React install no longer pulls in Angular peer dependencies. Angular-specific deps (
@angular/core,@angular/common,rxjs) were runtime-polluting React installs; they now live only in the Angular (@ornery/ui-grid) package. - Grid business logic extracted from
@ornery/ui-grid(Angular) and@ornery/ui-grid-reactinto@ornery/ui-grid-core. All three framework packages are now thin wrappers over the shared core. - Individual package publish workflows (
publish-core.yml,publish-react.yml,publish-vanilla.yml) are manual-only fallbacks; thev*tag drives the full release suite automatically.
- Vanilla package manifest no longer ships with a
file:dependency onui-grid-core; uses a proper semver range so the published package resolves correctly.
- Column pinning across Angular and React — freeze columns left or right with CSS
position: sticky,enablePinning,pinnedLeft/pinnedRight, save/restore support, andgridApi.pinning.pinColumn()for programmatic control. - Angular/React header pin controls with explicit left/right actions, direct unpinning, and matching pinning labels in
GridLabelsanden-US.json. - React wrapper docs page mounted inside the Angular docs app so wrapper behavior can be verified directly in the published documentation.
- Expanded
--ui-grid-*CSS variable coverage for pinning, layout, controls, scrolling, and state styling. - Release packaging now includes the Rust/WASM core artifacts and CI provisions the Rust toolchains needed to build them.
- Framework wrappers now route more work through the Rust/WASM core where it improves performance without changing the public API.
- React docs/demo column widths and header layout were widened so header text and pin controls fit cleanly.
- Docs and Pages packaging were updated so the published site can bundle and serve the React wrapper consistently.
- Pin button behavior now matches between Angular and React, including left/right pin selection, pin-state icon behavior, and unpin flows.
- React pinned-column ordering, sticky offsets, and virtualized header/body alignment were corrected.
- GitHub Pages now mounts the React docs island correctly in production, including local alias/resolver fixes for
@ornery/ui-grid-reactand wrapper-owned mounting to avoid React runtime mismatches. - React virtualization tests and docs build type resolution were updated for the current wrapper output and import surface.
- Pin button visibility uses the same opt-out behavior as the rest of the grid feature flags, and React cell focusing now escapes selector values safely.
- Rust packaging, screenshots, and documentation updates for the crate workspace and WASM distribution.
- CI/build pipeline support for Rust toolchains and packaging the WASM core alongside the web deliverables.
- Framework wrappers now use as much of the Rust/WASM core as possible without regressing interactive performance.
- Bumped the Rust workspace crates from
0.1.1to0.1.2via the workspace-levelCargo.toml.
- egui: Row expansion click handling — expand/collapse icons in
ui-grid-eguino longer lose clicks to the full-cell overlay. - Clicking the expand icon now selects the row without focusing the cell or entering edit mode.
- Double-clicking the expand icon no longer triggers cell editing.
- Expand icon hit target increased from
16x16to24x24for easier interaction.
- Column Pinning — freeze columns left/right via CSS
position: sticky. NewenablePinningoption onGridOptionsandGridColumnDef,pinnedLeft/pinnedRightcolumn def properties, andgridApi.pinning.pinColumn()programmatic API. Pinning state is included in save/restore. Implemented for both Angular and React. - Header pin control for choosing left or right pinning, with direct unpin on already-pinned columns.
FEATURE_PINNINGcompile-time feature flag for tree-shaking.- Pinning labels (
pinColumn,pinLeft,pinRight,unpin) added toGridLabelsanden-US.json. - Pinned column styles with opaque backgrounds and edge shadow indicators.
- Expanded public
--ui-grid-*CSS variable coverage for layout, controls, scrolling, pinning, and state styling. See Theming.
- Pin button visibility now uses opt-out pattern (
enablePinning !== false) consistent with other features. - Reduced redundant
pinnedOffset()calls per cell in Angular and React renders. - Removed unnecessary
as anycasts inbuildInitialPinnedState. - Added
CSS.escape()to selector-based cell focusing in React to prevent selector injection from special characters in row IDs.
- egui: Row expansion click handling — Expand/collapse icons in
ui-grid-eguiwere not responding to clicks because the full-cell click overlay was stealing interaction from the icon button. Replaced the interactive icon button with a passive visual icon and moved click handling into the cell overlay with hit-testing against the icon rect. - Clicking the expand icon now selects the row without focusing the cell or entering edit mode.
- Double-clicking the expand icon no longer triggers cell editing.
- Expand icon hit target increased from 16×16 to 24×24 for easier clicking.
- Bumped all Rust crate versions from 0.1.0 to 0.1.1 (workspace-level
Cargo.toml).
- React wrapper package (
@ornery/ui-grid-react) withuseGridStatehook,useVirtualScrollhook, render-prop templating, and full CSS theming parity. Ships ESM + CJS +.d.tsvia tsup. Peer-depends on@ornery/ui-gridfor 100% core reuse. - Build-time feature flags (
grid.features.ts): 12 const boolean flags enabling bundler dead-code elimination. Custom build script (scripts/build-grid.mjs) with--featuresand--localeflags. - Internationalization:
GridLabelsinterface (26 keys) backed byi18n/en-US.json, overridable at runtime viaGridOptions.labelsand at build time via--locale. - Accessibility: ARIA grid pattern roles, SVG icons replacing text symbols,
aria-labelandsr-onlyscreen reader text on all interactive controls. Remediation plan atdocs/plans/a11y-remediation.md. - Pure-TS core extraction:
grid.core.pipeline.ts,grid.core.viewmodel.ts,grid.core.edit.ts,grid.core.display.ts,grid.core.sorting.ts,grid.core.filtering.ts,grid.core.grouping.ts,grid.core.pagination.ts,grid.core.tree.ts,grid.core.state.ts,grid.core.export.ts,grid.core.identity.ts,grid.core.row-state.ts,grid.core.infinite-scroll.ts, andgrid.core.types.tswith zero Angular dependencies. - Command and event layer:
ui-grid.commands.ts,ui-grid.events.ts,ui-grid.host.ts, andui-grid.state.tsseparating Angular orchestration from pure logic. - SSR harness component (
grid-ssr-harness.component.ts) and server-side rendering guards. - Documentation site with routed pages for Getting Started, Features, API Reference, Cell Editing, Expandable Rows, Tree View, Theming, i18n, Custom Builds, Web Component, Accessibility, and React.
- React CI workflow (
.github/workflows/ci-react.yml). - Library presets via
scripts/build-library-preset.mjswith separate public API entry points (public-api.core.ts,public-api.full.ts,public-api.minimal.ts,public-api.react.ts). - Combined
npm testscript that runs both Angular and React test suites.
- Slimmed
ui-grid.component.tsfrom monolithic class to thin rendering layer delegating to core modules and command functions. - Moved grid styles from
ui-grid.component.scssinto sharedgrid.core.styles.scsswith new.toggle-icon,.pagination-icon, and.group-disclosure-iconstyles. - Refactored demo app with routed layout, cleaner theme controls, and browser harness integration.
- Updated
README.mdfor new project structure and feature set.
- jsdom cross-realm
dispatchEventerror in CI: replacednew KeyboardEvent()withdocument.createEvent()+initEvent()+ property descriptors to work around realm-checking validation. - Keyboard event handling for cell editing in test environment (
bubbles: falseto prevent double-handling).
- Demo theming for default and wireframe modes with light and dark variants.
- A 100,000-row demo dataset to showcase virtualization and performance.
- GitHub Pages deployment and packaging automation for the demo and library builds.
- CI coverage upload to Coveralls and expanded library coverage around the grid, filters, and API wrappers.
- Updated the demo shell styling, removed the shark logo study, and improved header contrast.
- Raised the production style budget to accommodate the expanded demo UI.
- Narrowed CI and Pages triggers to project-relevant paths.
- Improved the demo harness and styles so theme variables pass cleanly through the shadow DOM layers.
- Hardened CSV export against spreadsheet formula injection.
- Blocked prototype-polluting dotted paths in nested property access and writes.
- Escaped filter text before regex construction, capped wildcard expansion, and sanitized restored state keys and filenames.
Initial public release of @ornery/ui-grid.
- Angular 21 standalone grid component
- Shadow DOM encapsulation with CSS custom properties and
parthooks - Sorting, filtering, grouping, and column moving
- Virtualized rendering with Angular CDK
- Inline cell editing with spreadsheet-style keyboard navigation
- Pagination controls and programmatic pagination API
- Infinite scroll hooks
- Auto-resize hooks
- CSV export
- Benchmark hook
- Transient save/restore state support
- Tree view and expandable-row support
- Web component build output
- Browser demo app for GitHub Pages
import { Component } from '@angular/core';
import { UiGridComponent } from '@ornery/ui-grid';
@Component({
selector: 'app-customers',
imports: [UiGridComponent],
template: `<app-ui-grid [options]="gridOptions" />`,
})
export class CustomersComponent {
gridOptions = {
id: 'customers',
title: 'Customers',
data: [
{ id: '1', name: 'Bob', status: 'Active', revenue: 1200 },
{ id: '2', name: 'Alice', status: 'Pilot', revenue: 900 },
],
columnDefs: [
{ name: 'name', displayName: 'Customer' },
{ name: 'status' },
{ name: 'revenue', align: 'end' },
],
enableSorting: true,
enableFiltering: true,
enableGrouping: true,
enableColumnMoving: true,
};
}import { Component, TemplateRef, viewChild } from '@angular/core';
import { GridCellTemplateContext, UiGridComponent } from '@ornery/ui-grid';
@Component({
selector: 'app-templated-grid',
imports: [UiGridComponent],
template: `
<ng-template #statusTemplate let-value>
<span class="status-pill">{{ value }}</span>
</ng-template>
<app-ui-grid [options]="gridOptions" />
`,
})
export class TemplatedGridComponent {
private readonly statusTemplate =
viewChild.required<TemplateRef<GridCellTemplateContext>>('statusTemplate');
readonly gridOptions = {
id: 'templated-customers',
data: [
{ id: '1', name: 'Bob', status: 'Active', revenue: 1200 },
{ id: '2', name: 'Alice', status: 'Pilot', revenue: 900 },
],
columnDefs: [
{ name: 'name', displayName: 'Customer' },
{ name: 'status', cellTemplate: this.statusTemplate() },
{ name: 'revenue', align: 'end' },
],
};
}import { Component, TemplateRef, viewChild } from '@angular/core';
import { GridExpandableTemplateContext, UiGridComponent } from '@ornery/ui-grid';
@Component({
selector: 'app-expandable-grid',
imports: [UiGridComponent],
template: `
<ng-template #detailTemplate let-row>
<div class="detail-card">
<strong>{{ row.name }}</strong>
<p>Status: {{ row.status }}</p>
</div>
</ng-template>
<app-ui-grid [options]="gridOptions" />
`,
})
export class ExpandableGridComponent {
private readonly detailTemplate =
viewChild.required<TemplateRef<GridExpandableTemplateContext>>('detailTemplate');
readonly gridOptions = {
id: 'expandable-customers',
data: [
{ id: '1', name: 'Bob', status: 'Active', revenue: 1200 },
{ id: '2', name: 'Alice', status: 'Pilot', revenue: 900 },
],
columnDefs: [
{ name: 'name', displayName: 'Customer' },
{ name: 'status' },
{ name: 'revenue', align: 'end' },
],
enableExpandable: true,
expandableRowHeight: 120,
expandableRowTemplate: this.detailTemplate(),
};
}<ui-grid-element></ui-grid-element>
<script type="module" src="./dist/ui-grid-element/main.js"></script>
<script>
const grid = document.querySelector('ui-grid-element');
grid.options = {
id: 'customers',
data: [
{ id: '1', name: 'Bob', status: 'Active', revenue: 1200 },
{ id: '2', name: 'Alice', status: 'Pilot', revenue: 900 },
],
columnDefs: [{ name: 'name' }, { name: 'status' }, { name: 'revenue' }],
};
</script>