Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ All notable Open Grid release changes should be documented here before a package
and rebaseline the Svelte-to-Vue document-node delta to 80 after measurement.
- Rebaseline the required Svelte UI gzip ceiling to 21,000 bytes after measuring
the component-renderer addition.
- Add explicit forward-only persisted preference migrations with guarded failure
handling and task-oriented customization, composition, and upgrade guides.
- Rebaseline the required primitives gzip ceiling from 14,500 to 15,000 bytes after
measuring the preference migration addition.
- Add generated public export and declaration contracts, including CSS entry points
and removal detection, plus a documented pre-1.0 compatibility and deprecation
policy enforced by CI and the protected publish workflow.
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ Open Grid aims to be:
- consistent across React, Vue, and Svelte
- localizable per grid instance through one typed cross-framework contract
- composable with typed framework-native cells, headers, toolbars, and query states
- explicit about persisted preference versions and product-owned migrations
- friendly to design systems through CSS variables and unstyled primitives
- suitable for server-side data workflows as a first-class use case

Expand Down
15 changes: 12 additions & 3 deletions api-contract/primitives/index.d.ts

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion api-contract/react-ui/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { CSSProperties, ReactNode } from 'react';
import { GridOptions, Grid, Row, Column, HeaderContext, CellContext, CellFillOptions, ClipboardPasteOptions, ClipboardPasteResult, ExportFile } from '@open-grid/core';
export { AccessorColumnOptions, AccessorFnColumnDef, AccessorKeyColumnDef, AnyColumnDef, CellContext, CellEditEvent, CellEditEventParams, CellEditHistoryAction, CellEditHistoryState, CellEditOption, CellEditParserContext, CellEditPhase, CellEditValidationContext, CellEditValidationResult, CellEditValidationState, CellEditingState, CellFillOptions, CellInteractionEvent, CellInteractionEventParams, CellRange, CellRangeSelectionState, ClipboardCellContext, ClipboardCopyOptions, ClipboardPasteCellContext, ClipboardPasteCommittedCell, ClipboardPasteOptions, ClipboardPasteResult, ClipboardPasteSkippedCell, ClipboardPasteSkippedReason, ClipboardPasteValidationError, ColumnDef, ColumnFiltersState, ColumnHelper, ColumnMovePosition, ColumnOrderState, ColumnPinningPosition, ColumnResizeEvent, ColumnResizeEventParams, ColumnResizePhase, DisplayColumnDef, ExpandedState, ExportFile, ExportFileOptions, FitColumnsToWidthOptions, GridCacheDiagnostics, GridCacheDiagnosticsEntry, GridCacheKey, GridOptions, GridState, GroupColumnDef, GroupingState, Header, HeaderContext, HeaderGroup, MoveFocusOptions, PaginationState, RowInteractionEvent, RowInteractionEventParams, RowSelectionCleanupScope, SortingState, createColumnHelper, fitColumnsToWidth } from '@open-grid/core';
import { ColumnVirtualizationPrimitiveOptions, GridLocalizationOverrides, GridDensity, RowVirtualizationPrimitiveOptions } from '@open-grid/primitives';
export { DEFAULT_GRID_LOCALIZATION, GRID_PREFERENCES_VERSION, GridDensity, GridLocalization, GridLocalizationOverrides, GridPreferences, GridPreferencesOptions, GridPreferencesState, GridPreferencesStorageEnvironmentLike, GridPreferencesStorageLike, createGridLocalization, createGridPreferences, getBrowserGridPreferencesStorage, parseGridPreferences, readGridPreferences, removeGridPreferences, serializeGridPreferences, writeGridPreferences } from '@open-grid/primitives';
export { DEFAULT_GRID_LOCALIZATION, GRID_PREFERENCES_VERSION, GridDensity, GridLocalization, GridLocalizationOverrides, GridPreferences, GridPreferencesMigration, GridPreferencesOptions, GridPreferencesParseOptions, GridPreferencesState, GridPreferencesStorageEnvironmentLike, GridPreferencesStorageLike, createGridLocalization, createGridPreferences, getBrowserGridPreferencesStorage, migrateGridPreferences, parseGridPreferences, readGridPreferences, removeGridPreferences, serializeGridPreferences, writeGridPreferences } from '@open-grid/primitives';

type RowVirtualizationOptions = RowVirtualizationPrimitiveOptions;
type ColumnVirtualizationOptions = ColumnVirtualizationPrimitiveOptions;
Expand Down
2 changes: 1 addition & 1 deletion api-contract/svelte-ui/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ExportFile } from '@open-grid/core';
export { AccessorColumnOptions, AccessorFnColumnDef, AccessorKey, AccessorKeyColumnDef, AnyColumnDef, CellContext, CellCoordinate, CellEditEvent, CellEditEventParams, CellEditHistoryAction, CellEditHistoryState, CellEditOption, CellEditParserContext, CellEditPhase, CellEditValidationContext, CellEditValidationResult, CellEditValidationState, CellEditingState, CellFillOptions, CellInteractionEvent, CellInteractionEventParams, CellRange, CellRangeSelectionState, ClipboardCellContext, ClipboardCopyOptions, ClipboardPasteCellContext, ClipboardPasteCommittedCell, ClipboardPasteOptions, ClipboardPasteResult, ClipboardPasteSkippedCell, ClipboardPasteSkippedReason, ClipboardPasteValidationError, Column, ColumnDef, ColumnFilter, ColumnFiltersState, ColumnHelper, ColumnId, ColumnMovePosition, ColumnOrderState, ColumnPinningPosition, ColumnPinningState, ColumnResizeEvent, ColumnResizeEventParams, ColumnResizePhase, ColumnSizingState, ColumnVisibilityState, DisplayColumnDef, ExpandedState, ExportFile, ExportFileOptions, FilterFn, FitColumnsToWidthOptions, Grid, GridCacheDiagnostics, GridCacheDiagnosticsEntry, GridCacheKey, GridOptions, GridState, GroupColumnDef, GroupingState, Header, HeaderContext, HeaderGroup, MoveFocusOptions, PaginationState, Row, RowId, RowInteractionEvent, RowInteractionEventParams, RowModel, RowSelectionCleanupScope, RowSelectionState, SortFn, SortingRule, SortingState, Updater } from '@open-grid/core';
export { ColumnVirtualizationOptions, default as DataGrid, DataGridErrorRenderContext, DataGridProps, DataGridRenderContext, GridReadyHandler, HeaderActionMenuActionItem, HeaderActionMenuContext, HeaderActionMenuCustomItem, HeaderActionMenuItem, HeaderActionMenuItems, HeaderActionMenuLabelItem, HeaderActionMenuSeparatorItem, RowVirtualizationOptions, SvelteDataGridRenderValue } from './DataGrid.svelte';
export { createColumnHelper, createGridStore, fitColumnsToWidth } from '@open-grid/svelte';
export { DEFAULT_GRID_LOCALIZATION, GRID_PREFERENCES_VERSION, GridDensity, GridLocalization, GridLocalizationOverrides, GridPreferences, GridPreferencesOptions, GridPreferencesState, GridPreferencesStorageEnvironmentLike, GridPreferencesStorageLike, createGridLocalization, createGridPreferences, getBrowserGridPreferencesStorage, parseGridPreferences, readGridPreferences, removeGridPreferences, serializeGridPreferences, writeGridPreferences } from '@open-grid/primitives';
export { DEFAULT_GRID_LOCALIZATION, GRID_PREFERENCES_VERSION, GridDensity, GridLocalization, GridLocalizationOverrides, GridPreferences, GridPreferencesMigration, GridPreferencesOptions, GridPreferencesParseOptions, GridPreferencesState, GridPreferencesStorageEnvironmentLike, GridPreferencesStorageLike, createGridLocalization, createGridPreferences, getBrowserGridPreferencesStorage, migrateGridPreferences, parseGridPreferences, readGridPreferences, removeGridPreferences, serializeGridPreferences, writeGridPreferences } from '@open-grid/primitives';

interface SvelteDataGridRenderer<TContext> {
type: "open-grid:svelte-renderer";
Expand Down
2 changes: 1 addition & 1 deletion api-contract/vue-ui/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { DefineComponent, PropType, VNodeChild, VNode } from 'vue';
import { GridOptions, Grid, Row, Column, HeaderContext, CellContext, CellFillOptions, ClipboardPasteResult, ClipboardPasteOptions, ExportFile } from '@open-grid/core';
export { AccessorColumnOptions, AccessorFnColumnDef, AccessorKey, AccessorKeyColumnDef, AnyColumnDef, CellContext, CellCoordinate, CellEditEvent, CellEditEventParams, CellEditHistoryAction, CellEditHistoryState, CellEditOption, CellEditParserContext, CellEditPhase, CellEditValidationContext, CellEditValidationResult, CellEditValidationState, CellEditingState, CellFillOptions, CellInteractionEvent, CellInteractionEventParams, CellRange, CellRangeSelectionState, ClipboardCellContext, ClipboardCopyOptions, ClipboardPasteCellContext, ClipboardPasteCommittedCell, ClipboardPasteOptions, ClipboardPasteResult, ClipboardPasteSkippedCell, ClipboardPasteSkippedReason, ClipboardPasteValidationError, Column, ColumnDef, ColumnFilter, ColumnFiltersState, ColumnHelper, ColumnId, ColumnMovePosition, ColumnOrderState, ColumnPinningPosition, ColumnPinningState, ColumnResizeEvent, ColumnResizeEventParams, ColumnResizePhase, ColumnSizingState, ColumnVisibilityState, DisplayColumnDef, ExpandedState, ExportFile, ExportFileOptions, FilterFn, FitColumnsToWidthOptions, Grid, GridCacheDiagnostics, GridCacheDiagnosticsEntry, GridCacheKey, GridOptions, GridState, GroupColumnDef, GroupingState, Header, HeaderContext, HeaderGroup, MoveFocusOptions, PaginationState, Row, RowId, RowInteractionEvent, RowInteractionEventParams, RowModel, RowSelectionCleanupScope, RowSelectionState, SortFn, SortingRule, SortingState, Updater, fitColumnsToWidth } from '@open-grid/core';
import { ColumnVirtualizationPrimitiveOptions, GridLocalizationOverrides, GridDensity, RowVirtualizationPrimitiveOptions, GridLocalization } from '@open-grid/primitives';
export { DEFAULT_GRID_LOCALIZATION, GRID_PREFERENCES_VERSION, GridDensity, GridLocalization, GridLocalizationOverrides, GridPreferences, GridPreferencesOptions, GridPreferencesState, GridPreferencesStorageEnvironmentLike, GridPreferencesStorageLike, createGridLocalization, createGridPreferences, getBrowserGridPreferencesStorage, parseGridPreferences, readGridPreferences, removeGridPreferences, serializeGridPreferences, writeGridPreferences } from '@open-grid/primitives';
export { DEFAULT_GRID_LOCALIZATION, GRID_PREFERENCES_VERSION, GridDensity, GridLocalization, GridLocalizationOverrides, GridPreferences, GridPreferencesMigration, GridPreferencesOptions, GridPreferencesParseOptions, GridPreferencesState, GridPreferencesStorageEnvironmentLike, GridPreferencesStorageLike, createGridLocalization, createGridPreferences, getBrowserGridPreferencesStorage, migrateGridPreferences, parseGridPreferences, readGridPreferences, removeGridPreferences, serializeGridPreferences, writeGridPreferences } from '@open-grid/primitives';
export { createColumnHelper, useGrid } from '@open-grid/vue';

interface DataGridRenderContext<TData> {
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/bundle-budgets.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"schemaVersion": 2,
"applications": [
{ "id": "package-core", "distDirectory": "packages/core/dist", "includeExtensions": [".js", ".css", ".svelte"], "limits": { "totalGzipBytes": 23000 } },
{ "id": "package-primitives", "distDirectory": "packages/primitives/dist", "includeExtensions": [".js", ".css", ".svelte"], "limits": { "totalGzipBytes": 14500 } },
{ "id": "package-primitives", "distDirectory": "packages/primitives/dist", "includeExtensions": [".js", ".css", ".svelte"], "limits": { "totalGzipBytes": 15000 } },
{ "id": "package-virtual", "distDirectory": "packages/virtual/dist", "includeExtensions": [".js", ".css", ".svelte"], "limits": { "totalGzipBytes": 3500 } },
{ "id": "package-theme", "distDirectory": "packages/theme/dist", "includeExtensions": [".js", ".css", ".svelte"], "limits": { "totalGzipBytes": 2000 } },
{ "id": "package-react", "distDirectory": "packages/react/dist", "includeExtensions": [".js", ".css", ".svelte"], "limits": { "totalGzipBytes": 768 } },
Expand Down
1 change: 1 addition & 0 deletions docs/.vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export default defineConfig({
{ text: "Localization", link: "/localization" },
{ text: "Custom rendering", link: "/custom-rendering" },
{ text: "Product composition", link: "/composition" },
{ text: "Persisted preferences", link: "/preferences" },
],
},
{
Expand Down
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ as the source-tree index when browsing the repository directly.
- [Localization](localization.md)
- [Custom rendering](custom-rendering.md)
- [Product composition](composition.md)
- [Persisted preferences](preferences.md)
- [Project overview](https://github.com/Goatshave/open-grid#readme)
- [React UI](react-ui.md)
- [Vue UI](vue-ui.md)
Expand Down
2 changes: 1 addition & 1 deletion docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ React, Vue, and Svelte UI packages can render optional searchable visibility con

React, Vue, and Svelte UI packages also share an optional density contract. `densityControl` owns only the compact/standard/comfortable segmented control, while `defaultDensity` supports uncontrolled initialization and `density` plus `onDensityChange` supports product-controlled preferences. Density remains renderer presentation state rather than core grid data state. Shared primitives own labels, pressed state, root markers, and row-height values; renderers clear measured virtual-row caches when the value changes so CSS sizing and virtual offsets remain aligned. Grids that do not opt into a density prop or control retain their existing CSS variables and virtualization estimates.

Persisted grid preferences remain product-owned rather than core state side effects. `@open-grid/primitives` exposes a versioned `GridPreferences` JSON contract and guarded browser-storage helpers, re-exported by each UI package. The contract includes only column visibility, sizing, order, pinning, and renderer density. Parsing filters unknown column ids through a product allowlist, removes duplicate ids and left/right pinning conflicts, rejects invalid sizes and unsupported schema versions, normalizes invalid density values, prevents a restored allowlist from hiding every column, and treats unavailable or denied browser storage as a no-op. Sorting, filters, grouping, expansion, pagination, selection, focus, editing, and cell ranges are deliberately excluded because they describe a working session rather than a durable product preference. Future shape changes require a new schema version and an explicit migration policy.
Persisted grid preferences remain product-owned rather than core state side effects. `@open-grid/primitives` exposes a versioned `GridPreferences` JSON contract and guarded browser-storage helpers, re-exported by each UI package. The contract includes only column visibility, sizing, order, pinning, and renderer density. Parsing filters unknown column ids through a product allowlist, removes duplicate ids and left/right pinning conflicts, rejects invalid sizes and unsupported schema versions, normalizes invalid density values, prevents a restored allowlist from hiding every column, and treats unavailable or denied browser storage as a no-op. Sorting, filters, grouping, expansion, pagination, selection, focus, editing, and cell ranges are deliberately excluded because they describe a working session rather than a durable product preference. Product-provided migrations are sequential and forward-only; missing, duplicate, malformed, future, or throwing steps reject the payload before current-schema normalization.

Column sizing updates are clamped in core against each column definition's `minSize` and `maxSize`. Renderers should call core sizing APIs instead of duplicating sizing constraints.

Expand Down
73 changes: 73 additions & 0 deletions docs/preferences.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Persisted preferences

Grid preferences are product-owned. Open Grid provides a narrow, versioned JSON
contract for column visibility, sizing, order, pinning, and density while excluding
transient sorting, filters, pagination, selection, focus, and edit state.

## Read and write safely

```ts
import {
getBrowserGridPreferencesStorage,
readGridPreferences,
writeGridPreferences,
} from "@open-grid/react-ui";

const storage = getBrowserGridPreferencesStorage();
const columnIds = ["invoice", "customer", "status", "amount"];
const saved = readGridPreferences(storage, "invoice-grid", {
validColumnIds: columnIds,
});

writeGridPreferences(storage, "invoice-grid", grid.getState(), density, {
validColumnIds: columnIds,
});
```

Use the equivalent re-exports from Vue UI, Svelte UI, or primitives. Storage access
failures return `null` or `false`; unknown columns and malformed values are removed
during normalization.

## Migrate an older product schema

Register every required forward step when reading an older payload:

```ts
import type { GridPreferencesMigration } from "@open-grid/react-ui";

const migrations: readonly GridPreferencesMigration[] = [{
fromVersion: 0,
toVersion: 1,
migrate: (legacy) => ({
version: 1,
density: legacy.compact === true ? "compact" : "standard",
state: {
columnVisibility: legacy.hiddenCustomer === true ? { customer: false } : {},
columnSizing: {},
columnOrder: [],
columnPinning: { left: [], right: [] },
},
}),
}];

const saved = readGridPreferences(storage, "invoice-grid", {
validColumnIds: columnIds,
migrations,
});
```

Migrations are explicit, sequential, and forward-only. A missing step, duplicate
`fromVersion`, invalid target version, malformed result, future schema, or thrown
error rejects the payload safely. A migration must return an object whose `version`
equals its declared `toVersion`.

`migrateGridPreferences(value, migrations)` is available when the product needs to
inspect a parsed object before normal Open Grid normalization. Prefer
`parseGridPreferences` or `readGridPreferences` for ordinary use.

## Upgrade policy

Keep the storage key stable when old values should migrate. Change the key when the
old preference meaning is incompatible and should reset. Always pass the current
leaf-column allowlist, and retain migration steps while supported old application
versions may still have written their payloads.
3 changes: 2 additions & 1 deletion docs/react-ui.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ Built-in text and accessibility labels use the shared typed
[localization contract](localization.md).

Product components and commands use the shared [custom rendering](custom-rendering.md)
and [composition](composition.md) contracts.
and [composition](composition.md) contracts. Durable column and density state uses
the guarded [persisted preferences](preferences.md) contract.

## Install

Expand Down
2 changes: 1 addition & 1 deletion docs/roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ regression safety, and dependable releases.
- [x] Expand typed custom renderer parity across React, Vue, and Svelte.
- [x] Add product toolbar and loading, error, and empty-state composition points without
coupling product UI to the grid core.
- Version persisted preference migrations and document upgrade behavior.
- [x] Version persisted preference migrations and document upgrade behavior.
- [x] Expand task-oriented API reference and integration examples from user reports.
- Continue keyboard, screen-reader, forced-colors, mobile reflow, server-data, and
export improvements where real integration evidence identifies a gap.
Expand Down
3 changes: 2 additions & 1 deletion docs/svelte-ui.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ Built-in text and accessibility labels use the shared typed
[localization contract](localization.md).

Product components and commands use the shared [custom rendering](custom-rendering.md)
and [composition](composition.md) contracts.
and [composition](composition.md) contracts. Durable column and density state uses
the guarded [persisted preferences](preferences.md) contract.

## Install

Expand Down
3 changes: 2 additions & 1 deletion docs/vue-ui.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ Built-in text and accessibility labels use the shared typed
[localization contract](localization.md).

Product components and commands use the shared [custom rendering](custom-rendering.md)
and [composition](composition.md) contracts.
and [composition](composition.md) contracts. Durable column and density state uses
the guarded [persisted preferences](preferences.md) contract.

## Install

Expand Down
Loading
Loading