From 9da7362f232ecb27931d5ff9c9e137e6fcdf306b Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Mon, 13 Jul 2026 10:29:33 +0200 Subject: [PATCH 1/9] Updated linter, linted nextjs demo. --- eslint.config.js | 9 ++++-- .../grid-lite/minimal-nextjs/app/page.tsx | 8 +++-- package.json | 2 +- pnpm-lock.yaml | 31 +++++++++++++++++++ 4 files changed, 45 insertions(+), 5 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index d7605c1..5e17dfa 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -6,7 +6,12 @@ import globals from 'globals'; export default defineConfig( { - ignores: ['**/dist/**', '**/build/**'], + ignores: [ + '**/dist/**', + '**/build/**', + '**/.next/**', + '**/node_modules/**' + ], }, eslint.configs.recommended, tseslint.configs.recommended, @@ -34,7 +39,7 @@ export default defineConfig( }, }, { - files: ['scripts/**/*.js'], + files: ['scripts/**/*.js', '**/next.config.js'], languageOptions: { globals: { ...globals.node, diff --git a/examples/grid-lite/minimal-nextjs/app/page.tsx b/examples/grid-lite/minimal-nextjs/app/page.tsx index 1d82cc4..5d5de97 100644 --- a/examples/grid-lite/minimal-nextjs/app/page.tsx +++ b/examples/grid-lite/minimal-nextjs/app/page.tsx @@ -48,8 +48,12 @@ export default function Home() { }; return ( - <> - + <> + ); diff --git a/package.json b/package.json index 0aaabc9..cdde1fa 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "test:all": "pnpm test && pnpm test:e2e", "check": "pnpm lint && pnpm test", "build": "pnpm -r --filter './packages/*' run build", - "lint": "pnpm -r --filter './packages/*' run lint", + "lint": "eslint packages examples scripts --ext .ts,.tsx,.js", "clean": "pnpm -r --filter './{packages,examples}/*' run clean && rimraf node_modules", "release:preflight": "pnpm check && pnpm build", "release:prepare": "node scripts/release.js", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 648ccc3..822c331 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -144,6 +144,37 @@ importers: specifier: ^5.0.0 version: 5.4.21(@types/node@20.19.26) + examples/grid-pro/components-react: + dependencies: + '@highcharts/grid-pro': + specifier: '>=3.0.0' + version: 3.0.0 + '@highcharts/grid-pro-react': + specifier: workspace:* + version: link:../../../packages/grid-pro-react + react: + specifier: '>=18' + version: 19.2.1 + react-dom: + specifier: '>=18' + version: 19.2.1(react@19.2.1) + devDependencies: + '@types/react': + specifier: '>=18' + version: 19.2.7 + '@types/react-dom': + specifier: '>=18' + version: 19.2.3(@types/react@19.2.7) + '@vitejs/plugin-react': + specifier: ^4.2.0 + version: 4.7.0(vite@5.4.21(@types/node@20.19.26)) + typescript: + specifier: ^5.0.0 + version: 5.9.3 + vite: + specifier: ^5.0.0 + version: 5.4.21(@types/node@20.19.26) + examples/grid-pro/minimal-nextjs: dependencies: '@highcharts/grid-pro': From 5979dd92f59e368933b1683eeeb01da1112dcc96 Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Mon, 13 Jul 2026 13:47:07 +0200 Subject: [PATCH 2/9] Linted. --- .../grid-lite/components-react/src/App.tsx | 11 +++++---- examples/grid-pro/minimal-nextjs/app/page.tsx | 7 +++++- packages/grid-lite-react/src/Grid.tsx | 23 +++++++------------ 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/examples/grid-lite/components-react/src/App.tsx b/examples/grid-lite/components-react/src/App.tsx index f24838d..9a2c3eb 100644 --- a/examples/grid-lite/components-react/src/App.tsx +++ b/examples/grid-lite/components-react/src/App.tsx @@ -1,12 +1,15 @@ -import { useState, useRef } from 'react'; +import { + useState, + // useRef +} from 'react'; import { type GridInstance, - type GridRefHandle, + // type GridRefHandle, type GridOptions, Grid, Caption, Data, - DataTable, + // DataTable, ColumnDefaults, Column, Description, @@ -15,7 +18,7 @@ import { } from '@highcharts/grid-lite-react'; function App() { - const grid = useRef | null>(null); + // const grid = useRef | null>(null); // ==== OPTIONS ==== // const [options] = useState({ diff --git a/examples/grid-pro/minimal-nextjs/app/page.tsx b/examples/grid-pro/minimal-nextjs/app/page.tsx index 0397a91..28cbb2b 100644 --- a/examples/grid-pro/minimal-nextjs/app/page.tsx +++ b/examples/grid-pro/minimal-nextjs/app/page.tsx @@ -65,7 +65,12 @@ export default function Home() { return ( <> - + ); diff --git a/packages/grid-lite-react/src/Grid.tsx b/packages/grid-lite-react/src/Grid.tsx index 44b5643..4f97043 100644 --- a/packages/grid-lite-react/src/Grid.tsx +++ b/packages/grid-lite-react/src/Grid.tsx @@ -7,37 +7,30 @@ * */ -import { useMemo } from 'react'; import { BaseGrid, - GridProps, - getChildProps + useDeclarativeGridOptions } from '@highcharts/grid-shared-react'; import { merge } from '@highcharts/grid-lite/es-modules/Shared/Utilities.js'; import Grid from '@highcharts/grid-lite/es-modules/masters/grid-lite.src'; import '@highcharts/grid-lite/css/grid-lite.css'; import type { Options } from '@highcharts/grid-lite/es-modules/Grid/Core/Options'; +import type { GridProps } from '@highcharts/grid-shared-react'; export default function GridLite(props: GridProps) { - const { gridRef, children, options, ...gridProps } = props; - const childOptions = useMemo(() => getChildProps(children), [children]); - const columnKey = useMemo(() => { - const columns = childOptions.columns as - Array<{ id?: string }> | undefined; - - return columns?.map((column) => column.id).join('\0') ?? ''; - }, [childOptions]); - const gridOptions = useMemo( - () => merge(childOptions, options ?? {}) as Options, - [childOptions, options] + const { gridRef, children, options, callback } = props; + const { gridOptions, columnKey } = useDeclarativeGridOptions( + children, + options, + (childOptions, opts) => merge(childOptions, opts ?? {}) as Options ); return ( ); From e39a0030eb914525e0a4d1c05b225acb36e3a406 Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Mon, 13 Jul 2026 15:00:08 +0200 Subject: [PATCH 3/9] Added grid-pro demo. --- examples/grid-pro/components-react/index.html | 12 +++ .../grid-pro/components-react/package.json | 25 +++++ .../grid-pro/components-react/src/App.tsx | 98 +++++++++++++++++++ .../grid-pro/components-react/src/index.css | 26 +++++ .../grid-pro/components-react/src/main.tsx | 10 ++ .../grid-pro/components-react/tsconfig.json | 27 +++++ .../components-react/tsconfig.node.json | 11 +++ .../grid-pro/components-react/vite.config.ts | 29 ++++++ examples/grid-pro/minimal-react/src/App.tsx | 7 +- 9 files changed, 244 insertions(+), 1 deletion(-) create mode 100644 examples/grid-pro/components-react/index.html create mode 100644 examples/grid-pro/components-react/package.json create mode 100644 examples/grid-pro/components-react/src/App.tsx create mode 100644 examples/grid-pro/components-react/src/index.css create mode 100644 examples/grid-pro/components-react/src/main.tsx create mode 100644 examples/grid-pro/components-react/tsconfig.json create mode 100644 examples/grid-pro/components-react/tsconfig.node.json create mode 100644 examples/grid-pro/components-react/vite.config.ts diff --git a/examples/grid-pro/components-react/index.html b/examples/grid-pro/components-react/index.html new file mode 100644 index 0000000..b07a6f3 --- /dev/null +++ b/examples/grid-pro/components-react/index.html @@ -0,0 +1,12 @@ + + + + + + Highcharts Grid Pro - React Example + + +
+ + + diff --git a/examples/grid-pro/components-react/package.json b/examples/grid-pro/components-react/package.json new file mode 100644 index 0000000..e70810f --- /dev/null +++ b/examples/grid-pro/components-react/package.json @@ -0,0 +1,25 @@ +{ + "name": "grid-pro-components-react", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview", + "clean": "rimraf dist node_modules" + }, + "dependencies": { + "@highcharts/grid-pro": ">=3.0.0", + "@highcharts/grid-pro-react": "workspace:*", + "react": ">=18", + "react-dom": ">=18" + }, + "devDependencies": { + "@types/react": ">=18", + "@types/react-dom": ">=18", + "@vitejs/plugin-react": "^4.2.0", + "typescript": "^5.0.0", + "vite": "^5.0.0" + } +} diff --git a/examples/grid-pro/components-react/src/App.tsx b/examples/grid-pro/components-react/src/App.tsx new file mode 100644 index 0000000..2d7f839 --- /dev/null +++ b/examples/grid-pro/components-react/src/App.tsx @@ -0,0 +1,98 @@ +import { useState } from 'react'; +import { + type GridInstance, + type GridOptions, + Grid, + Caption, + Data, + ColumnDefaults, + Column, + Description, + Pagination +} from '@highcharts/grid-pro-react'; + +const GRID_KEY = 'AAAA-BBBB-CCCC-DDDD-EEEE-FFFF'; + +function App() { + const [dataSource, setDataSource] = useState({ + name: ['Alice', 'Bob', 'Charlie', 'David', 'Eve'], + age: [23, 34, 45, 56, 67], + city: ['New York', 'Oslo', 'Paris', 'Tokyo', 'London'], + salary: [50000, 60000, 70000, 80000, 90000] + }); + + const onButtonClick = () => { + setDataSource({ + name: ['John', 'Jane', 'Jim', 'Jill', 'Jack'], + age: [30, 25, 35, 40, 45], + city: ['New York', 'Los Angeles', 'Chicago', 'Houston', 'Miami'], + salary: [40000, 35000, 45000, 50000, 55000] + }); + }; + + const onGridCallback = (grid: GridInstance) => { + console.info('(callback) grid:', grid); + }; + + return ( + <> + + + + Grid Pro Components + Declarative API with gridKey and event props + + + + + + +
+ +
+ + ); +} + +export default App; diff --git a/examples/grid-pro/components-react/src/index.css b/examples/grid-pro/components-react/src/index.css new file mode 100644 index 0000000..16edde1 --- /dev/null +++ b/examples/grid-pro/components-react/src/index.css @@ -0,0 +1,26 @@ +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +#root { + width: 100%; + min-height: 100vh; + padding: 20px; +} + +#controls { + margin-top: 20px; + display: flex; + gap: 10px; +} + +@media (prefers-color-scheme: dark) { + body { + background-color: #121212; + color: #ffffff; + } +} diff --git a/examples/grid-pro/components-react/src/main.tsx b/examples/grid-pro/components-react/src/main.tsx new file mode 100644 index 0000000..2339d59 --- /dev/null +++ b/examples/grid-pro/components-react/src/main.tsx @@ -0,0 +1,10 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import App from './App'; +import './index.css'; + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + +); diff --git a/examples/grid-pro/components-react/tsconfig.json b/examples/grid-pro/components-react/tsconfig.json new file mode 100644 index 0000000..561b165 --- /dev/null +++ b/examples/grid-pro/components-react/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "ES2020", + "lib": [ + "DOM", + "ES2016", + "ES2017.Object" + ], + "jsx": "react-jsx", + "module": "ES6", + "moduleResolution": "node", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitThis": true, + "noFallthroughCasesInSwitch": true, + "skipDefaultLibCheck": true, + "skipLibCheck": true, + "ignoreDeprecations": "5.0", + "allowSyntheticDefaultImports": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/examples/grid-pro/components-react/tsconfig.node.json b/examples/grid-pro/components-react/tsconfig.node.json new file mode 100644 index 0000000..b940375 --- /dev/null +++ b/examples/grid-pro/components-react/tsconfig.node.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "types": ["node"] + }, + "include": ["vite.config.ts"] +} diff --git a/examples/grid-pro/components-react/vite.config.ts b/examples/grid-pro/components-react/vite.config.ts new file mode 100644 index 0000000..524f9cd --- /dev/null +++ b/examples/grid-pro/components-react/vite.config.ts @@ -0,0 +1,29 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import { resolve, dirname } from 'path'; +import { fileURLToPath } from 'url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +export default defineConfig({ + plugins: [react()], + resolve: { + alias: [ + { + find: '@highcharts/grid-pro-react', + replacement: resolve(__dirname, '../../../packages/grid-pro-react/src/index.ts') + }, + { + find: '@highcharts/grid-shared-react', + replacement: resolve(__dirname, '../../../packages/grid-shared-react/src/index.ts') + }, + { + find: /^@highcharts\/grid-pro(\/.*)?$/, + replacement: resolve(__dirname, 'node_modules/@highcharts/grid-pro$1') + } + ] + }, + server: { + port: 3002 + } +}); diff --git a/examples/grid-pro/minimal-react/src/App.tsx b/examples/grid-pro/minimal-react/src/App.tsx index 8abbbe0..0e7b1aa 100644 --- a/examples/grid-pro/minimal-react/src/App.tsx +++ b/examples/grid-pro/minimal-react/src/App.tsx @@ -55,7 +55,12 @@ function App() { return ( <> - + ); From 01835c537d19ac13efa6724971d788c7c3ae56ee Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Tue, 14 Jul 2026 14:54:49 +0200 Subject: [PATCH 4/9] Refactored shared utils and hooks. --- .../src/hooks/useDeclarativeGridOptions.ts | 60 ++++++++++++++++++ packages/grid-shared-react/src/index.ts | 7 +++ .../src/utils/getChildProps.ts | 62 +++++++++---------- .../grid-shared-react/src/utils/isObject.ts | 14 +++++ .../mappers/pagination/paginationOptions.ts | 9 ++- .../src/utils/normalizeChildOptions.ts | 48 ++++++++++++++ 6 files changed, 161 insertions(+), 39 deletions(-) create mode 100644 packages/grid-shared-react/src/hooks/useDeclarativeGridOptions.ts create mode 100644 packages/grid-shared-react/src/utils/isObject.ts create mode 100644 packages/grid-shared-react/src/utils/normalizeChildOptions.ts diff --git a/packages/grid-shared-react/src/hooks/useDeclarativeGridOptions.ts b/packages/grid-shared-react/src/hooks/useDeclarativeGridOptions.ts new file mode 100644 index 0000000..be657fe --- /dev/null +++ b/packages/grid-shared-react/src/hooks/useDeclarativeGridOptions.ts @@ -0,0 +1,60 @@ +/** + * Grid React integration. + * Copyright (c) 2025, Highsoft + * + * A valid license is required for using this software. + * See highcharts.com/license + * + */ + +import { useMemo, type ReactNode } from 'react'; +import { getChildProps } from '../utils/getChildProps'; + +export interface OptionsBuildFn { + (childOptions: Record, options?: TOptions): TOptions; +} + +export type DeclarativeGridOptionsState = { + gridOptions: TOptions; + columnKey: string; +}; + +/** + * Builds a React key that remounts the grid when declarative column ids change. + */ +function getColumnKey(childOptions: Record): string { + const columns = childOptions.columns as Array<{ id?: string }> | undefined; + + return columns?.map((column) => column.id).join('\0') ?? ''; +} + +export interface UseDeclarativeGridOptionsFn { + ( + children: ReactNode | undefined, + options: T | undefined, + build: OptionsBuildFn, + buildDeps?: unknown[] + ): DeclarativeGridOptionsState; +} + +export const useDeclarativeGridOptions: UseDeclarativeGridOptionsFn = ( + children, + options, + build, + buildDeps = [] +) => { + const childOptions = useMemo( + () => (children != null ? getChildProps(children) : {}), + [children] + ); + const columnKey = useMemo( + () => getColumnKey(childOptions), + [childOptions] + ); + const gridOptions = useMemo( + () => build(childOptions, options), + [childOptions, options, ...buildDeps] + ); + + return { gridOptions, columnKey }; +}; diff --git a/packages/grid-shared-react/src/index.ts b/packages/grid-shared-react/src/index.ts index 4cfc3ca..a9c907e 100644 --- a/packages/grid-shared-react/src/index.ts +++ b/packages/grid-shared-react/src/index.ts @@ -22,6 +22,13 @@ export { Header } from './components/options'; export { getChildProps } from './utils/getChildProps'; +/** + * Monorepo-internal utilities. Not part of the public consumer API for + * grid-lite-react / grid-pro-react packages. + */ +export { isObject } from './utils/isObject'; +export { normalizeChildOptions } from './utils/normalizeChildOptions'; +export { useDeclarativeGridOptions } from './hooks/useDeclarativeGridOptions'; export type { CaptionProps, DescriptionProps, diff --git a/packages/grid-shared-react/src/utils/getChildProps.ts b/packages/grid-shared-react/src/utils/getChildProps.ts index bc1c5a6..aca622a 100644 --- a/packages/grid-shared-react/src/utils/getChildProps.ts +++ b/packages/grid-shared-react/src/utils/getChildProps.ts @@ -9,8 +9,24 @@ import { Fragment, isValidElement, ReactElement, ReactNode } from 'react'; import type { BaseGridOptionsComponent, BaseGridOptions } from '../components/BaseGridOptions'; -import { normalizeColumnOptions } from './mappers/column'; -import { normalizePaginationOptions } from './mappers/pagination'; +import { isObject } from './isObject'; + +function flattenChildren(childNodes: ReactNode): ReactNode[] { + if (childNodes == null || childNodes === false) { + return []; + } + + if (Array.isArray(childNodes)) { + return childNodes.flatMap((child) => flattenChildren(child)); + } + + if (isValidElement(childNodes) && childNodes.type === Fragment) { + const fragmentProps = childNodes.props as { children?: ReactNode }; + return flattenChildren(fragmentProps.children); + } + + return [childNodes]; +} function objInsert( obj: Record, @@ -41,10 +57,6 @@ function objInsert( return obj; } -function isObject(value: unknown): value is Record { - return typeof value === 'object' && value !== null && !Array.isArray(value); -} - function isReactElement(value: unknown): value is ReactElement { return isValidElement(value); } @@ -79,23 +91,6 @@ function renderChildren(children: ReactNode): string { return ''; } -function flattenChildren(childNodes: ReactNode): ReactNode[] { - if (childNodes == null || childNodes === false) { - return []; - } - - if (Array.isArray(childNodes)) { - return childNodes.flatMap((child) => flattenChildren(child)); - } - - if (isReactElement(childNodes) && childNodes.type === Fragment) { - const fragmentProps = childNodes.props as { children?: ReactNode }; - return flattenChildren(fragmentProps.children); - } - - return [childNodes]; -} - function getEffectiveMeta( component: BaseGridOptionsComponent, parentMeta?: BaseGridOptions @@ -127,14 +122,12 @@ function parseColumnElement(child: ReactElement): Record { void children; void id; - const options = normalizeColumnOptions(props); - // columnId selects the column; Core expects the same value as `id`. if (columnId !== void 0) { - options.id = columnId; + props.id = columnId; } - return options; + return props; } function pushColumn( @@ -209,7 +202,7 @@ export function getChildProps(children: ReactNode): Record { const { children: childChildren, ...props } = childProps; if (meta.gridOption === 'columnDefaults') { - optionsFromChildren.columnDefaults = normalizeColumnOptions(props); + optionsFromChildren.columnDefaults = props; return; } @@ -219,12 +212,13 @@ export function getChildProps(children: ReactNode): Record { } if (meta.gridOption === 'pagination') { - const pagination = normalizePaginationOptions(props); - pagination.position = isTopPaginationChild( - child, - resolvedChildren - ) ? 'top' : 'bottom'; - optionsFromChildren.pagination = pagination; + optionsFromChildren.pagination = { + ...props, + position: isTopPaginationChild( + child, + resolvedChildren + ) ? 'top' : 'bottom' + }; return; } diff --git a/packages/grid-shared-react/src/utils/isObject.ts b/packages/grid-shared-react/src/utils/isObject.ts new file mode 100644 index 0000000..636cdf0 --- /dev/null +++ b/packages/grid-shared-react/src/utils/isObject.ts @@ -0,0 +1,14 @@ +/** + * Grid React integration. + * Copyright (c) 2025, Highsoft + * + * A valid license is required for using this software. + * See highcharts.com/license + * + */ + +export function isObject( + value: unknown +): value is Record { + return typeof value === 'object' && value !== null && !Array.isArray(value); +} diff --git a/packages/grid-shared-react/src/utils/mappers/pagination/paginationOptions.ts b/packages/grid-shared-react/src/utils/mappers/pagination/paginationOptions.ts index 0db2d55..03c9f03 100644 --- a/packages/grid-shared-react/src/utils/mappers/pagination/paginationOptions.ts +++ b/packages/grid-shared-react/src/utils/mappers/pagination/paginationOptions.ts @@ -7,8 +7,6 @@ * */ -import type { PaginationProps } from '../../../components/options/pagination/paginationProps'; - export function normalizePaginationOptions( props: Record ): Record { @@ -23,8 +21,9 @@ export function normalizePaginationOptions( enabled, page, pageSize, - align - } = props as PaginationProps; + align, + ...rest + } = props; const result: Record = { enabled: enabled ?? true @@ -80,5 +79,5 @@ export function normalizePaginationOptions( result.controls = controls; } - return result; + return { ...result, ...rest }; } diff --git a/packages/grid-shared-react/src/utils/normalizeChildOptions.ts b/packages/grid-shared-react/src/utils/normalizeChildOptions.ts new file mode 100644 index 0000000..ef45c01 --- /dev/null +++ b/packages/grid-shared-react/src/utils/normalizeChildOptions.ts @@ -0,0 +1,48 @@ +/** + * Grid React integration. + * Copyright (c) 2025, Highsoft + * + * A valid license is required for using this software. + * See highcharts.com/license + * + */ + +import { isObject } from './isObject'; +import { normalizeColumnOptions } from './mappers/column'; +import { normalizePaginationOptions } from './mappers/pagination'; + +/** + * Maps raw declarative child options onto nested Grid option paths. + * Used by lite and pro build pipelines after `getChildProps`. + */ +export function normalizeChildOptions( + raw: Record +): Record { + const result = { ...raw }; + + if (isObject(result.columnDefaults)) { + result.columnDefaults = normalizeColumnOptions({ + ...result.columnDefaults + }); + } + + if (Array.isArray(result.columns)) { + result.columns = result.columns.map((column) => ( + isObject(column) ? normalizeColumnOptions({ ...column }) : column + )); + } + + if (isObject(result.pagination)) { + const pagination = { ...result.pagination }; + const { position, ...props } = pagination; + const normalized = normalizePaginationOptions(props); + + if (position !== void 0) { + normalized.position = position; + } + + result.pagination = normalized; + } + + return result; +} From 9df999f47e451868af59ebd266ae3807cad0c034 Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Wed, 15 Jul 2026 13:13:08 +0200 Subject: [PATCH 5/9] Added mappers and builders. --- .../src/utils/buildGridOptions.ts | 25 ++++ .../src/utils/buildGridOptions.ts | 42 +++++++ .../src/utils/mapEventsProps.ts | 81 +++++++++++++ .../src/utils/mappers/column/columnOptions.ts | 108 +++++++++++++++++ .../src/utils/mappers/column/index.ts | 21 ++++ .../src/utils/mappers/grid/gridOptions.ts | 110 ++++++++++++++++++ .../src/utils/mappers/grid/index.ts | 23 ++++ .../src/utils/mappers/pagination/index.ts | 18 +++ .../mappers/pagination/paginationOptions.ts | 72 ++++++++++++ 9 files changed, 500 insertions(+) create mode 100644 packages/grid-lite-react/src/utils/buildGridOptions.ts create mode 100644 packages/grid-pro-react/src/utils/buildGridOptions.ts create mode 100644 packages/grid-pro-react/src/utils/mapEventsProps.ts create mode 100644 packages/grid-pro-react/src/utils/mappers/column/columnOptions.ts create mode 100644 packages/grid-pro-react/src/utils/mappers/column/index.ts create mode 100644 packages/grid-pro-react/src/utils/mappers/grid/gridOptions.ts create mode 100644 packages/grid-pro-react/src/utils/mappers/grid/index.ts create mode 100644 packages/grid-pro-react/src/utils/mappers/pagination/index.ts create mode 100644 packages/grid-pro-react/src/utils/mappers/pagination/paginationOptions.ts diff --git a/packages/grid-lite-react/src/utils/buildGridOptions.ts b/packages/grid-lite-react/src/utils/buildGridOptions.ts new file mode 100644 index 0000000..548f8b8 --- /dev/null +++ b/packages/grid-lite-react/src/utils/buildGridOptions.ts @@ -0,0 +1,25 @@ +/** + * Grid React integration. + * Copyright (c) 2025, Highsoft + * + * A valid license is required for using this software. + * See highcharts.com/license + * + */ + +import { normalizeChildOptions } from '@highcharts/grid-shared-react'; +import { merge } from '@highcharts/grid-lite/es-modules/Shared/Utilities.js'; +import type { Options } from '@highcharts/grid-lite/es-modules/Grid/Core/Options'; + +/** + * Builds final Grid Lite options from raw declarative child options. + */ +export function buildGridOptions( + childOptions: Record, + options?: Options +): Options { + return merge( + normalizeChildOptions(childOptions), + options ?? {} + ) as Options; +} diff --git a/packages/grid-pro-react/src/utils/buildGridOptions.ts b/packages/grid-pro-react/src/utils/buildGridOptions.ts new file mode 100644 index 0000000..4cef6ab --- /dev/null +++ b/packages/grid-pro-react/src/utils/buildGridOptions.ts @@ -0,0 +1,42 @@ +/** + * Grid React integration. + * Copyright (c) 2025, Highsoft + * + * A valid license is required for using this software. + * See highcharts.com/license + * + */ + +import { normalizeChildOptions } from '@highcharts/grid-shared-react'; +import { merge } from '@highcharts/grid-pro/es-modules/Shared/Utilities.js'; +import { mergeColumnEventProps } from './mappers/column'; +import { mergePaginationEventProps } from './mappers/pagination'; +import { + normalizeGridEventProps, + type GridProOptions, + type GridProProps +} from './mappers/grid/gridOptions'; + +/** + * Builds final Grid Pro options from raw declarative child options. + */ +export function buildGridOptions( + gridKey: string, + childOptions: Record, + options: GridProOptions | undefined, + props: GridProProps +): GridProOptions { + const declarativeOptions = mergePaginationEventProps( + mergeColumnEventProps(normalizeChildOptions(childOptions)) + ); + const result = merge( + true, + {}, + merge(declarativeOptions, options ?? {}), + normalizeGridEventProps(props) + ) as GridProOptions; + + result.gridKey = gridKey; + + return result; +} diff --git a/packages/grid-pro-react/src/utils/mapEventsProps.ts b/packages/grid-pro-react/src/utils/mapEventsProps.ts new file mode 100644 index 0000000..0dcab05 --- /dev/null +++ b/packages/grid-pro-react/src/utils/mapEventsProps.ts @@ -0,0 +1,81 @@ +/** + * Grid React integration. + * Copyright (c) 2025, Highsoft + * + * A valid license is required for using this software. + * See highcharts.com/license + * + */ + +import { isObject } from '@highcharts/grid-shared-react'; + +function setNestedValue( + target: Record, + path: readonly string[], + value: unknown +): void { + if (path.length === 0) { + return; + } + + let current = target; + + for (let i = 0; i < path.length - 1; i++) { + const key = path[i]; + + if (key === void 0) { + continue; + } + + const next = current[key]; + + if (!isObject(next)) { + current[key] = {}; + } else { + current[key] = { ...next }; + } + + current = current[key] as Record; + } + + const lastKey = path.at(-1); + + if (lastKey !== void 0) { + current[lastKey] = value; + } +} + +/** + * Maps flat event props onto nested option paths. + * + * When `source` is omitted, handlers are read from `target` and flat props + * are removed after mapping. When `source` is provided, handlers are copied + * onto `target` without mutating `source`. + * + * @example + * mapEventsProps(column, { + * onCellClick: ['cells', 'events', 'click'] + * }); + */ +export function mapEventsProps( + target: Record, + aliases: Record, + source?: Record +): void { + const props = source ?? target; + const removeFlatProps = source === undefined; + + for (const [propName, path] of Object.entries(aliases)) { + const handler = props[propName]; + + if (typeof handler !== 'function') { + continue; + } + + if (removeFlatProps) { + delete target[propName]; + } + + setNestedValue(target, path, handler); + } +} diff --git a/packages/grid-pro-react/src/utils/mappers/column/columnOptions.ts b/packages/grid-pro-react/src/utils/mappers/column/columnOptions.ts new file mode 100644 index 0000000..a1e3bce --- /dev/null +++ b/packages/grid-pro-react/src/utils/mappers/column/columnOptions.ts @@ -0,0 +1,108 @@ +/** + * Grid React integration. + * Copyright (c) 2025, Highsoft + * + * A valid license is required for using this software. + * See highcharts.com/license + * + */ + +import { isObject } from '@highcharts/grid-shared-react'; +import { mapEventsProps } from '../../mapEventsProps'; +import type { ColumnProps } from '@highcharts/grid-shared-react'; +import type { + CellEventCallback, + ColumnEventCallback +} from '@highcharts/grid-pro/es-modules/Grid/Pro/GridEvents.js'; + +/** + * Column-level event props mapped to `columns[].events`. + */ +export interface ColumnLevelEventProps { + onAfterResize?: ColumnEventCallback; + onBeforeSort?: ColumnEventCallback; + onAfterSort?: ColumnEventCallback; + onBeforeFilter?: ColumnEventCallback; + onAfterFilter?: ColumnEventCallback; +} + +/** + * Cell-level event props mapped to `columns[].cells.events`. + */ +export interface CellLevelEventProps { + onCellClick?: CellEventCallback; + onCellDblClick?: CellEventCallback; + onCellMouseOver?: CellEventCallback; + onCellMouseOut?: CellEventCallback; + onCellAfterRender?: CellEventCallback; + onCellAfterEdit?: CellEventCallback; +} + +/** + * Header-level event props mapped to `columns[].header.events`. + */ +export interface HeaderLevelEventProps { + onHeaderClick?: ColumnEventCallback; + onHeaderAfterRender?: ColumnEventCallback; +} + +export type ProColumnEventProps = ( + ColumnLevelEventProps & + CellLevelEventProps & + HeaderLevelEventProps +); + +/** + * Column props for Grid Pro, including event handlers. + */ +export type ProColumnProps = ColumnProps & ProColumnEventProps; + +/** Flat event prop → nested Grid option path for columns. */ +const COLUMN_EVENT_ALIASES = { + onAfterResize: ['events', 'afterResize'], + onBeforeSort: ['events', 'beforeSort'], + onAfterSort: ['events', 'afterSort'], + onBeforeFilter: ['events', 'beforeFilter'], + onAfterFilter: ['events', 'afterFilter'], + onCellClick: ['cells', 'events', 'click'], + onCellDblClick: ['cells', 'events', 'dblClick'], + onCellMouseOver: ['cells', 'events', 'mouseOver'], + onCellMouseOut: ['cells', 'events', 'mouseOut'], + onCellAfterRender: ['cells', 'events', 'afterRender'], + onCellAfterEdit: ['cells', 'events', 'afterEdit'], + onHeaderClick: ['header', 'events', 'click'], + onHeaderAfterRender: ['header', 'events', 'afterRender'] +} as const satisfies Record; + +/** + * Maps Pro column event props onto nested Grid option paths. + */ +export function mapColumnEventProps( + props: Record +): Record { + const options = { ...props }; + + mapEventsProps(options, COLUMN_EVENT_ALIASES); + + return options; +} + +/** + * Maps Pro column event props on declarative `options.columns`. + */ +export function mergeColumnEventProps( + options: Record +): Record { + const columns = options.columns; + + if (!Array.isArray(columns)) { + return options; + } + + return { + ...options, + columns: columns.map((column) => ( + isObject(column) ? mapColumnEventProps(column) : column + )) + }; +} diff --git a/packages/grid-pro-react/src/utils/mappers/column/index.ts b/packages/grid-pro-react/src/utils/mappers/column/index.ts new file mode 100644 index 0000000..6c7abfd --- /dev/null +++ b/packages/grid-pro-react/src/utils/mappers/column/index.ts @@ -0,0 +1,21 @@ +/** + * Grid React integration. + * Copyright (c) 2025, Highsoft + * + * A valid license is required for using this software. + * See highcharts.com/license + * + */ + +export { + mapColumnEventProps, + mergeColumnEventProps +} from './columnOptions'; + +export type { + ColumnLevelEventProps, + CellLevelEventProps, + HeaderLevelEventProps, + ProColumnEventProps, + ProColumnProps +} from './columnOptions'; diff --git a/packages/grid-pro-react/src/utils/mappers/grid/gridOptions.ts b/packages/grid-pro-react/src/utils/mappers/grid/gridOptions.ts new file mode 100644 index 0000000..08a0bcf --- /dev/null +++ b/packages/grid-pro-react/src/utils/mappers/grid/gridOptions.ts @@ -0,0 +1,110 @@ +/** + * Grid React integration. + * Copyright (c) 2025, Highsoft + * + * A valid license is required for using this software. + * See highcharts.com/license + * + */ + +import type { GridProps as BaseGridProps } from '@highcharts/grid-shared-react'; +import type GridPro from '@highcharts/grid-pro'; +import type { GridEventCallback } from '@highcharts/grid-pro/es-modules/Grid/Pro/GridEvents.js'; +import type { + RowPinningChangeEventCallback +} from '@highcharts/grid-pro/es-modules/Grid/Pro/RowPinning/RowPinningController.js'; +import type { + AfterTreeRowToggleEvent, + BeforeTreeRowToggleEvent +} from '@highcharts/grid-pro/es-modules/Grid/Pro/TreeView/TreeProjectionController.js'; +import { mapEventsProps } from '../../mapEventsProps'; + +/** + * Grid Pro options, including license key support from the Pro bundle. + */ +export type GridProOptions = GridPro.Options & { + gridKey?: string; +}; + +export type GridOptions = GridProOptions; + +/** + * Grid-level event props mapped to `options.events`. + */ +export interface GridLevelEventProps { + onBeforeLoad?: GridEventCallback; + onAfterLoad?: GridEventCallback; + onBeforeUpdate?: GridEventCallback; + onAfterUpdate?: GridEventCallback; + onBeforeRedraw?: GridEventCallback; + onAfterRedraw?: GridEventCallback; + onBeforeTreeRowToggle?: (e: BeforeTreeRowToggleEvent) => void; + onAfterTreeRowToggle?: (e: AfterTreeRowToggleEvent) => void; +} + +/** + * Row pinning event props mapped to + * `options.rendering.rows.pinning.events`. + */ +export interface RowPinningEventProps { + onBeforeRowPin?: RowPinningChangeEventCallback; + onAfterRowPin?: RowPinningChangeEventCallback; +} + +export type GridEventProps = GridLevelEventProps & RowPinningEventProps; + +/** + * Props for the Grid Pro React component. + */ +export interface GridProProps + extends BaseGridProps, GridEventProps { + /** + * Grid Pro license key. + */ + gridKey: string; +} + +/** Flat event prop → nested Grid option path for grid props. */ +const GRID_EVENT_ALIASES = { + onBeforeLoad: ['events', 'beforeLoad'], + onAfterLoad: ['events', 'afterLoad'], + onBeforeUpdate: ['events', 'beforeUpdate'], + onAfterUpdate: ['events', 'afterUpdate'], + onBeforeRedraw: ['events', 'beforeRedraw'], + onAfterRedraw: ['events', 'afterRedraw'], + onBeforeTreeRowToggle: ['events', 'beforeTreeRowToggle'], + onAfterTreeRowToggle: ['events', 'afterTreeRowToggle'], + onBeforeRowPin: ['rendering', 'rows', 'pinning', 'events', 'beforeRowPin'], + onAfterRowPin: ['rendering', 'rows', 'pinning', 'events', 'afterRowPin'] +} as const satisfies Record; + +export const GRID_EVENT_PROP_KEYS = Object.keys( + GRID_EVENT_ALIASES +) as (keyof GridEventProps)[]; + +/** + * Maps Pro event props from declarative `` props. + */ +export function normalizeGridEventProps( + props: GridProProps +): Record { + const options: Record = {}; + + mapEventsProps( + options, + GRID_EVENT_ALIASES, + props as unknown as Record + ); + + return options; +} + +/** + * Event handler values from Grid props, for `useMemo` dependency lists. + */ +export function getGridEventPropDeps(props: GridProProps): unknown[] { + return [ + props.gridKey, + ...GRID_EVENT_PROP_KEYS.map((key) => props[key]) + ]; +} diff --git a/packages/grid-pro-react/src/utils/mappers/grid/index.ts b/packages/grid-pro-react/src/utils/mappers/grid/index.ts new file mode 100644 index 0000000..6f8427a --- /dev/null +++ b/packages/grid-pro-react/src/utils/mappers/grid/index.ts @@ -0,0 +1,23 @@ +/** + * Grid React integration. + * Copyright (c) 2025, Highsoft + * + * A valid license is required for using this software. + * See highcharts.com/license + * + */ + +export { + normalizeGridEventProps, + getGridEventPropDeps, + GRID_EVENT_PROP_KEYS +} from './gridOptions'; + +export type { + GridProOptions, + GridOptions, + GridLevelEventProps, + RowPinningEventProps, + GridEventProps, + GridProProps +} from './gridOptions'; diff --git a/packages/grid-pro-react/src/utils/mappers/pagination/index.ts b/packages/grid-pro-react/src/utils/mappers/pagination/index.ts new file mode 100644 index 0000000..0cde40f --- /dev/null +++ b/packages/grid-pro-react/src/utils/mappers/pagination/index.ts @@ -0,0 +1,18 @@ +/** + * Grid React integration. + * Copyright (c) 2025, Highsoft + * + * A valid license is required for using this software. + * See highcharts.com/license + * + */ + +export { + mapPaginationEventProps, + mergePaginationEventProps +} from './paginationOptions'; + +export type { + PaginationEventProps, + ProPaginationProps +} from './paginationOptions'; diff --git a/packages/grid-pro-react/src/utils/mappers/pagination/paginationOptions.ts b/packages/grid-pro-react/src/utils/mappers/pagination/paginationOptions.ts new file mode 100644 index 0000000..855a0c8 --- /dev/null +++ b/packages/grid-pro-react/src/utils/mappers/pagination/paginationOptions.ts @@ -0,0 +1,72 @@ +/** + * Grid React integration. + * Copyright (c) 2025, Highsoft + * + * A valid license is required for using this software. + * See highcharts.com/license + * + */ + +import { isObject } from '@highcharts/grid-shared-react'; +import { mapEventsProps } from '../../mapEventsProps'; +import type { PaginationProps } from '@highcharts/grid-shared-react'; +import type { + AfterPageChangeEvent, + AfterPageSizeChangeEvent, + BeforePageChangeEvent, + BeforePageSizeChangeEvent +} from '@highcharts/grid-pro/es-modules/Grid/Pro/Pagination/PaginationComposition.js'; + +/** + * Pagination event props mapped to `pagination.events`. + */ +export interface PaginationEventProps { + onBeforePageChange?: (e: BeforePageChangeEvent) => void; + onAfterPageChange?: (e: AfterPageChangeEvent) => void; + onBeforePageSizeChange?: (e: BeforePageSizeChangeEvent) => void; + onAfterPageSizeChange?: (e: AfterPageSizeChangeEvent) => void; +} + +/** + * Pagination props for Grid Pro, including event handlers. + */ +export type ProPaginationProps = PaginationProps & PaginationEventProps; + +/** Flat event prop → nested Grid option path for pagination. */ +const PAGINATION_EVENT_ALIASES = { + onBeforePageChange: ['events', 'beforePageChange'], + onAfterPageChange: ['events', 'afterPageChange'], + onBeforePageSizeChange: ['events', 'beforePageSizeChange'], + onAfterPageSizeChange: ['events', 'afterPageSizeChange'] +} as const satisfies Record; + +/** + * Maps Pro pagination event props onto nested Grid option paths. + */ +export function mapPaginationEventProps( + props: Record +): Record { + const options = { ...props }; + + mapEventsProps(options, PAGINATION_EVENT_ALIASES); + + return options; +} + +/** + * Maps Pro pagination event props on declarative `options.pagination`. + */ +export function mergePaginationEventProps( + options: Record +): Record { + const pagination = options.pagination; + + if (!isObject(pagination)) { + return options; + } + + return { + ...options, + pagination: mapPaginationEventProps({ ...pagination }) + }; +} From 2c5013f551ebd498ff9b1576f032c874f03645ae Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Wed, 15 Jul 2026 13:14:24 +0200 Subject: [PATCH 6/9] Added exports. --- packages/grid-pro-react/src/index.ts | 44 ++++++++++++++++++------- packages/grid-shared-react/src/index.ts | 4 --- 2 files changed, 33 insertions(+), 15 deletions(-) diff --git a/packages/grid-pro-react/src/index.ts b/packages/grid-pro-react/src/index.ts index 7ba334f..01e6fee 100644 --- a/packages/grid-pro-react/src/index.ts +++ b/packages/grid-pro-react/src/index.ts @@ -7,19 +7,25 @@ * */ -import GridPro from '@highcharts/grid-pro'; - -export { default as Grid } from './Grid'; -export { default as GridPro } from './Grid'; -export { +import type { ComponentType } from 'react'; +import { + Column as SharedColumn, + Data as SharedData, + Pagination as SharedPagination, Caption, - Data, ColumnDefaults, - Column, Description, - Pagination, Header } from '@highcharts/grid-shared-react'; +import type { ProColumnProps } from './utils/mappers/column'; +import type { ProPaginationProps } from './utils/mappers/pagination'; + +export { default as Grid } from './Grid'; +export { default as GridPro } from './Grid'; +export { Caption, ColumnDefaults, Description, Header }; +export const Column = SharedColumn as ComponentType; +export const Data = SharedData; +export const Pagination = SharedPagination as ComponentType; export { DataTable, DataConnector } from '@highcharts/grid-pro'; export { merge } from '@highcharts/grid-pro/es-modules/Shared/Utilities.js'; export type { @@ -30,14 +36,30 @@ export type { DataProps, DataColumns, DataColumnValue, - ColumnProps, ColumnOptionsProps, ColumnDataType, ColumnSortingOrder, CellValueGetterContext, - PaginationProps, HeaderProps, GroupedHeaderOptions, HeaderCellAccessibilityProps } from '@highcharts/grid-shared-react'; -export type GridOptions = GridPro.Options; +export type { + GridProProps, + GridProOptions, + GridOptions, + GridEventProps, + GridLevelEventProps, + RowPinningEventProps +} from './utils/mappers/grid'; +export type { + ProColumnProps, + ProColumnEventProps, + ColumnLevelEventProps, + CellLevelEventProps, + HeaderLevelEventProps +} from './utils/mappers/column'; +export type { + ProPaginationProps, + PaginationEventProps +} from './utils/mappers/pagination'; diff --git a/packages/grid-shared-react/src/index.ts b/packages/grid-shared-react/src/index.ts index a9c907e..e9f5bd8 100644 --- a/packages/grid-shared-react/src/index.ts +++ b/packages/grid-shared-react/src/index.ts @@ -22,10 +22,6 @@ export { Header } from './components/options'; export { getChildProps } from './utils/getChildProps'; -/** - * Monorepo-internal utilities. Not part of the public consumer API for - * grid-lite-react / grid-pro-react packages. - */ export { isObject } from './utils/isObject'; export { normalizeChildOptions } from './utils/normalizeChildOptions'; export { useDeclarativeGridOptions } from './hooks/useDeclarativeGridOptions'; From 6f793d3e6b1df1d1933cb8b82f25f9488e5ab200 Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Wed, 15 Jul 2026 13:18:36 +0200 Subject: [PATCH 7/9] Fixed Grid main component. --- packages/grid-lite-react/src/Grid.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/grid-lite-react/src/Grid.tsx b/packages/grid-lite-react/src/Grid.tsx index 4f97043..25ce188 100644 --- a/packages/grid-lite-react/src/Grid.tsx +++ b/packages/grid-lite-react/src/Grid.tsx @@ -11,18 +11,18 @@ import { BaseGrid, useDeclarativeGridOptions } from '@highcharts/grid-shared-react'; -import { merge } from '@highcharts/grid-lite/es-modules/Shared/Utilities.js'; import Grid from '@highcharts/grid-lite/es-modules/masters/grid-lite.src'; import '@highcharts/grid-lite/css/grid-lite.css'; import type { Options } from '@highcharts/grid-lite/es-modules/Grid/Core/Options'; import type { GridProps } from '@highcharts/grid-shared-react'; +import { buildGridOptions } from './utils/buildGridOptions'; export default function GridLite(props: GridProps) { const { gridRef, children, options, callback } = props; const { gridOptions, columnKey } = useDeclarativeGridOptions( children, options, - (childOptions, opts) => merge(childOptions, opts ?? {}) as Options + (childOptions, opts) => buildGridOptions(childOptions, opts) ); return ( From e492b041a4eafaca39f96dd24bd51c64a056506d Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Wed, 15 Jul 2026 13:19:19 +0200 Subject: [PATCH 8/9] Fixed Grid main component. --- packages/grid-pro-react/src/Grid.tsx | 37 ++++++++++++++-------------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/packages/grid-pro-react/src/Grid.tsx b/packages/grid-pro-react/src/Grid.tsx index c9e596f..cbd0d46 100644 --- a/packages/grid-pro-react/src/Grid.tsx +++ b/packages/grid-pro-react/src/Grid.tsx @@ -7,37 +7,38 @@ * */ -import { useMemo } from 'react'; import { BaseGrid, - GridProps, - getChildProps + useDeclarativeGridOptions } from '@highcharts/grid-shared-react'; -import { merge } from '@highcharts/grid-pro/es-modules/Shared/Utilities.js'; import Grid from '@highcharts/grid-pro/es-modules/masters/grid-pro.src'; import '@highcharts/grid-pro/css/grid-pro.css'; -import type { Options } from '@highcharts/grid-pro/es-modules/Grid/Core/Options'; - -export default function GridPro(props: GridProps) { - const { gridRef, children, options, ...gridProps } = props; - const childOptions = useMemo(() => getChildProps(children), [children]); - const columnKey = useMemo(() => { - const columns = childOptions.columns as - Array<{ id?: string }> | undefined; +import type { GridProProps } from './utils/mappers/grid'; +import { + getGridEventPropDeps +} from './utils/mappers/grid'; +import { buildGridOptions } from './utils/buildGridOptions'; - return columns?.map((column) => column.id).join('\0') ?? ''; - }, [childOptions]); - const gridOptions = useMemo( - () => merge(childOptions, options ?? {}) as Options, - [childOptions, options] +export default function GridPro(props: GridProProps) { + const { gridKey, gridRef, children, options, callback } = props; + const { gridOptions, columnKey } = useDeclarativeGridOptions( + children, + options, + (childOptions, opts) => buildGridOptions( + gridKey, + childOptions, + opts, + props + ), + getGridEventPropDeps(props) ); return ( ); From 9cd42b13fd0660714d5233f6a07b20fceab84aa1 Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Wed, 15 Jul 2026 14:37:04 +0200 Subject: [PATCH 9/9] Added tests. --- packages/grid-pro-react/tests/Grid.test.tsx | 7 +- .../tests/mappers/columnOptions.test.tsx | 90 +++++++++++++++++ .../tests/mappers/gridOptions.test.tsx | 96 +++++++++++++++++++ .../tests/mappers/paginationOptions.test.tsx | 55 +++++++++++ .../tests/utils/mapEventsProps.test.ts | 45 +++++++++ .../tests/createGridTests.tsx | 37 +++++-- .../tests/options/Column.test.tsx | 50 ++++++++-- .../tests/options/ColumnDefaults.test.tsx | 27 +++++- .../tests/options/Pagination.test.tsx | 68 +++++++++---- 9 files changed, 439 insertions(+), 36 deletions(-) create mode 100644 packages/grid-pro-react/tests/mappers/columnOptions.test.tsx create mode 100644 packages/grid-pro-react/tests/mappers/gridOptions.test.tsx create mode 100644 packages/grid-pro-react/tests/mappers/paginationOptions.test.tsx create mode 100644 packages/grid-pro-react/tests/utils/mapEventsProps.test.ts diff --git a/packages/grid-pro-react/tests/Grid.test.tsx b/packages/grid-pro-react/tests/Grid.test.tsx index 65e49d7..7cbdcb2 100644 --- a/packages/grid-pro-react/tests/Grid.test.tsx +++ b/packages/grid-pro-react/tests/Grid.test.tsx @@ -1,7 +1,9 @@ import { createGridTests } from '@highcharts/grid-shared-react/tests/createGridTests'; import { Grid, GridOptions } from '../src/index'; -createGridTests( +const GRID_KEY = 'AAAA-BBBB-CCCC-DDDD-EEEE-FFFF'; + +createGridTests( 'Grid Pro', Grid, { @@ -19,5 +21,8 @@ createGridTests( age: [40, 35] } } + }, + { + gridKey: GRID_KEY } ); diff --git a/packages/grid-pro-react/tests/mappers/columnOptions.test.tsx b/packages/grid-pro-react/tests/mappers/columnOptions.test.tsx new file mode 100644 index 0000000..4562165 --- /dev/null +++ b/packages/grid-pro-react/tests/mappers/columnOptions.test.tsx @@ -0,0 +1,90 @@ +import { describe, it, expect, vi } from 'vitest'; +import { Column, Data } from '../../src/index'; +import { + mergeColumnEventProps, + mapColumnEventProps +} from '../../src/utils/mappers/column'; +import { getChildProps, normalizeChildOptions } from '@highcharts/grid-shared-react'; + +describe('mapColumnEventProps', () => { + it('maps column event props onto nested option paths', () => { + const onAfterSort = vi.fn(); + const onCellClick = vi.fn(); + + expect(mapColumnEventProps({ + columnId: 'name', + onAfterSort, + onCellClick + })).toEqual({ + columnId: 'name', + events: { + afterSort: onAfterSort + }, + cells: { + events: { + click: onCellClick + } + } + }); + }); + + it('maps header event props onto nested option paths', () => { + const onHeaderClick = vi.fn(); + const onHeaderAfterRender = vi.fn(); + + expect(mapColumnEventProps({ + columnId: 'name', + onHeaderClick, + onHeaderAfterRender + })).toEqual({ + columnId: 'name', + header: { + events: { + click: onHeaderClick, + afterRender: onHeaderAfterRender + } + } + }); + }); +}); + +describe('mergeColumnEventProps', () => { + it('maps event props on declarative column options', () => { + const onAfterSort = vi.fn(); + const onCellClick = vi.fn(); + + const options = mergeColumnEventProps( + normalizeChildOptions( + getChildProps( + <> + + + + ) + ) + ); + + expect(options.columns).toEqual([ + { + id: 'name', + events: { + afterSort: onAfterSort + }, + cells: { + events: { + click: onCellClick + } + } + } + ]); + }); +}); diff --git a/packages/grid-pro-react/tests/mappers/gridOptions.test.tsx b/packages/grid-pro-react/tests/mappers/gridOptions.test.tsx new file mode 100644 index 0000000..b3dcde3 --- /dev/null +++ b/packages/grid-pro-react/tests/mappers/gridOptions.test.tsx @@ -0,0 +1,96 @@ +import { describe, it, expect, vi } from 'vitest'; +import { Column } from '../../src/index'; +import { buildGridOptions } from '../../src/utils/buildGridOptions'; +import { + getGridEventPropDeps, + GRID_EVENT_PROP_KEYS, + normalizeGridEventProps +} from '../../src/utils/mappers/grid'; +import { getChildProps } from '@highcharts/grid-shared-react'; +import type { GridProOptions, GridProProps } from '../../src/utils/mappers/grid'; + +describe('normalizeGridEventProps', () => { + it('maps grid-level and row pinning event props', () => { + const onAfterLoad = vi.fn(); + const onAfterRowPin = vi.fn(); + + expect(normalizeGridEventProps({ + gridKey: 'GRID-KEY', + onAfterLoad, + onAfterRowPin + } as GridProProps)).toEqual({ + events: { + afterLoad: onAfterLoad + }, + rendering: { + rows: { + pinning: { + events: { + afterRowPin: onAfterRowPin + } + } + } + } + }); + }); +}); + +describe('buildGridOptions', () => { + it('merges gridKey and grid-level events into options', () => { + const onAfterLoad = vi.fn(); + const options = buildGridOptions( + 'GRID-KEY', + { + data: { + columns: { + name: ['Alice'] + } + } + }, + { + gridKey: 'OLD-KEY', + events: { + beforeLoad: vi.fn() + } + } as GridProOptions, + { + gridKey: 'GRID-KEY', + onAfterLoad + } as GridProProps + ); + + expect(options.gridKey).toBe('GRID-KEY'); + expect(options.events?.beforeLoad).toBeTypeOf('function'); + expect(options.events?.afterLoad).toBe(onAfterLoad); + }); + + it('maps declarative children and builds full grid options', () => { + const options = buildGridOptions( + 'GRID-KEY', + getChildProps(), + undefined, + { gridKey: 'GRID-KEY' } as GridProProps + ); + + expect(options.gridKey).toBe('GRID-KEY'); + expect(options.columns).toEqual([{ id: 'name' }]); + }); +}); + +describe('getGridEventPropDeps', () => { + it('exposes stable dep keys for every grid event prop', () => { + const onAfterLoad = vi.fn(); + const props = { + gridKey: 'KEY', + onAfterLoad + } as GridProProps; + + expect(GRID_EVENT_PROP_KEYS).toContain('onAfterLoad'); + expect(getGridEventPropDeps(props)).toEqual([ + 'KEY', + ...GRID_EVENT_PROP_KEYS.map( + (key: keyof GridProProps) => props[key] + ) + ]); + }); +}); diff --git a/packages/grid-pro-react/tests/mappers/paginationOptions.test.tsx b/packages/grid-pro-react/tests/mappers/paginationOptions.test.tsx new file mode 100644 index 0000000..b4da27d --- /dev/null +++ b/packages/grid-pro-react/tests/mappers/paginationOptions.test.tsx @@ -0,0 +1,55 @@ +import { describe, it, expect, vi } from 'vitest'; +import { Data, Pagination } from '../../src/index'; +import { + mapPaginationEventProps, + mergePaginationEventProps +} from '../../src/utils/mappers/pagination'; +import { getChildProps, normalizeChildOptions } from '@highcharts/grid-shared-react'; + +describe('mapPaginationEventProps', () => { + it('maps pagination event props onto nested option paths', () => { + const onBeforePageChange = vi.fn(); + + expect(mapPaginationEventProps({ + page: 1, + pageSize: 2, + onBeforePageChange + })).toEqual({ + page: 1, + pageSize: 2, + events: { + beforePageChange: onBeforePageChange + } + }); + }); +}); + +describe('mergePaginationEventProps', () => { + it('maps pagination event props from declarative child options', () => { + const onBeforePageChange = vi.fn(); + const children = ( + <> + + + + ); + + const options = mergePaginationEventProps( + normalizeChildOptions(getChildProps(children)) + ); + + expect(options.pagination).toEqual({ + enabled: true, + page: 1, + pageSize: 2, + position: 'bottom', + events: { + beforePageChange: onBeforePageChange + } + }); + }); +}); diff --git a/packages/grid-pro-react/tests/utils/mapEventsProps.test.ts b/packages/grid-pro-react/tests/utils/mapEventsProps.test.ts new file mode 100644 index 0000000..1a329fb --- /dev/null +++ b/packages/grid-pro-react/tests/utils/mapEventsProps.test.ts @@ -0,0 +1,45 @@ +import { describe, it, expect, vi } from 'vitest'; +import { mapEventsProps } from '../../src/utils/mapEventsProps'; + +describe('mapEventsProps', () => { + it('maps flat event props onto nested option paths', () => { + const onCellClick = vi.fn(); + const column: Record = { + id: 'name', + onCellClick + }; + + mapEventsProps(column, { + onCellClick: ['cells', 'events', 'click'] + }); + + expect(column).toEqual({ + id: 'name', + cells: { + events: { + click: onCellClick + } + } + }); + }); + + it('copies handlers from a source object onto nested target paths', () => { + const onBeforePageChange = vi.fn(); + const pagination: Record = { enabled: true }; + + mapEventsProps( + pagination, + { + onBeforePageChange: ['events', 'beforePageChange'] + }, + { page: 1, onBeforePageChange } + ); + + expect(pagination).toEqual({ + enabled: true, + events: { + beforePageChange: onBeforePageChange + } + }); + }); +}); diff --git a/packages/grid-shared-react/tests/createGridTests.tsx b/packages/grid-shared-react/tests/createGridTests.tsx index acf05db..29a1c7d 100644 --- a/packages/grid-shared-react/tests/createGridTests.tsx +++ b/packages/grid-shared-react/tests/createGridTests.tsx @@ -13,11 +13,15 @@ import { GridInstance } from '../src/hooks/useGrid'; * Use this to avoid duplicating tests between * grid-lite-react and grid-pro-react. */ -export function createGridTests( +export function createGridTests< + TOptions, + TComponentProps extends Record = Record +>( name: string, - GridComponent: ComponentType>, + GridComponent: ComponentType & TComponentProps>, testOptions: TOptions, - updatedOptions: TOptions + updatedOptions: TOptions, + componentProps?: TComponentProps ) { describe(name, () => { @@ -29,7 +33,11 @@ export function createGridTests( }; const { container } = render( - + ); expect(container.firstChild).toBeInstanceOf(HTMLDivElement); @@ -47,6 +55,7 @@ export function createGridTests( gridRef = useRef>(null); return ( { initialized = true; }} @@ -64,7 +73,13 @@ export function createGridTests( it('calls callback when grid is initialized', async () => { const callback = vi.fn(); - render(); + render( + + ); await waitFor(() => { expect(callback).toHaveBeenCalled(); @@ -83,7 +98,11 @@ export function createGridTests( return ( <> - +