From ea622a5a3625c6a657096dd9ded4cbbcbfebac1f Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Mon, 22 Jun 2026 14:14:38 +0200 Subject: [PATCH 01/51] Init demo. --- .../grid-lite/components-react/index.html | 13 +++++ .../grid-lite/components-react/package.json | 26 ++++++++++ .../grid-lite/components-react/src/App.tsx | 48 +++++++++++++++++++ .../grid-lite/components-react/src/index.css | 26 ++++++++++ .../grid-lite/components-react/src/main.tsx | 11 +++++ .../grid-lite/components-react/tsconfig.json | 28 +++++++++++ .../components-react/tsconfig.node.json | 12 +++++ .../grid-lite/components-react/vite.config.ts | 22 +++++++++ 8 files changed, 186 insertions(+) create mode 100644 examples/grid-lite/components-react/index.html create mode 100644 examples/grid-lite/components-react/package.json create mode 100644 examples/grid-lite/components-react/src/App.tsx create mode 100644 examples/grid-lite/components-react/src/index.css create mode 100644 examples/grid-lite/components-react/src/main.tsx create mode 100644 examples/grid-lite/components-react/tsconfig.json create mode 100644 examples/grid-lite/components-react/tsconfig.node.json create mode 100644 examples/grid-lite/components-react/vite.config.ts diff --git a/examples/grid-lite/components-react/index.html b/examples/grid-lite/components-react/index.html new file mode 100644 index 0000000..7f1e258 --- /dev/null +++ b/examples/grid-lite/components-react/index.html @@ -0,0 +1,13 @@ + + + + + + Highcharts Grid Lite - React Example + + +
+ + + + diff --git a/examples/grid-lite/components-react/package.json b/examples/grid-lite/components-react/package.json new file mode 100644 index 0000000..cd5b45a --- /dev/null +++ b/examples/grid-lite/components-react/package.json @@ -0,0 +1,26 @@ +{ + "name": "grid-lite-minimal-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-lite": ">=3.0.0", + "@highcharts/grid-lite-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-lite/components-react/src/App.tsx b/examples/grid-lite/components-react/src/App.tsx new file mode 100644 index 0000000..9151ef7 --- /dev/null +++ b/examples/grid-lite/components-react/src/App.tsx @@ -0,0 +1,48 @@ +import { useState, useRef } from 'react'; +import { + type GridInstance, + type GridOptions, + type GridRefHandle, + Grid +} from '@highcharts/grid-lite-react'; + +function App() { + const [options] = useState({ + dataTable: { + columns: { + 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] + } + }, + caption: { + text: 'Grid Lite' + }, + pagination: { + enabled: true, + pageSize: 3, + controls: { + pageSizeSelector: true, + pageButtons: true + } + } + }); + const grid = useRef | null>(null); + + const onButtonClick = () => { + console.info('(ref) grid:', grid.current?.grid); + }; + const onGridCallback = (grid: GridInstance) => { + console.info('(callback) grid:', grid); + }; + + return ( + <> + + + + ); +} + +export default App; diff --git a/examples/grid-lite/components-react/src/index.css b/examples/grid-lite/components-react/src/index.css new file mode 100644 index 0000000..04bacd8 --- /dev/null +++ b/examples/grid-lite/components-react/src/index.css @@ -0,0 +1,26 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +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; +} + +@media (prefers-color-scheme: dark) { + body { + background-color: #121212; + color: #ffffff; + } +} diff --git a/examples/grid-lite/components-react/src/main.tsx b/examples/grid-lite/components-react/src/main.tsx new file mode 100644 index 0000000..0c657b5 --- /dev/null +++ b/examples/grid-lite/components-react/src/main.tsx @@ -0,0 +1,11 @@ +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-lite/components-react/tsconfig.json b/examples/grid-lite/components-react/tsconfig.json new file mode 100644 index 0000000..78a6daf --- /dev/null +++ b/examples/grid-lite/components-react/tsconfig.json @@ -0,0 +1,28 @@ +{ + "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-lite/components-react/tsconfig.node.json b/examples/grid-lite/components-react/tsconfig.node.json new file mode 100644 index 0000000..f7c2070 --- /dev/null +++ b/examples/grid-lite/components-react/tsconfig.node.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "types": ["node"] + }, + "include": ["vite.config.ts"] +} + diff --git a/examples/grid-lite/components-react/vite.config.ts b/examples/grid-lite/components-react/vite.config.ts new file mode 100644 index 0000000..adf42ba --- /dev/null +++ b/examples/grid-lite/components-react/vite.config.ts @@ -0,0 +1,22 @@ +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-lite(\/.*)?$/, + replacement: resolve(__dirname, 'node_modules/@highcharts/grid-lite$1') + } + ] + }, + server: { + port: 3000 + } +}); + From 70e0846f94ba2d9fcb870a30642ff34fb021a5fa Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Mon, 29 Jun 2026 14:16:47 +0200 Subject: [PATCH 02/51] Options attrib as optional. --- .../grid-lite/components-react/src/App.tsx | 34 ++++++++----------- .../src/components/BaseGrid.tsx | 2 +- .../grid-shared-react/src/hooks/useGrid.ts | 14 +++++--- pnpm-lock.yaml | 31 +++++++++++++++++ 4 files changed, 55 insertions(+), 26 deletions(-) diff --git a/examples/grid-lite/components-react/src/App.tsx b/examples/grid-lite/components-react/src/App.tsx index 9151ef7..eaa04d9 100644 --- a/examples/grid-lite/components-react/src/App.tsx +++ b/examples/grid-lite/components-react/src/App.tsx @@ -15,32 +15,26 @@ function App() { city: ['New York', 'Oslo', 'Paris', 'Tokyo', 'London'], salary: [50000, 60000, 70000, 80000, 90000] } - }, - caption: { - text: 'Grid Lite' - }, - pagination: { - enabled: true, - pageSize: 3, - controls: { - pageSizeSelector: true, - pageButtons: true - } } }); - const grid = useRef | null>(null); + // const grid = useRef | null>(null); - const onButtonClick = () => { - console.info('(ref) grid:', grid.current?.grid); - }; - const onGridCallback = (grid: GridInstance) => { - console.info('(callback) grid:', grid); - }; + // const onButtonClick = () => { + // console.info('(ref) grid:', grid.current?.grid); + // }; + // const onGridCallback = (grid: GridInstance) => { + // console.info('(callback) grid:', grid); + // }; return ( <> - - + + + {/* */} ); } diff --git a/packages/grid-shared-react/src/components/BaseGrid.tsx b/packages/grid-shared-react/src/components/BaseGrid.tsx index 437459e..655de72 100644 --- a/packages/grid-shared-react/src/components/BaseGrid.tsx +++ b/packages/grid-shared-react/src/components/BaseGrid.tsx @@ -31,7 +31,7 @@ export interface GridProps { /** * Grid configuration options */ - options: TOptions; + options?: TOptions; /** * Optional ref to access the grid instance */ diff --git a/packages/grid-shared-react/src/hooks/useGrid.ts b/packages/grid-shared-react/src/hooks/useGrid.ts index 327e3fb..bfbf566 100644 --- a/packages/grid-shared-react/src/hooks/useGrid.ts +++ b/packages/grid-shared-react/src/hooks/useGrid.ts @@ -25,7 +25,7 @@ export interface GridInstance { * directly depending on their types. */ export interface GridType { - grid(container: HTMLDivElement, options: TOptions, async?: boolean): GridInstance | Promise>; + grid(container: HTMLDivElement, options?: TOptions, async?: boolean): GridInstance | Promise>; } export interface UseGridOptions extends BaseGridProps { @@ -40,7 +40,7 @@ export function useGrid({ }: UseGridOptions) { const currGridRef = useRef | null>(null); const callbackRef = useRef(callback); - const pendingOptionsRef = useRef(null); + const pendingOptionsRef = useRef(void 0); const initStartedRef = useRef(false); // StrictMode runs effects twice: mount → cleanup → mount. @@ -73,7 +73,7 @@ export function useGrid({ try { // Use pending options if available (from rapid updates during init) const initOptions = pendingOptionsRef.current ?? options; - pendingOptionsRef.current = null; + pendingOptionsRef.current = void 0; const grid = await Grid.grid(container, initOptions, true); @@ -86,9 +86,9 @@ export function useGrid({ currGridRef.current = grid; // Apply any pending options that came in while we were initializing - if (pendingOptionsRef.current) { + if (pendingOptionsRef.current !== void 0) { grid.update(pendingOptionsRef.current, true); - pendingOptionsRef.current = null; + pendingOptionsRef.current = void 0; } callbackRef.current?.(grid); @@ -115,6 +115,10 @@ export function useGrid({ // Effect for options updates - separate from init useEffect(() => { + if (options === void 0) { + return; + } + if (currGridRef.current) { // Grid exists, update it directly currGridRef.current.update(options, true); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 05e60b9..ff81d18 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -48,6 +48,37 @@ importers: specifier: ^4.0.16 version: 4.0.16(@types/node@20.19.26)(@vitest/browser-playwright@4.0.16)(jsdom@27.4.0) + examples/grid-lite/components-react: + dependencies: + '@highcharts/grid-lite': + specifier: '>=3.0.0' + version: 3.0.0 + '@highcharts/grid-lite-react': + specifier: workspace:* + version: link:../../../packages/grid-lite-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-lite/minimal-nextjs: dependencies: '@highcharts/grid-lite': From 360a72fd78306ac02688dcba823b93f3e185e8c5 Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Mon, 29 Jun 2026 14:56:14 +0200 Subject: [PATCH 03/51] Added children support in Grid. --- .../grid-lite/components-react/src/App.tsx | 31 +++++++++++++------ packages/grid-lite-react/src/Grid.tsx | 5 +-- packages/grid-pro-react/src/Grid.tsx | 5 +-- .../src/components/BaseGrid.tsx | 15 +++++++-- 4 files changed, 39 insertions(+), 17 deletions(-) diff --git a/examples/grid-lite/components-react/src/App.tsx b/examples/grid-lite/components-react/src/App.tsx index eaa04d9..744ea89 100644 --- a/examples/grid-lite/components-react/src/App.tsx +++ b/examples/grid-lite/components-react/src/App.tsx @@ -1,12 +1,21 @@ import { useState, useRef } from 'react'; import { - type GridInstance, + // type GridInstance, + // type GridRefHandle, type GridOptions, - type GridRefHandle, Grid } from '@highcharts/grid-lite-react'; function App() { + /* const grid = useRef | null>(null); + + const onButtonClick = () => { + console.info('(ref) grid:', grid.current?.grid); + }; + const onGridCallback = (grid: GridInstance) => { + console.info('(callback) grid:', grid); + };*/ + const [options] = useState({ dataTable: { columns: { @@ -17,14 +26,6 @@ function App() { } } }); - // const grid = useRef | null>(null); - - // const onButtonClick = () => { - // console.info('(ref) grid:', grid.current?.grid); - // }; - // const onGridCallback = (grid: GridInstance) => { - // console.info('(callback) grid:', grid); - // }; return ( <> @@ -33,6 +34,16 @@ function App() { // gridRef={grid} // callback={onGridCallback} > +
Whatever
+ {/* Grid Caption */} + {/* Grid Description + + +
Grid Header
+ Grid Cell +
+
*/} + {/* Grid Pagination */}
{/* */} diff --git a/packages/grid-lite-react/src/Grid.tsx b/packages/grid-lite-react/src/Grid.tsx index f4d21fb..4b918d2 100644 --- a/packages/grid-lite-react/src/Grid.tsx +++ b/packages/grid-lite-react/src/Grid.tsx @@ -15,6 +15,7 @@ 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'; -export default function GridLite({ options, gridRef, callback }: GridProps) { - return ; +export default function GridLite(props: GridProps) { + const { gridRef, ...gridProps } = props; + return ; } diff --git a/packages/grid-pro-react/src/Grid.tsx b/packages/grid-pro-react/src/Grid.tsx index 398edea..1e55204 100644 --- a/packages/grid-pro-react/src/Grid.tsx +++ b/packages/grid-pro-react/src/Grid.tsx @@ -15,6 +15,7 @@ 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({ options, gridRef, callback }: GridProps) { - return ; +export default function GridPro(props: GridProps) { + const { gridRef, ...gridProps } = props; + return ; } diff --git a/packages/grid-shared-react/src/components/BaseGrid.tsx b/packages/grid-shared-react/src/components/BaseGrid.tsx index 655de72..6014d73 100644 --- a/packages/grid-shared-react/src/components/BaseGrid.tsx +++ b/packages/grid-shared-react/src/components/BaseGrid.tsx @@ -7,7 +7,7 @@ * */ -import { useRef, useImperativeHandle, forwardRef, ForwardedRef } from 'react'; +import { useRef, useImperativeHandle, forwardRef, ForwardedRef, ReactNode } from 'react'; import { useGrid, GridType, @@ -32,6 +32,10 @@ export interface GridProps { * Grid configuration options */ options?: TOptions; + /** + * Optional React children rendered inside the Grid wrapper. + */ + children?: ReactNode; /** * Optional ref to access the grid instance */ @@ -56,7 +60,7 @@ export const BaseGrid = forwardRef(function BaseGrid( props: BaseGridProps, ref: ForwardedRef> ) { - const { options, Grid, callback } = props; + const { options, Grid, callback, children } = props; const containerRef = useRef(null); const currGridRef = useGrid({ @@ -76,5 +80,10 @@ export const BaseGrid = forwardRef(function BaseGrid( [] ); - return
; + return ( +
+ {children} +
+
+ ); }); From 651d8aeb2010115fa03d9125568fa3d5f29c13be Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Tue, 30 Jun 2026 13:28:41 +0200 Subject: [PATCH 04/51] Created Caption.jsx component. --- .../grid-lite/components-react/src/App.tsx | 15 +- packages/grid-lite-react/src/Grid.tsx | 14 +- packages/grid-lite-react/src/index.ts | 4 +- packages/grid-pro-react/src/Grid.tsx | 14 +- packages/grid-pro-react/src/index.ts | 4 +- .../src/components/BaseGrid.tsx | 18 +- .../src/components/BaseGridOptions.ts | 32 +++ .../components/options/caption/Caption.tsx | 34 +++ .../src/components/options/caption/index.ts | 11 + .../src/components/options/index.ts | 11 + .../src/hooks/useGrid.test.tsx | 5 +- .../grid-shared-react/src/hooks/useGrid.ts | 6 +- packages/grid-shared-react/src/index.ts | 3 + .../src/utils/getChildProps.ts | 215 ++++++++++++++++++ 14 files changed, 353 insertions(+), 33 deletions(-) create mode 100644 packages/grid-shared-react/src/components/BaseGridOptions.ts create mode 100644 packages/grid-shared-react/src/components/options/caption/Caption.tsx create mode 100644 packages/grid-shared-react/src/components/options/caption/index.ts create mode 100644 packages/grid-shared-react/src/components/options/index.ts create mode 100644 packages/grid-shared-react/src/utils/getChildProps.ts diff --git a/examples/grid-lite/components-react/src/App.tsx b/examples/grid-lite/components-react/src/App.tsx index 744ea89..d593199 100644 --- a/examples/grid-lite/components-react/src/App.tsx +++ b/examples/grid-lite/components-react/src/App.tsx @@ -1,9 +1,10 @@ import { useState, useRef } from 'react'; import { - // type GridInstance, + type GridInstance, // type GridRefHandle, type GridOptions, - Grid + Grid, + Caption } from '@highcharts/grid-lite-react'; function App() { @@ -11,10 +12,11 @@ function App() { const onButtonClick = () => { console.info('(ref) grid:', grid.current?.grid); - }; + }; */ + const onGridCallback = (grid: GridInstance) => { console.info('(callback) grid:', grid); - };*/ + }; const [options] = useState({ dataTable: { @@ -32,10 +34,9 @@ function App() { -
Whatever
- {/* Grid Caption */} + Grid Caption {/* Grid Description diff --git a/packages/grid-lite-react/src/Grid.tsx b/packages/grid-lite-react/src/Grid.tsx index 4b918d2..f7851aa 100644 --- a/packages/grid-lite-react/src/Grid.tsx +++ b/packages/grid-lite-react/src/Grid.tsx @@ -7,15 +7,23 @@ * */ +import { useMemo } from 'react'; import { BaseGrid, - GridProps + GridProps, + getChildProps } 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'; export default function GridLite(props: GridProps) { - const { gridRef, ...gridProps } = props; - return ; + const { gridRef, children, options, ...gridProps } = props; + const gridOptions = useMemo( + () => merge(getChildProps(children), options ?? {}) as Options, + [children, options] + ); + + return ; } diff --git a/packages/grid-lite-react/src/index.ts b/packages/grid-lite-react/src/index.ts index 93cb0e5..a96ed99 100644 --- a/packages/grid-lite-react/src/index.ts +++ b/packages/grid-lite-react/src/index.ts @@ -11,6 +11,6 @@ import GridLite from '@highcharts/grid-lite'; export { default as Grid } from './Grid'; export { default as GridLite } from './Grid'; -export type { GridInstance } from '@highcharts/grid-shared-react'; -export type { GridRefHandle } from '@highcharts/grid-shared-react'; +export { Caption } from '@highcharts/grid-shared-react'; +export type { GridInstance, GridRefHandle, CaptionProps } from '@highcharts/grid-shared-react'; export type GridOptions = GridLite.Options; diff --git a/packages/grid-pro-react/src/Grid.tsx b/packages/grid-pro-react/src/Grid.tsx index 1e55204..fc7f2ba 100644 --- a/packages/grid-pro-react/src/Grid.tsx +++ b/packages/grid-pro-react/src/Grid.tsx @@ -7,15 +7,23 @@ * */ +import { useMemo } from 'react'; import { BaseGrid, - GridProps + GridProps, + getChildProps } 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, ...gridProps } = props; - return ; + const { gridRef, children, options, ...gridProps } = props; + const gridOptions = useMemo( + () => merge(getChildProps(children), options ?? {}) as Options, + [children, options] + ); + + return ; } diff --git a/packages/grid-pro-react/src/index.ts b/packages/grid-pro-react/src/index.ts index a44be66..d628c95 100644 --- a/packages/grid-pro-react/src/index.ts +++ b/packages/grid-pro-react/src/index.ts @@ -11,6 +11,6 @@ import GridPro from '@highcharts/grid-pro'; export { default as Grid } from './Grid'; export { default as GridPro } from './Grid'; -export type { GridInstance } from '@highcharts/grid-shared-react'; -export type { GridRefHandle } from '@highcharts/grid-shared-react'; +export { Caption } from '@highcharts/grid-shared-react'; +export type { GridInstance, GridRefHandle, CaptionProps } from '@highcharts/grid-shared-react'; export type GridOptions = GridPro.Options; diff --git a/packages/grid-shared-react/src/components/BaseGrid.tsx b/packages/grid-shared-react/src/components/BaseGrid.tsx index 6014d73..4d51c20 100644 --- a/packages/grid-shared-react/src/components/BaseGrid.tsx +++ b/packages/grid-shared-react/src/components/BaseGrid.tsx @@ -33,7 +33,7 @@ export interface GridProps { */ options?: TOptions; /** - * Optional React children rendered inside the Grid wrapper. + * Declarative option components (e.g. Caption) passed as children. */ children?: ReactNode; /** @@ -49,18 +49,17 @@ export interface GridProps { /** * Props for BaseGrid component */ -export interface BaseGridProps extends GridProps { - /** - * Grid instance (from @highcharts/grid-lite or @highcharts/grid-pro) - */ +export interface BaseGridProps { + options?: TOptions; Grid: GridType; + callback?: (grid: GridInstance) => void; } export const BaseGrid = forwardRef(function BaseGrid( props: BaseGridProps, ref: ForwardedRef> ) { - const { options, Grid, callback, children } = props; + const { options, Grid, callback } = props; const containerRef = useRef(null); const currGridRef = useGrid({ @@ -80,10 +79,5 @@ export const BaseGrid = forwardRef(function BaseGrid( [] ); - return ( -
- {children} -
-
- ); + return
; }); diff --git a/packages/grid-shared-react/src/components/BaseGridOptions.ts b/packages/grid-shared-react/src/components/BaseGridOptions.ts new file mode 100644 index 0000000..9440edc --- /dev/null +++ b/packages/grid-shared-react/src/components/BaseGridOptions.ts @@ -0,0 +1,32 @@ +/** + * Grid React integration. + * Copyright (c) 2025, Highsoft + * + * A valid license is required for using this software. + * See highcharts.com/license + * + */ + +/** + * Metadata attached to declarative option components rendered as BaseGrid children. + */ +export interface BaseGridOptions { + type: 'Grid_Option'; + /** + * Dot-notation path of the Grid option (e.g. `caption`). + */ + gridOption: string; + /** + * Sub-option that receives string children (e.g. `text`). + */ + childOption?: string; + defaultOptions?: Record; + isArrayType?: boolean; +} + +/** + * A React component that maps JSX props to a Grid options path via `_GridReact`. + */ +export interface BaseGridOptionsComponent { + _GridReact: BaseGridOptions; +} diff --git a/packages/grid-shared-react/src/components/options/caption/Caption.tsx b/packages/grid-shared-react/src/components/options/caption/Caption.tsx new file mode 100644 index 0000000..e8ffaef --- /dev/null +++ b/packages/grid-shared-react/src/components/options/caption/Caption.tsx @@ -0,0 +1,34 @@ +/** + * Grid React integration. + * Copyright (c) 2025, Highsoft + * + * A valid license is required for using this software. + * See highcharts.com/license + * + */ + +import { ReactNode } from 'react'; + +export interface CaptionProps { + /** + * The custom CSS class name for the table caption. + */ + className?: string; + /** + * The HTML tag to use for the caption. + */ + htmlTag?: string; + children?: ReactNode; +} + +export function Caption(_props: CaptionProps): null; +export function Caption(): null { + return null; +} + +Caption._GridReact = { + type: 'Grid_Option', + gridOption: 'caption', + childOption: 'text', + isArrayType: false +}; diff --git a/packages/grid-shared-react/src/components/options/caption/index.ts b/packages/grid-shared-react/src/components/options/caption/index.ts new file mode 100644 index 0000000..3e96b78 --- /dev/null +++ b/packages/grid-shared-react/src/components/options/caption/index.ts @@ -0,0 +1,11 @@ +/** + * Grid React integration. + * Copyright (c) 2025, Highsoft + * + * A valid license is required for using this software. + * See highcharts.com/license + * + */ + +export { Caption } from './Caption'; +export type { CaptionProps } from './Caption'; diff --git a/packages/grid-shared-react/src/components/options/index.ts b/packages/grid-shared-react/src/components/options/index.ts new file mode 100644 index 0000000..f4822ce --- /dev/null +++ b/packages/grid-shared-react/src/components/options/index.ts @@ -0,0 +1,11 @@ +/** + * Grid React integration. + * Copyright (c) 2025, Highsoft + * + * A valid license is required for using this software. + * See highcharts.com/license + * + */ + +export { Caption } from './caption'; +export type { CaptionProps } from './caption'; diff --git a/packages/grid-shared-react/src/hooks/useGrid.test.tsx b/packages/grid-shared-react/src/hooks/useGrid.test.tsx index 774a6a1..53e86d1 100644 --- a/packages/grid-shared-react/src/hooks/useGrid.test.tsx +++ b/packages/grid-shared-react/src/hooks/useGrid.test.tsx @@ -60,9 +60,10 @@ describe('useGrid', () => { expect(initQueue).toHaveLength(1); }); - const [firstInit] = initQueue; + const firstInit = initQueue[0]; - await firstInit.resolve(); + expect(firstInit).toBeDefined(); + await firstInit!.resolve(); await waitFor(() => { expect(container.querySelector('[data-grid-id="1"]')).not.toBeNull(); diff --git a/packages/grid-shared-react/src/hooks/useGrid.ts b/packages/grid-shared-react/src/hooks/useGrid.ts index bfbf566..adf07cf 100644 --- a/packages/grid-shared-react/src/hooks/useGrid.ts +++ b/packages/grid-shared-react/src/hooks/useGrid.ts @@ -8,7 +8,6 @@ */ import { useEffect, RefObject, useRef } from 'react'; -import { BaseGridProps } from '../components/BaseGrid'; /** * Interface describing the shape of a Grid instance returned by Grid.grid() @@ -28,8 +27,11 @@ export interface GridType { grid(container: HTMLDivElement, options?: TOptions, async?: boolean): GridInstance | Promise>; } -export interface UseGridOptions extends BaseGridProps { +export interface UseGridOptions { containerRef: RefObject; + options?: TOptions; + Grid: GridType; + callback?: (grid: GridInstance) => void; } export function useGrid({ diff --git a/packages/grid-shared-react/src/index.ts b/packages/grid-shared-react/src/index.ts index 6a47b1b..445d13c 100644 --- a/packages/grid-shared-react/src/index.ts +++ b/packages/grid-shared-react/src/index.ts @@ -12,4 +12,7 @@ import { GridType, GridInstance } from './hooks/useGrid'; import type { GridProps, GridRefHandle } from './components/BaseGrid'; export { BaseGrid }; +export { Caption } from './components/options'; +export { getChildProps } from './utils/getChildProps'; +export type { CaptionProps } from './components/options'; export type { GridType, GridInstance, GridProps, GridRefHandle }; diff --git a/packages/grid-shared-react/src/utils/getChildProps.ts b/packages/grid-shared-react/src/utils/getChildProps.ts new file mode 100644 index 0000000..3d11b1a --- /dev/null +++ b/packages/grid-shared-react/src/utils/getChildProps.ts @@ -0,0 +1,215 @@ +/** + * Grid React integration. + * Copyright (c) 2025, Highsoft + * + * A valid license is required for using this software. + * See highcharts.com/license + * + */ + +import { isValidElement, ReactElement, ReactNode } from 'react'; +import type { BaseGridOptionsComponent, BaseGridOptions } from '../components/BaseGridOptions'; + +function objInsert( + obj: Record, + path: string, + value: unknown +): Record { + const keys = path.split('.'); + let current = obj; + + for (let i = 0; i < keys.length - 1; i++) { + const key = keys[i]; + + if (key === void 0) { + continue; + } + + if (!isObject(current[key])) { + current[key] = {}; + } + current = current[key] as Record; + } + + const lastKey = keys.at(-1); + + if (lastKey !== void 0) { + current[lastKey] = value; + } + 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); +} + +function getOptionComponent(type: unknown): BaseGridOptionsComponent | null { + if (typeof type !== 'function' && (typeof type !== 'object' || type === null)) { + return null; + } + + const component = type as Partial; + + return component._GridReact ? type as BaseGridOptionsComponent : null; +} + +function getChildPropsFromElement(child: ReactElement): Record { + return (child.props ?? {}) as Record; +} + +function renderChildren(children: ReactNode): string { + if (typeof children === 'string' || typeof children === 'number') { + return String(children); + } + + if (Array.isArray(children)) { + return children + .map((child) => renderChildren(child)) + .join(''); + } + + return ''; +} + +function getEffectiveMeta( + component: BaseGridOptionsComponent, + parentMeta?: BaseGridOptions +): BaseGridOptions { + const meta = component._GridReact; + + if (!parentMeta) { + return meta; + } + + return { + ...meta, + childOption: parentMeta.childOption + ? `${parentMeta.childOption}.${meta.childOption ?? ''}` + : meta.childOption, + gridOption: parentMeta.gridOption + ? `${parentMeta.gridOption}.${meta.gridOption}` + : meta.gridOption + }; +} + +export function getChildProps(children: ReactNode): Record { + const optionsFromChildren: Record = {}; + const resolvedChildren = (Array.isArray(children) ? children.flat() : [children]) + .map((child) => resolveOptionChild(child)) + .filter((child): child is ReactElement => child !== null); + + function handleChildren( + childNodes: ReactNode, + obj: Record, + meta: BaseGridOptions + ): void { + if (childNodes == null || childNodes === false) { + return; + } + + const nonOptionChildren: ReactNode[] = []; + + if (Array.isArray(childNodes)) { + for (const child of childNodes) { + if (isReactElement(child) && isOptionElement(child)) { + handleChild(child, meta); + continue; + } + + nonOptionChildren.push(child); + } + } else if (isReactElement(childNodes) && isOptionElement(childNodes)) { + handleChild(childNodes, meta); + } else { + nonOptionChildren.push(childNodes); + } + + if (meta.childOption) { + const childrenToRender = nonOptionChildren.length > 0 ? + nonOptionChildren : + [childNodes]; + + objInsert(obj, meta.childOption, renderChildren(childrenToRender)); + } + } + + function handleChild(child: ReactElement, parentMeta?: BaseGridOptions): void { + const component = getOptionComponent(child.type); + + if (!component) { + return; + } + + const meta = getEffectiveMeta(component, parentMeta); + + if (!meta.gridOption) { + return; + } + + const optionParent = optionsFromChildren[meta.gridOption] ?? ( + optionsFromChildren[meta.gridOption] = meta.isArrayType ? [] : {} + ); + const parentIsArray = Array.isArray(optionParent); + const insertInto = parentIsArray ? {} : optionParent as Record; + const childProps = getChildPropsFromElement(child); + const { children: childChildren, ...props } = childProps; + + if (meta.defaultOptions) { + Object.assign(insertInto, meta.defaultOptions); + } + + Object.assign(insertInto, props); + + if (typeof childChildren === 'string' || typeof childChildren === 'number') { + if (meta.childOption) { + objInsert(insertInto, meta.childOption, String(childChildren)); + } + } else if (childChildren != null) { + handleChildren(childChildren as ReactNode, insertInto, meta); + } + + if (parentIsArray) { + (optionsFromChildren[meta.gridOption] as unknown[]).push(insertInto); + } + } + + for (const child of resolvedChildren) { + handleChild(child); + } + + return optionsFromChildren; +} + +function isOptionElement(child: ReactElement): boolean { + return getOptionComponent(child.type) !== null; +} + +function resolveOptionChild(child: ReactNode): ReactElement | null { + if (!isReactElement(child)) { + return null; + } + + const component = getOptionComponent(child.type); + + if (component) { + return child; + } + + if (typeof child.type !== 'function') { + return null; + } + + const rendered = (child.type as (props: Record) => ReactNode)( + getChildPropsFromElement(child) + ); + + if (isReactElement(rendered) && getOptionComponent(rendered.type)) { + return rendered; + } + + return null; +} From dd9055ab41ba59105b8b5b4cbd0d51a634ac9ad5 Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Tue, 30 Jun 2026 13:43:43 +0200 Subject: [PATCH 05/51] Fixed overloads. --- eslint.config.js | 3 +++ .../src/components/options/caption/Caption.tsx | 3 +-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index a14c2bd..6be1776 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -17,6 +17,9 @@ export default defineConfig( }, rules: { 'curly': ['error', 'all'], + '@typescript-eslint/no-unused-vars': ['error', { + argsIgnorePattern: '^_' + }], '@stylistic/semi': ['error', 'always'], '@stylistic/quotes': ['error', 'single', { avoidEscape: true }], '@stylistic/brace-style': ['error', '1tbs', { allowSingleLine: true }], diff --git a/packages/grid-shared-react/src/components/options/caption/Caption.tsx b/packages/grid-shared-react/src/components/options/caption/Caption.tsx index e8ffaef..99c9199 100644 --- a/packages/grid-shared-react/src/components/options/caption/Caption.tsx +++ b/packages/grid-shared-react/src/components/options/caption/Caption.tsx @@ -21,8 +21,7 @@ export interface CaptionProps { children?: ReactNode; } -export function Caption(_props: CaptionProps): null; -export function Caption(): null { +export function Caption(_props: CaptionProps) { return null; } From 380c87821fac2c5515f3301c6f91ee5360975831 Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Tue, 30 Jun 2026 14:13:13 +0200 Subject: [PATCH 06/51] Added description component. --- .../grid-lite/components-react/src/App.tsx | 26 ++++++++--------- packages/grid-lite-react/src/index.ts | 4 +-- packages/grid-pro-react/src/index.ts | 4 +-- .../options/description/Description.tsx | 29 +++++++++++++++++++ .../components/options/description/index.ts | 11 +++++++ .../src/components/options/index.ts | 2 ++ packages/grid-shared-react/src/index.ts | 4 +-- 7 files changed, 60 insertions(+), 20 deletions(-) create mode 100644 packages/grid-shared-react/src/components/options/description/Description.tsx create mode 100644 packages/grid-shared-react/src/components/options/description/index.ts diff --git a/examples/grid-lite/components-react/src/App.tsx b/examples/grid-lite/components-react/src/App.tsx index d593199..5894db3 100644 --- a/examples/grid-lite/components-react/src/App.tsx +++ b/examples/grid-lite/components-react/src/App.tsx @@ -1,21 +1,19 @@ import { useState, useRef } from 'react'; import { type GridInstance, - // type GridRefHandle, + type GridRefHandle, type GridOptions, Grid, - Caption + Caption, + Description } from '@highcharts/grid-lite-react'; function App() { - /* const grid = useRef | null>(null); - - const onButtonClick = () => { - console.info('(ref) grid:', grid.current?.grid); - }; */ - - const onGridCallback = (grid: GridInstance) => { - console.info('(callback) grid:', grid); + // const grid = useRef | null>(null); + const [description, setDescription] = useState('Grid Description'); + const onSetDescriptionClick = () => { + setDescription('This is a new description'); + // console.info('(ref) grid:', grid.current?.grid); }; const [options] = useState({ @@ -34,11 +32,11 @@ function App() { console.info('(callback) grid:', grid)} > Grid Caption - {/* Grid Description - + {description} + { /*
Grid Header
Grid Cell @@ -46,7 +44,7 @@ function App() {
*/} {/* Grid Pagination */}
- {/* */} + ); } diff --git a/packages/grid-lite-react/src/index.ts b/packages/grid-lite-react/src/index.ts index a96ed99..695362c 100644 --- a/packages/grid-lite-react/src/index.ts +++ b/packages/grid-lite-react/src/index.ts @@ -11,6 +11,6 @@ import GridLite from '@highcharts/grid-lite'; export { default as Grid } from './Grid'; export { default as GridLite } from './Grid'; -export { Caption } from '@highcharts/grid-shared-react'; -export type { GridInstance, GridRefHandle, CaptionProps } from '@highcharts/grid-shared-react'; +export { Caption, Description } from '@highcharts/grid-shared-react'; +export type { GridInstance, GridRefHandle, CaptionProps, DescriptionProps } from '@highcharts/grid-shared-react'; export type GridOptions = GridLite.Options; diff --git a/packages/grid-pro-react/src/index.ts b/packages/grid-pro-react/src/index.ts index d628c95..84f1e66 100644 --- a/packages/grid-pro-react/src/index.ts +++ b/packages/grid-pro-react/src/index.ts @@ -11,6 +11,6 @@ import GridPro from '@highcharts/grid-pro'; export { default as Grid } from './Grid'; export { default as GridPro } from './Grid'; -export { Caption } from '@highcharts/grid-shared-react'; -export type { GridInstance, GridRefHandle, CaptionProps } from '@highcharts/grid-shared-react'; +export { Caption, Description } from '@highcharts/grid-shared-react'; +export type { GridInstance, GridRefHandle, CaptionProps, DescriptionProps } from '@highcharts/grid-shared-react'; export type GridOptions = GridPro.Options; diff --git a/packages/grid-shared-react/src/components/options/description/Description.tsx b/packages/grid-shared-react/src/components/options/description/Description.tsx new file mode 100644 index 0000000..c6ee575 --- /dev/null +++ b/packages/grid-shared-react/src/components/options/description/Description.tsx @@ -0,0 +1,29 @@ +/** + * Grid React integration. + * Copyright (c) 2025, Highsoft + * + * A valid license is required for using this software. + * See highcharts.com/license + * + */ + +import { ReactNode } from 'react'; + +export interface DescriptionProps { + /** + * The custom CSS class name for the description. + */ + className?: string; + children?: ReactNode; +} + +export function Description(_props: DescriptionProps) { + return null; +} + +Description._GridReact = { + type: 'Grid_Option', + gridOption: 'description', + childOption: 'text', + isArrayType: false +}; diff --git a/packages/grid-shared-react/src/components/options/description/index.ts b/packages/grid-shared-react/src/components/options/description/index.ts new file mode 100644 index 0000000..65cb487 --- /dev/null +++ b/packages/grid-shared-react/src/components/options/description/index.ts @@ -0,0 +1,11 @@ +/** + * Grid React integration. + * Copyright (c) 2025, Highsoft + * + * A valid license is required for using this software. + * See highcharts.com/license + * + */ + +export { Description } from './Description'; +export type { DescriptionProps } from './Description'; diff --git a/packages/grid-shared-react/src/components/options/index.ts b/packages/grid-shared-react/src/components/options/index.ts index f4822ce..5cf87d4 100644 --- a/packages/grid-shared-react/src/components/options/index.ts +++ b/packages/grid-shared-react/src/components/options/index.ts @@ -9,3 +9,5 @@ export { Caption } from './caption'; export type { CaptionProps } from './caption'; +export { Description } from './description'; +export type { DescriptionProps } from './description'; diff --git a/packages/grid-shared-react/src/index.ts b/packages/grid-shared-react/src/index.ts index 445d13c..3672450 100644 --- a/packages/grid-shared-react/src/index.ts +++ b/packages/grid-shared-react/src/index.ts @@ -12,7 +12,7 @@ import { GridType, GridInstance } from './hooks/useGrid'; import type { GridProps, GridRefHandle } from './components/BaseGrid'; export { BaseGrid }; -export { Caption } from './components/options'; +export { Caption, Description } from './components/options'; export { getChildProps } from './utils/getChildProps'; -export type { CaptionProps } from './components/options'; +export type { CaptionProps, DescriptionProps } from './components/options'; export type { GridType, GridInstance, GridProps, GridRefHandle }; From ad721bbef4e2c09a7e8bd4e959ee5e98ceff4f27 Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Tue, 30 Jun 2026 15:32:53 +0200 Subject: [PATCH 07/51] Added Data component. --- .../grid-lite/components-react/src/App.tsx | 77 +++++++++++++------ packages/grid-lite-react/src/index.ts | 13 +++- packages/grid-pro-react/src/index.ts | 13 +++- .../src/components/options/data/Data.tsx | 60 +++++++++++++++ .../src/components/options/index.ts | 2 + packages/grid-shared-react/src/index.ts | 4 +- .../src/utils/getChildProps.test.tsx | 50 ++++++++++++ packages/grid-shared-react/tsconfig.json | 2 +- 8 files changed, 191 insertions(+), 30 deletions(-) create mode 100644 packages/grid-shared-react/src/components/options/data/Data.tsx create mode 100644 packages/grid-shared-react/src/utils/getChildProps.test.tsx diff --git a/examples/grid-lite/components-react/src/App.tsx b/examples/grid-lite/components-react/src/App.tsx index d593199..939c0bc 100644 --- a/examples/grid-lite/components-react/src/App.tsx +++ b/examples/grid-lite/components-react/src/App.tsx @@ -1,52 +1,83 @@ import { useState, useRef } from 'react'; import { type GridInstance, - // type GridRefHandle, + type GridRefHandle, type GridOptions, Grid, - Caption + Caption, + Data, + DataTable } from '@highcharts/grid-lite-react'; function App() { - /* const grid = useRef | null>(null); + const grid = useRef | null>(null); + // ==== OPTIONS ==== + // const [options] = useState({ + // dataTable: { + // columns: { + // name: ['1111Alice', 'Bob', 'Charlie', 'David', 'Eve'], + // age: [23, 34, 45, 56, 67], + // city: ['New York', 'Oslo', 'Paris', 'Tokyo', 'London'], + // salary: [50000, 60000, 70000, 80000, 90000] + // } + // } + // }); + + // ==== DATA ==== + // Data Columns + const [dataSource, setDataSource] = useState({ + name: ['COLUMNS', 'Bob', 'Charlie', 'David', 'Eve'], + age: [23, 34, 45, 56, 67], + city: ['New York', 'Oslo', 'Paris', 'Tokyo', 'London'], + salary: [50000, 60000, 70000, 80000, 90000] + }); + + // Data Table + const dataTable = new DataTable({ + columns: { + name: ['DATATABLE', 'Bob', 'Charlie', 'David', 'Eve'], + age: [23, 34, 45, 56, 67], + city: ['New York', 'Oslo', 'Paris', 'Tokyo', 'London'], + salary: [50000, 60000, 70000, 80000, 90000] + } + }); + + // ==== ACTIONS ==== const onButtonClick = () => { - console.info('(ref) grid:', grid.current?.grid); - }; */ + // console.info('(ref) grid:', grid.current?.grid); + 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); }; - const [options] = useState({ - dataTable: { - columns: { - 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] - } - } - }); - return ( <> Grid Caption - {/* Grid Description - - + + {/*
Grid Header
Grid Cell -
-
*/} + */} + {/* Grid Description */} {/* Grid Pagination */}
- {/* */} + ); } diff --git a/packages/grid-lite-react/src/index.ts b/packages/grid-lite-react/src/index.ts index a96ed99..abf3c20 100644 --- a/packages/grid-lite-react/src/index.ts +++ b/packages/grid-lite-react/src/index.ts @@ -11,6 +11,15 @@ import GridLite from '@highcharts/grid-lite'; export { default as Grid } from './Grid'; export { default as GridLite } from './Grid'; -export { Caption } from '@highcharts/grid-shared-react'; -export type { GridInstance, GridRefHandle, CaptionProps } from '@highcharts/grid-shared-react'; +export { Caption, Data } from '@highcharts/grid-shared-react'; +export { DataTable, DataConnector } from '@highcharts/grid-lite'; +export { merge } from '@highcharts/grid-lite/es-modules/Shared/Utilities.js'; +export type { + GridInstance, + GridRefHandle, + CaptionProps, + DataProps, + DataColumns, + DataColumnValue +} from '@highcharts/grid-shared-react'; export type GridOptions = GridLite.Options; diff --git a/packages/grid-pro-react/src/index.ts b/packages/grid-pro-react/src/index.ts index d628c95..f62dff1 100644 --- a/packages/grid-pro-react/src/index.ts +++ b/packages/grid-pro-react/src/index.ts @@ -11,6 +11,15 @@ import GridPro from '@highcharts/grid-pro'; export { default as Grid } from './Grid'; export { default as GridPro } from './Grid'; -export { Caption } from '@highcharts/grid-shared-react'; -export type { GridInstance, GridRefHandle, CaptionProps } from '@highcharts/grid-shared-react'; +export { Caption, Data } from '@highcharts/grid-shared-react'; +export { DataTable, DataConnector } from '@highcharts/grid-pro'; +export { merge } from '@highcharts/grid-pro/es-modules/Shared/Utilities.js'; +export type { + GridInstance, + GridRefHandle, + CaptionProps, + DataProps, + DataColumns, + DataColumnValue +} from '@highcharts/grid-shared-react'; export type GridOptions = GridPro.Options; diff --git a/packages/grid-shared-react/src/components/options/data/Data.tsx b/packages/grid-shared-react/src/components/options/data/Data.tsx new file mode 100644 index 0000000..da24ab3 --- /dev/null +++ b/packages/grid-shared-react/src/components/options/data/Data.tsx @@ -0,0 +1,60 @@ +/** + * Grid React integration. + * Copyright (c) 2025, Highsoft + * + * A valid license is required for using this software. + * See highcharts.com/license + * + */ + +export type DataColumnValue = boolean | null | number | string | undefined; + +export type DataColumns = Record>; + +export interface DataProps { + /** + * The type of the data provider. + * + * @default 'local' + */ + providerType?: 'local' | string; + /** + * Whether columns should be generated automatically from data source + * column ids. + * + * @default true + */ + autogenerateColumns?: boolean; + /** + * Columns data to initialize the Grid with. + */ + columns?: DataColumns; + /** + * Data table as a source of data for the grid. + */ + dataTable?: unknown; + /** + * Connector instance or options used to populate the data table. + */ + connector?: unknown; + /** + * Automatically update the grid when the data table changes. + * + * @default false + */ + updateOnChange?: boolean; + /** + * The column ID that contains the stable, unique row IDs. + */ + idColumn?: string; +} + +export function Data(_props: DataProps) { + return null; +} + +Data._GridReact = { + type: 'Grid_Option', + gridOption: 'data', + isArrayType: false +}; diff --git a/packages/grid-shared-react/src/components/options/index.ts b/packages/grid-shared-react/src/components/options/index.ts index f4822ce..5f099f9 100644 --- a/packages/grid-shared-react/src/components/options/index.ts +++ b/packages/grid-shared-react/src/components/options/index.ts @@ -9,3 +9,5 @@ export { Caption } from './caption'; export type { CaptionProps } from './caption'; +export { Data } from './data/Data'; +export type { DataProps, DataColumns, DataColumnValue } from './data/Data'; diff --git a/packages/grid-shared-react/src/index.ts b/packages/grid-shared-react/src/index.ts index 445d13c..ad49409 100644 --- a/packages/grid-shared-react/src/index.ts +++ b/packages/grid-shared-react/src/index.ts @@ -12,7 +12,7 @@ import { GridType, GridInstance } from './hooks/useGrid'; import type { GridProps, GridRefHandle } from './components/BaseGrid'; export { BaseGrid }; -export { Caption } from './components/options'; +export { Caption, Data } from './components/options'; export { getChildProps } from './utils/getChildProps'; -export type { CaptionProps } from './components/options'; +export type { CaptionProps, DataProps, DataColumns, DataColumnValue } from './components/options'; export type { GridType, GridInstance, GridProps, GridRefHandle }; diff --git a/packages/grid-shared-react/src/utils/getChildProps.test.tsx b/packages/grid-shared-react/src/utils/getChildProps.test.tsx new file mode 100644 index 0000000..1498a4e --- /dev/null +++ b/packages/grid-shared-react/src/utils/getChildProps.test.tsx @@ -0,0 +1,50 @@ +import { describe, it, expect } from 'vitest'; +import { Data } from '../components/options/data/Data'; +import { getChildProps } from './getChildProps'; + +describe('getChildProps', () => { + it('maps Data columns to options.data.columns', () => { + const columns = { + name: ['Alice', 'Bob'], + age: [23, 34] + }; + + expect(getChildProps()).toEqual({ + data: { + columns + } + }); + }); + + it('maps all Data props to options.data', () => { + const columns = { + name: ['Alice'] + }; + const connector = { id: 'csv' }; + const dataTable = { id: 'table-1' }; + + expect( + getChildProps( + + ) + ).toEqual({ + data: { + providerType: 'local', + autogenerateColumns: false, + columns, + connector, + dataTable, + updateOnChange: true, + idColumn: 'id' + } + }); + }); +}); diff --git a/packages/grid-shared-react/tsconfig.json b/packages/grid-shared-react/tsconfig.json index b1b3107..f1d16c9 100644 --- a/packages/grid-shared-react/tsconfig.json +++ b/packages/grid-shared-react/tsconfig.json @@ -1,4 +1,4 @@ { "extends": "../../tsconfig.base.json", - "include": ["src", "tests"] + "include": ["src"] } From 88fe92ac9eb4cbd2067999345e4a9d5450215fa2 Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Wed, 1 Jul 2026 13:29:41 +0200 Subject: [PATCH 08/51] Added Data, Columns and Column components. --- .../grid-lite/components-react/src/App.tsx | 72 +++++++++-- .../grid-lite/components-react/vite.config.ts | 8 ++ packages/grid-lite-react/src/Grid.tsx | 20 ++- packages/grid-lite-react/src/index.ts | 10 +- packages/grid-pro-react/src/Grid.tsx | 20 ++- packages/grid-pro-react/src/index.ts | 10 +- .../src/components/BaseGridOptions.ts | 4 + .../src/components/options/columns/Column.tsx | 21 ++++ .../components/options/columns/Columns.tsx | 20 +++ .../components/options/columns/columnProps.ts | 80 ++++++++++++ .../src/components/options/data/Data.tsx | 11 ++ .../src/components/options/index.ts | 10 ++ .../grid-shared-react/src/hooks/useGrid.ts | 8 +- packages/grid-shared-react/src/index.ts | 15 ++- .../src/utils/getChildProps.ts | 116 +++++++++++++++++- .../src/utils/mappers/columnOptions.ts | 24 ++++ .../src/utils/mappers/mapPrefixedProps.ts | 57 +++++++++ 17 files changed, 478 insertions(+), 28 deletions(-) create mode 100644 packages/grid-shared-react/src/components/options/columns/Column.tsx create mode 100644 packages/grid-shared-react/src/components/options/columns/Columns.tsx create mode 100644 packages/grid-shared-react/src/components/options/columns/columnProps.ts create mode 100644 packages/grid-shared-react/src/utils/mappers/columnOptions.ts create mode 100644 packages/grid-shared-react/src/utils/mappers/mapPrefixedProps.ts diff --git a/examples/grid-lite/components-react/src/App.tsx b/examples/grid-lite/components-react/src/App.tsx index 939c0bc..26cf85e 100644 --- a/examples/grid-lite/components-react/src/App.tsx +++ b/examples/grid-lite/components-react/src/App.tsx @@ -6,7 +6,9 @@ import { Grid, Caption, Data, - DataTable + DataTable, + Columns, + Column } from '@highcharts/grid-lite-react'; function App() { @@ -63,17 +65,73 @@ function App() { Grid Caption - {/* -
Grid Header
- Grid Cell -
*/} + > + + + + + + + +
{/* Grid Description */} {/* Grid Pagination */}
diff --git a/examples/grid-lite/components-react/vite.config.ts b/examples/grid-lite/components-react/vite.config.ts index adf42ba..2e3f28b 100644 --- a/examples/grid-lite/components-react/vite.config.ts +++ b/examples/grid-lite/components-react/vite.config.ts @@ -9,6 +9,14 @@ export default defineConfig({ plugins: [react()], resolve: { alias: [ + { + find: '@highcharts/grid-lite-react', + replacement: resolve(__dirname, '../../../packages/grid-lite-react/src/index.ts') + }, + { + find: '@highcharts/grid-shared-react', + replacement: resolve(__dirname, '../../../packages/grid-shared-react/src/index.ts') + }, { find: /^@highcharts\/grid-lite(\/.*)?$/, replacement: resolve(__dirname, 'node_modules/@highcharts/grid-lite$1') diff --git a/packages/grid-lite-react/src/Grid.tsx b/packages/grid-lite-react/src/Grid.tsx index f7851aa..aa47803 100644 --- a/packages/grid-lite-react/src/Grid.tsx +++ b/packages/grid-lite-react/src/Grid.tsx @@ -20,10 +20,24 @@ import type { Options } from '@highcharts/grid-lite/es-modules/Grid/Core/Options 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(getChildProps(children), options ?? {}) as Options, - [children, options] + () => merge(childOptions, options ?? {}) as Options, + [childOptions, options] ); - return ; + return ( + + ); } diff --git a/packages/grid-lite-react/src/index.ts b/packages/grid-lite-react/src/index.ts index abf3c20..4d0a248 100644 --- a/packages/grid-lite-react/src/index.ts +++ b/packages/grid-lite-react/src/index.ts @@ -11,7 +11,7 @@ import GridLite from '@highcharts/grid-lite'; export { default as Grid } from './Grid'; export { default as GridLite } from './Grid'; -export { Caption, Data } from '@highcharts/grid-shared-react'; +export { Caption, Data, Columns, Column } from '@highcharts/grid-shared-react'; export { DataTable, DataConnector } from '@highcharts/grid-lite'; export { merge } from '@highcharts/grid-lite/es-modules/Shared/Utilities.js'; export type { @@ -20,6 +20,12 @@ export type { CaptionProps, DataProps, DataColumns, - DataColumnValue + DataColumnValue, + ColumnsProps, + ColumnProps, + ColumnOptionsProps, + ColumnDataType, + ColumnSortingOrder, + CellValueGetterContext } from '@highcharts/grid-shared-react'; export type GridOptions = GridLite.Options; diff --git a/packages/grid-pro-react/src/Grid.tsx b/packages/grid-pro-react/src/Grid.tsx index fc7f2ba..c236cfd 100644 --- a/packages/grid-pro-react/src/Grid.tsx +++ b/packages/grid-pro-react/src/Grid.tsx @@ -20,10 +20,24 @@ 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; + + return columns?.map((column) => column.id).join('\0') ?? ''; + }, [childOptions]); const gridOptions = useMemo( - () => merge(getChildProps(children), options ?? {}) as Options, - [children, options] + () => merge(childOptions, options ?? {}) as Options, + [childOptions, options] ); - return ; + return ( + + ); } diff --git a/packages/grid-pro-react/src/index.ts b/packages/grid-pro-react/src/index.ts index f62dff1..0cb0930 100644 --- a/packages/grid-pro-react/src/index.ts +++ b/packages/grid-pro-react/src/index.ts @@ -11,7 +11,7 @@ import GridPro from '@highcharts/grid-pro'; export { default as Grid } from './Grid'; export { default as GridPro } from './Grid'; -export { Caption, Data } from '@highcharts/grid-shared-react'; +export { Caption, Data, Columns, Column } from '@highcharts/grid-shared-react'; export { DataTable, DataConnector } from '@highcharts/grid-pro'; export { merge } from '@highcharts/grid-pro/es-modules/Shared/Utilities.js'; export type { @@ -20,6 +20,12 @@ export type { CaptionProps, DataProps, DataColumns, - DataColumnValue + DataColumnValue, + ColumnsProps, + ColumnProps, + ColumnOptionsProps, + ColumnDataType, + ColumnSortingOrder, + CellValueGetterContext } from '@highcharts/grid-shared-react'; export type GridOptions = GridPro.Options; diff --git a/packages/grid-shared-react/src/components/BaseGridOptions.ts b/packages/grid-shared-react/src/components/BaseGridOptions.ts index 9440edc..d35d54c 100644 --- a/packages/grid-shared-react/src/components/BaseGridOptions.ts +++ b/packages/grid-shared-react/src/components/BaseGridOptions.ts @@ -22,6 +22,10 @@ export interface BaseGridOptions { childOption?: string; defaultOptions?: Record; isArrayType?: boolean; + /** + * Special parsing role for column-related components. + */ + role?: 'columnsContainer' | 'column'; } /** diff --git a/packages/grid-shared-react/src/components/options/columns/Column.tsx b/packages/grid-shared-react/src/components/options/columns/Column.tsx new file mode 100644 index 0000000..f6c1d60 --- /dev/null +++ b/packages/grid-shared-react/src/components/options/columns/Column.tsx @@ -0,0 +1,21 @@ +/** + * Grid React integration. + * Copyright (c) 2025, Highsoft + * + * A valid license is required for using this software. + * See highcharts.com/license + * + */ + +import type { ColumnProps } from './columnProps'; + +export function Column(_props: ColumnProps) { + return null; +} + +Column._GridReact = { + type: 'Grid_Option', + gridOption: 'columns', + role: 'column', + isArrayType: true +}; diff --git a/packages/grid-shared-react/src/components/options/columns/Columns.tsx b/packages/grid-shared-react/src/components/options/columns/Columns.tsx new file mode 100644 index 0000000..939903f --- /dev/null +++ b/packages/grid-shared-react/src/components/options/columns/Columns.tsx @@ -0,0 +1,20 @@ +/** + * Grid React integration. + * Copyright (c) 2025, Highsoft + * + * A valid license is required for using this software. + * See highcharts.com/license + * + */ + +import type { ColumnsProps } from './columnProps'; + +export function Columns(_props: ColumnsProps) { + return null; +} + +Columns._GridReact = { + type: 'Grid_Option', + gridOption: 'columnDefaults', + role: 'columnsContainer' +}; diff --git a/packages/grid-shared-react/src/components/options/columns/columnProps.ts b/packages/grid-shared-react/src/components/options/columns/columnProps.ts new file mode 100644 index 0000000..5d53aae --- /dev/null +++ b/packages/grid-shared-react/src/components/options/columns/columnProps.ts @@ -0,0 +1,80 @@ +/** + * Grid React integration. + * Copyright (c) 2025, Highsoft + * + * A valid license is required for using this software. + * See highcharts.com/license + * + */ + +import type { ReactNode } from 'react'; + +export type ColumnDataType = 'string' | 'number' | 'boolean' | 'datetime'; + +export type ColumnSortingOrder = 'asc' | 'desc' | null; + +/** + * `this` context passed to `cellValueGetter` by Grid Core. + */ +export interface CellValueGetterContext { + row: { + index: number; + }; +} + +/** + * Shared column options (`columnDefaults` and per-column overrides). + */ +export interface ColumnOptionsProps { + dataType?: ColumnDataType; + width?: number | string; + sortingEnabled?: boolean; + sortingOrder?: ColumnSortingOrder; + sortingPriority?: number; + sortingOrderSequence?: ColumnSortingOrder[]; + sortingCompare?: (a: unknown, b: unknown) => number; + filteringEnabled?: boolean; + filteringInline?: boolean; + filteringCondition?: string; + filteringValue?: string | number | boolean | null; + headerClassName?: string; + headerFormat?: string; + headerFormatter?: (this: unknown) => string; + headerStyle?: unknown; + cellRowHeader?: boolean; + cellClassName?: string; + cellFormat?: string; + cellFormatter?: (this: unknown) => string; + /** + * Custom cell value resolver. `this` is the Grid table cell (`row.index` + * is the row index in the presentation data). + */ + cellValueGetter?: (this: CellValueGetterContext) => unknown; + cellContextMenu?: { + enabled?: boolean; + items?: unknown[]; + }; + cellStyle?: unknown; + style?: unknown; + exportable?: boolean; +} + +export type ColumnsProps = ColumnOptionsProps & { + children?: ReactNode; +}; + +export interface ColumnProps extends ColumnOptionsProps { + /** + * HTML `id` attribute for styling hooks. Not passed to Grid options. + */ + id?: string; + /** + * References the column to configure (data field id) and applies the + * nested options from this element — header, cells, sorting, filtering, etc. + * + * Becomes `options.columns[].id` in Grid Core (same identifier). + */ + columnId?: string; + className?: string; + enabled?: boolean; +} diff --git a/packages/grid-shared-react/src/components/options/data/Data.tsx b/packages/grid-shared-react/src/components/options/data/Data.tsx index da24ab3..933b32e 100644 --- a/packages/grid-shared-react/src/components/options/data/Data.tsx +++ b/packages/grid-shared-react/src/components/options/data/Data.tsx @@ -7,6 +7,8 @@ * */ +import type { ReactNode } from 'react'; + export type DataColumnValue = boolean | null | number | string | undefined; export type DataColumns = Record>; @@ -22,6 +24,10 @@ export interface DataProps { * Whether columns should be generated automatically from data source * column ids. * + * Defaults to `true`. When declarative `` children are used + * inside nested ``, the React wrapper sets this to `false` + * unless you pass this prop explicitly. + * * @default true */ autogenerateColumns?: boolean; @@ -47,6 +53,11 @@ export interface DataProps { * The column ID that contains the stable, unique row IDs. */ idColumn?: string; + /** + * Column configuration. Use `` with `` children to + * define which data fields are shown and how they are rendered. + */ + children?: ReactNode; } export function Data(_props: DataProps) { diff --git a/packages/grid-shared-react/src/components/options/index.ts b/packages/grid-shared-react/src/components/options/index.ts index 5f099f9..4a301f0 100644 --- a/packages/grid-shared-react/src/components/options/index.ts +++ b/packages/grid-shared-react/src/components/options/index.ts @@ -11,3 +11,13 @@ export { Caption } from './caption'; export type { CaptionProps } from './caption'; export { Data } from './data/Data'; export type { DataProps, DataColumns, DataColumnValue } from './data/Data'; +export { Columns } from './columns/Columns'; +export { Column } from './columns/Column'; +export type { + ColumnsProps, + ColumnProps, + ColumnOptionsProps, + ColumnDataType, + ColumnSortingOrder, + CellValueGetterContext +} from './columns/columnProps'; diff --git a/packages/grid-shared-react/src/hooks/useGrid.ts b/packages/grid-shared-react/src/hooks/useGrid.ts index adf07cf..c5b752f 100644 --- a/packages/grid-shared-react/src/hooks/useGrid.ts +++ b/packages/grid-shared-react/src/hooks/useGrid.ts @@ -14,7 +14,7 @@ import { useEffect, RefObject, useRef } from 'react'; */ export interface GridInstance { destroy(): void; - update(options: TOptions, redraw?: boolean): void; + update(options: TOptions, redraw?: boolean, oneToOne?: boolean): void; } /** @@ -89,7 +89,7 @@ export function useGrid({ // Apply any pending options that came in while we were initializing if (pendingOptionsRef.current !== void 0) { - grid.update(pendingOptionsRef.current, true); + grid.update(pendingOptionsRef.current, true, true); pendingOptionsRef.current = void 0; } @@ -122,8 +122,8 @@ export function useGrid({ } if (currGridRef.current) { - // Grid exists, update it directly - currGridRef.current.update(options, true); + // Declarative React options replace the previous snapshot (oneToOne). + currGridRef.current.update(options, true, true); } else { // Grid still initializing, queue the update pendingOptionsRef.current = options; diff --git a/packages/grid-shared-react/src/index.ts b/packages/grid-shared-react/src/index.ts index ad49409..bcdd026 100644 --- a/packages/grid-shared-react/src/index.ts +++ b/packages/grid-shared-react/src/index.ts @@ -12,7 +12,18 @@ import { GridType, GridInstance } from './hooks/useGrid'; import type { GridProps, GridRefHandle } from './components/BaseGrid'; export { BaseGrid }; -export { Caption, Data } from './components/options'; +export { Caption, Data, Columns, Column } from './components/options'; export { getChildProps } from './utils/getChildProps'; -export type { CaptionProps, DataProps, DataColumns, DataColumnValue } from './components/options'; +export type { + CaptionProps, + DataProps, + DataColumns, + DataColumnValue, + ColumnsProps, + ColumnProps, + ColumnOptionsProps, + ColumnDataType, + ColumnSortingOrder, + CellValueGetterContext +} from './components/options'; export type { GridType, GridInstance, GridProps, GridRefHandle }; diff --git a/packages/grid-shared-react/src/utils/getChildProps.ts b/packages/grid-shared-react/src/utils/getChildProps.ts index 3d11b1a..029dc08 100644 --- a/packages/grid-shared-react/src/utils/getChildProps.ts +++ b/packages/grid-shared-react/src/utils/getChildProps.ts @@ -7,8 +7,9 @@ * */ -import { isValidElement, ReactElement, ReactNode } from 'react'; +import { Fragment, isValidElement, ReactElement, ReactNode } from 'react'; import type { BaseGridOptionsComponent, BaseGridOptions } from '../components/BaseGridOptions'; +import { normalizeColumnOptions } from './mappers/columnOptions'; function objInsert( obj: Record, @@ -75,6 +76,22 @@ 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) { + return flattenChildren((childNodes.props as { children?: ReactNode }).children); + } + + return [childNodes]; +} + function getEffectiveMeta( component: BaseGridOptionsComponent, parentMeta?: BaseGridOptions @@ -96,12 +113,49 @@ function getEffectiveMeta( }; } +function parseColumnElement(child: ReactElement): Record { + const { children: _ignored, columnId, id: _cssId, ...props } = getChildPropsFromElement(child); + const options = normalizeColumnOptions(props); + + // columnId selects the column; Core expects the same value as `id`. + if (columnId !== void 0) { + options.id = columnId; + } + + return options; +} + +function pushColumn( + optionsFromChildren: Record, + child: ReactElement +): void { + const columns = (optionsFromChildren.columns ?? ( + optionsFromChildren.columns = [] + )) as Record[]; + + columns.push(parseColumnElement(child)); +} + export function getChildProps(children: ReactNode): Record { const optionsFromChildren: Record = {}; - const resolvedChildren = (Array.isArray(children) ? children.flat() : [children]) + const resolvedChildren = flattenChildren(children) .map((child) => resolveOptionChild(child)) .filter((child): child is ReactElement => child !== null); + function handleDataChildren(childNodes: ReactNode): void { + for (const node of flattenChildren(childNodes)) { + if (!isReactElement(node)) { + continue; + } + + const role = getOptionComponent(node.type)?._GridReact.role; + + if (role === 'columnsContainer' || role === 'column') { + handleChild(node); + } + } + } + function handleChildren( childNodes: ReactNode, obj: Record, @@ -146,7 +200,36 @@ export function getChildProps(children: ReactNode): Record { const meta = getEffectiveMeta(component, parentMeta); - if (!meta.gridOption) { + if (!meta.gridOption && meta.role !== 'columnsContainer') { + return; + } + + const childProps = getChildPropsFromElement(child); + const { children: childChildren, ...props } = childProps; + + if (meta.role === 'columnsContainer') { + optionsFromChildren.columnDefaults = normalizeColumnOptions(props); + + for (const node of flattenChildren(childChildren as ReactNode)) { + if (isReactElement(node) && getOptionComponent(node.type)?._GridReact.role === 'column') { + pushColumn(optionsFromChildren, node); + } + } + return; + } + + if (meta.role === 'column') { + pushColumn(optionsFromChildren, child); + return; + } + + if (meta.gridOption === 'data') { + const dataOptions = (optionsFromChildren.data ?? ( + optionsFromChildren.data = {} + )) as Record; + + Object.assign(dataOptions, props); + handleDataChildren(childChildren as ReactNode); return; } @@ -155,8 +238,6 @@ export function getChildProps(children: ReactNode): Record { ); const parentIsArray = Array.isArray(optionParent); const insertInto = parentIsArray ? {} : optionParent as Record; - const childProps = getChildPropsFromElement(child); - const { children: childChildren, ...props } = childProps; if (meta.defaultOptions) { Object.assign(insertInto, meta.defaultOptions); @@ -181,9 +262,34 @@ export function getChildProps(children: ReactNode): Record { handleChild(child); } + applyDeclarativeColumnDefaults(optionsFromChildren); + return optionsFromChildren; } +/** + * When declarative `` components are present, only those columns + * should render unless `data.autogenerateColumns` is set explicitly on ``. + */ +function applyDeclarativeColumnDefaults( + optionsFromChildren: Record +): void { + const columns = optionsFromChildren.columns; + + if (!Array.isArray(columns) || columns.length === 0) { + return; + } + + const data = isObject(optionsFromChildren.data) ? + { ...optionsFromChildren.data } : + {}; + + if (!('autogenerateColumns' in data)) { + data.autogenerateColumns = false; + optionsFromChildren.data = data; + } +} + function isOptionElement(child: ReactElement): boolean { return getOptionComponent(child.type) !== null; } diff --git a/packages/grid-shared-react/src/utils/mappers/columnOptions.ts b/packages/grid-shared-react/src/utils/mappers/columnOptions.ts new file mode 100644 index 0000000..300c39c --- /dev/null +++ b/packages/grid-shared-react/src/utils/mappers/columnOptions.ts @@ -0,0 +1,24 @@ +/** + * Grid React integration. + * Copyright (c) 2025, Highsoft + * + * A valid license is required for using this software. + * See highcharts.com/license + * + */ + +import { mapPrefixedProps } from './mapPrefixedProps'; + +/** Flat prop prefix → nested Grid option key for columns. */ +const COLUMN_PROP_PREFIXES = { + sorting: 'sorting', + filtering: 'filtering', + header: 'header', + cell: 'cells' +} as const; + +export function normalizeColumnOptions( + props: Record +): Record { + return mapPrefixedProps(props, COLUMN_PROP_PREFIXES); +} diff --git a/packages/grid-shared-react/src/utils/mappers/mapPrefixedProps.ts b/packages/grid-shared-react/src/utils/mappers/mapPrefixedProps.ts new file mode 100644 index 0000000..9fdd271 --- /dev/null +++ b/packages/grid-shared-react/src/utils/mappers/mapPrefixedProps.ts @@ -0,0 +1,57 @@ +/** + * Grid React integration. + * Copyright (c) 2025, Highsoft + * + * A valid license is required for using this software. + * See highcharts.com/license + * + */ + +/** + * Maps flat props with a shared prefix into nested Grid option objects. + * + * Convention: `{prefix}{OptionKey}` → `{groupKey}.{optionKey}` + * + * @example + * mapPrefixedProps( + * { sortingEnabled: true, sortingOrder: 'asc', width: 120 }, + * { sorting: 'sorting' } + * ); + * // => { width: 120, sorting: { enabled: true, order: 'asc' } } + */ +export type PrefixedPropMap = Record; + +export function mapPrefixedProps( + props: Record, + prefixToGroup: PrefixedPropMap +): Record { + const result = { ...props }; + const groups: Record> = {}; + const prefixes = Object.keys(prefixToGroup).sort((a, b) => b.length - a.length); + + for (const flatKey of Object.keys(result)) { + const prefix = prefixes.find( + (candidate) => flatKey.startsWith(candidate) && flatKey.length > candidate.length + ); + + if (!prefix) { + continue; + } + + const groupKey = prefixToGroup[prefix]; + const nestedKey = toNestedKey(flatKey.slice(prefix.length)); + + (groups[groupKey] ??= {})[nestedKey] = result[flatKey]; + delete result[flatKey]; + } + + for (const [groupKey, nested] of Object.entries(groups)) { + result[groupKey] = nested; + } + + return result; +} + +function toNestedKey(segment: string): string { + return segment.charAt(0).toLowerCase() + segment.slice(1); +} From fa3e1fafed8f80d6975a38e61bbc2b4a95f81442 Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Thu, 2 Jul 2026 13:57:02 +0200 Subject: [PATCH 09/51] Added ColumnDefaults compomnent. --- .../options/columns/ColumnDefaults.tsx | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 packages/grid-shared-react/src/components/options/columns/ColumnDefaults.tsx diff --git a/packages/grid-shared-react/src/components/options/columns/ColumnDefaults.tsx b/packages/grid-shared-react/src/components/options/columns/ColumnDefaults.tsx new file mode 100644 index 0000000..cf2ce37 --- /dev/null +++ b/packages/grid-shared-react/src/components/options/columns/ColumnDefaults.tsx @@ -0,0 +1,19 @@ +/** + * Grid React integration. + * Copyright (c) 2025, Highsoft + * + * A valid license is required for using this software. + * See highcharts.com/license + * + */ + +import type { ColumnDefaultsProps } from './columnProps'; + +export function ColumnDefaults(_props: ColumnDefaultsProps) { + return null; +} + +ColumnDefaults._GridReact = { + type: 'Grid_Option', + gridOption: 'columnDefaults' +}; From c4e962d45eab44a8e7921944bebb70e95baadd51 Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Mon, 6 Jul 2026 11:45:53 +0200 Subject: [PATCH 10/51] Refactored Columsn and Data options. --- .../grid-lite/components-react/src/App.tsx | 142 +++++++++--------- packages/grid-lite-react/src/index.ts | 3 +- packages/grid-pro-react/src/index.ts | 3 +- .../src/components/BaseGridOptions.ts | 4 - .../src/components/options/columns/Column.tsx | 1 - .../options/columns/ColumnDefaults.tsx | 4 +- .../components/options/columns/Columns.tsx | 20 --- .../components/options/columns/columnProps.ts | 10 +- .../src/components/options/data/Data.tsx | 13 +- .../src/components/options/index.ts | 3 +- packages/grid-shared-react/src/index.ts | 3 +- .../src/utils/getChildProps.ts | 36 +---- 12 files changed, 86 insertions(+), 156 deletions(-) delete mode 100644 packages/grid-shared-react/src/components/options/columns/Columns.tsx diff --git a/examples/grid-lite/components-react/src/App.tsx b/examples/grid-lite/components-react/src/App.tsx index b0d8c05..eb439eb 100644 --- a/examples/grid-lite/components-react/src/App.tsx +++ b/examples/grid-lite/components-react/src/App.tsx @@ -7,7 +7,11 @@ import { Caption, Data, DataTable, +<<<<<<< HEAD Columns, +======= + ColumnDefaults, +>>>>>>> 868635b (Refactored Columsn and Data options.) Column, Description } from '@highcharts/grid-lite-react'; @@ -37,14 +41,14 @@ function App() { }); // Data Table - const dataTable = new DataTable({ - columns: { - name: ['DATATABLE', 'Bob', 'Charlie', 'David', 'Eve'], - age: [23, 34, 45, 56, 67], - city: ['New York', 'Oslo', 'Paris', 'Tokyo', 'London'], - salary: [50000, 60000, 70000, 80000, 90000] - } - }); + // const dataTable = new DataTable({ + // columns: { + // name: ['DATATABLE', 'Bob', 'Charlie', 'David', 'Eve'], + // age: [23, 34, 45, 56, 67], + // city: ['New York', 'Oslo', 'Paris', 'Tokyo', 'London'], + // salary: [50000, 60000, 70000, 80000, 90000] + // } + // }); // ==== ACTIONS ==== const onButtonClick = () => { @@ -68,71 +72,69 @@ function App() { // gridRef={grid} callback={onGridCallback} > - Grid Caption - - - - - - - - + /> + + Grid Caption v2.1 + + + + + Grid Description {/* Grid Pagination */} diff --git a/packages/grid-lite-react/src/index.ts b/packages/grid-lite-react/src/index.ts index 906e41f..4272e3d 100644 --- a/packages/grid-lite-react/src/index.ts +++ b/packages/grid-lite-react/src/index.ts @@ -11,7 +11,7 @@ import GridLite from '@highcharts/grid-lite'; export { default as Grid } from './Grid'; export { default as GridLite } from './Grid'; -export { Caption, Data, Columns, Column, Description } from '@highcharts/grid-shared-react'; +export { Caption, Data, ColumnDefaults, Column, Description } from '@highcharts/grid-shared-react'; export { DataTable, DataConnector } from '@highcharts/grid-lite'; export { merge } from '@highcharts/grid-lite/es-modules/Shared/Utilities.js'; export type { @@ -22,7 +22,6 @@ export type { DataProps, DataColumns, DataColumnValue, - ColumnsProps, ColumnProps, ColumnOptionsProps, ColumnDataType, diff --git a/packages/grid-pro-react/src/index.ts b/packages/grid-pro-react/src/index.ts index 8aa80fc..a8a0200 100644 --- a/packages/grid-pro-react/src/index.ts +++ b/packages/grid-pro-react/src/index.ts @@ -11,7 +11,7 @@ import GridPro from '@highcharts/grid-pro'; export { default as Grid } from './Grid'; export { default as GridPro } from './Grid'; -export { Caption, Data, Columns, Column, Description } from '@highcharts/grid-shared-react'; +export { Caption, Data, ColumnDefaults, Column, Description } from '@highcharts/grid-shared-react'; export { DataTable, DataConnector } from '@highcharts/grid-pro'; export { merge } from '@highcharts/grid-pro/es-modules/Shared/Utilities.js'; export type { @@ -22,7 +22,6 @@ export type { DataProps, DataColumns, DataColumnValue, - ColumnsProps, ColumnProps, ColumnOptionsProps, ColumnDataType, diff --git a/packages/grid-shared-react/src/components/BaseGridOptions.ts b/packages/grid-shared-react/src/components/BaseGridOptions.ts index d35d54c..9440edc 100644 --- a/packages/grid-shared-react/src/components/BaseGridOptions.ts +++ b/packages/grid-shared-react/src/components/BaseGridOptions.ts @@ -22,10 +22,6 @@ export interface BaseGridOptions { childOption?: string; defaultOptions?: Record; isArrayType?: boolean; - /** - * Special parsing role for column-related components. - */ - role?: 'columnsContainer' | 'column'; } /** diff --git a/packages/grid-shared-react/src/components/options/columns/Column.tsx b/packages/grid-shared-react/src/components/options/columns/Column.tsx index f6c1d60..f5cd44a 100644 --- a/packages/grid-shared-react/src/components/options/columns/Column.tsx +++ b/packages/grid-shared-react/src/components/options/columns/Column.tsx @@ -16,6 +16,5 @@ export function Column(_props: ColumnProps) { Column._GridReact = { type: 'Grid_Option', gridOption: 'columns', - role: 'column', isArrayType: true }; diff --git a/packages/grid-shared-react/src/components/options/columns/ColumnDefaults.tsx b/packages/grid-shared-react/src/components/options/columns/ColumnDefaults.tsx index cf2ce37..d8569ba 100644 --- a/packages/grid-shared-react/src/components/options/columns/ColumnDefaults.tsx +++ b/packages/grid-shared-react/src/components/options/columns/ColumnDefaults.tsx @@ -7,9 +7,9 @@ * */ -import type { ColumnDefaultsProps } from './columnProps'; +import type { ColumnOptionsProps } from './columnProps'; -export function ColumnDefaults(_props: ColumnDefaultsProps) { +export function ColumnDefaults(_props: ColumnOptionsProps) { return null; } diff --git a/packages/grid-shared-react/src/components/options/columns/Columns.tsx b/packages/grid-shared-react/src/components/options/columns/Columns.tsx deleted file mode 100644 index 939903f..0000000 --- a/packages/grid-shared-react/src/components/options/columns/Columns.tsx +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Grid React integration. - * Copyright (c) 2025, Highsoft - * - * A valid license is required for using this software. - * See highcharts.com/license - * - */ - -import type { ColumnsProps } from './columnProps'; - -export function Columns(_props: ColumnsProps) { - return null; -} - -Columns._GridReact = { - type: 'Grid_Option', - gridOption: 'columnDefaults', - role: 'columnsContainer' -}; diff --git a/packages/grid-shared-react/src/components/options/columns/columnProps.ts b/packages/grid-shared-react/src/components/options/columns/columnProps.ts index 5d53aae..1452f99 100644 --- a/packages/grid-shared-react/src/components/options/columns/columnProps.ts +++ b/packages/grid-shared-react/src/components/options/columns/columnProps.ts @@ -7,8 +7,6 @@ * */ -import type { ReactNode } from 'react'; - export type ColumnDataType = 'string' | 'number' | 'boolean' | 'datetime'; export type ColumnSortingOrder = 'asc' | 'desc' | null; @@ -59,18 +57,14 @@ export interface ColumnOptionsProps { exportable?: boolean; } -export type ColumnsProps = ColumnOptionsProps & { - children?: ReactNode; -}; - export interface ColumnProps extends ColumnOptionsProps { /** * HTML `id` attribute for styling hooks. Not passed to Grid options. */ id?: string; /** - * References the column to configure (data field id) and applies the - * nested options from this element — header, cells, sorting, filtering, etc. + * References the column to configure (data field id). Maps header, cells, + * sorting, filtering, etc. to Grid Core column options. * * Becomes `options.columns[].id` in Grid Core (same identifier). */ diff --git a/packages/grid-shared-react/src/components/options/data/Data.tsx b/packages/grid-shared-react/src/components/options/data/Data.tsx index 933b32e..7ec764a 100644 --- a/packages/grid-shared-react/src/components/options/data/Data.tsx +++ b/packages/grid-shared-react/src/components/options/data/Data.tsx @@ -7,8 +7,6 @@ * */ -import type { ReactNode } from 'react'; - export type DataColumnValue = boolean | null | number | string | undefined; export type DataColumns = Record>; @@ -24,9 +22,9 @@ export interface DataProps { * Whether columns should be generated automatically from data source * column ids. * - * Defaults to `true`. When declarative `` children are used - * inside nested ``, the React wrapper sets this to `false` - * unless you pass this prop explicitly. + * Defaults to `true`. When declarative `` components are used, + * the React wrapper sets this to `false` unless you pass this prop + * explicitly. * * @default true */ @@ -53,11 +51,6 @@ export interface DataProps { * The column ID that contains the stable, unique row IDs. */ idColumn?: string; - /** - * Column configuration. Use `` with `` children to - * define which data fields are shown and how they are rendered. - */ - children?: ReactNode; } export function Data(_props: DataProps) { diff --git a/packages/grid-shared-react/src/components/options/index.ts b/packages/grid-shared-react/src/components/options/index.ts index d90c37f..dbbf7f4 100644 --- a/packages/grid-shared-react/src/components/options/index.ts +++ b/packages/grid-shared-react/src/components/options/index.ts @@ -11,10 +11,9 @@ export { Caption } from './caption'; export type { CaptionProps } from './caption'; export { Data } from './data/Data'; export type { DataProps, DataColumns, DataColumnValue } from './data/Data'; -export { Columns } from './columns/Columns'; +export { ColumnDefaults } from './columns/ColumnDefaults'; export { Column } from './columns/Column'; export type { - ColumnsProps, ColumnProps, ColumnOptionsProps, ColumnDataType, diff --git a/packages/grid-shared-react/src/index.ts b/packages/grid-shared-react/src/index.ts index 6703780..3814da0 100644 --- a/packages/grid-shared-react/src/index.ts +++ b/packages/grid-shared-react/src/index.ts @@ -12,7 +12,7 @@ import { GridType, GridInstance } from './hooks/useGrid'; import type { GridProps, GridRefHandle } from './components/BaseGrid'; export { BaseGrid }; -export { Caption, Data, Columns, Column, Description } from './components/options'; +export { Caption, Data, ColumnDefaults, Column, Description } from './components/options'; export { getChildProps } from './utils/getChildProps'; export type { CaptionProps, @@ -20,7 +20,6 @@ export type { DataProps, DataColumns, DataColumnValue, - ColumnsProps, ColumnProps, ColumnOptionsProps, ColumnDataType, diff --git a/packages/grid-shared-react/src/utils/getChildProps.ts b/packages/grid-shared-react/src/utils/getChildProps.ts index 029dc08..572376d 100644 --- a/packages/grid-shared-react/src/utils/getChildProps.ts +++ b/packages/grid-shared-react/src/utils/getChildProps.ts @@ -142,20 +142,6 @@ export function getChildProps(children: ReactNode): Record { .map((child) => resolveOptionChild(child)) .filter((child): child is ReactElement => child !== null); - function handleDataChildren(childNodes: ReactNode): void { - for (const node of flattenChildren(childNodes)) { - if (!isReactElement(node)) { - continue; - } - - const role = getOptionComponent(node.type)?._GridReact.role; - - if (role === 'columnsContainer' || role === 'column') { - handleChild(node); - } - } - } - function handleChildren( childNodes: ReactNode, obj: Record, @@ -200,39 +186,23 @@ export function getChildProps(children: ReactNode): Record { const meta = getEffectiveMeta(component, parentMeta); - if (!meta.gridOption && meta.role !== 'columnsContainer') { + if (!meta.gridOption) { return; } const childProps = getChildPropsFromElement(child); const { children: childChildren, ...props } = childProps; - if (meta.role === 'columnsContainer') { + if (meta.gridOption === 'columnDefaults') { optionsFromChildren.columnDefaults = normalizeColumnOptions(props); - - for (const node of flattenChildren(childChildren as ReactNode)) { - if (isReactElement(node) && getOptionComponent(node.type)?._GridReact.role === 'column') { - pushColumn(optionsFromChildren, node); - } - } return; } - if (meta.role === 'column') { + if (meta.gridOption === 'columns') { pushColumn(optionsFromChildren, child); return; } - if (meta.gridOption === 'data') { - const dataOptions = (optionsFromChildren.data ?? ( - optionsFromChildren.data = {} - )) as Record; - - Object.assign(dataOptions, props); - handleDataChildren(childChildren as ReactNode); - return; - } - const optionParent = optionsFromChildren[meta.gridOption] ?? ( optionsFromChildren[meta.gridOption] = meta.isArrayType ? [] : {} ); From 18ff1791167c515564325b090e18362677ae6d10 Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Mon, 6 Jul 2026 11:49:24 +0200 Subject: [PATCH 11/51] Fixed conflicts. --- examples/grid-lite/components-react/src/App.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/examples/grid-lite/components-react/src/App.tsx b/examples/grid-lite/components-react/src/App.tsx index eb439eb..ac64cd3 100644 --- a/examples/grid-lite/components-react/src/App.tsx +++ b/examples/grid-lite/components-react/src/App.tsx @@ -7,11 +7,7 @@ import { Caption, Data, DataTable, -<<<<<<< HEAD - Columns, -======= ColumnDefaults, ->>>>>>> 868635b (Refactored Columsn and Data options.) Column, Description } from '@highcharts/grid-lite-react'; From dc2402bbf22f3e27a8c801acac8ded8a88e972e1 Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Mon, 6 Jul 2026 12:57:08 +0200 Subject: [PATCH 12/51] Added basic pagination component. --- .../grid-lite/components-react/src/App.tsx | 29 ++++++- .../grid-lite/components-react/src/index.css | 7 ++ packages/grid-lite-react/src/index.ts | 5 +- packages/grid-pro-react/src/index.ts | 5 +- .../src/components/options/index.ts | 2 + .../options/pagination/Pagination.tsx | 21 +++++ .../components/options/pagination/index.ts | 11 +++ .../options/pagination/paginationProps.ts | 57 +++++++++++++ packages/grid-shared-react/src/index.ts | 5 +- .../src/utils/getChildProps.test.tsx | 50 ----------- .../src/utils/getChildProps.ts | 8 +- .../mappers/{ => column}/columnOptions.ts | 2 +- .../src/utils/mappers/column/index.ts | 10 +++ .../src/utils/mappers/pagination/index.ts | 10 +++ .../mappers/pagination/paginationOptions.ts | 84 +++++++++++++++++++ 15 files changed, 245 insertions(+), 61 deletions(-) create mode 100644 packages/grid-shared-react/src/components/options/pagination/Pagination.tsx create mode 100644 packages/grid-shared-react/src/components/options/pagination/index.ts create mode 100644 packages/grid-shared-react/src/components/options/pagination/paginationProps.ts delete mode 100644 packages/grid-shared-react/src/utils/getChildProps.test.tsx rename packages/grid-shared-react/src/utils/mappers/{ => column}/columnOptions.ts (90%) create mode 100644 packages/grid-shared-react/src/utils/mappers/column/index.ts create mode 100644 packages/grid-shared-react/src/utils/mappers/pagination/index.ts create mode 100644 packages/grid-shared-react/src/utils/mappers/pagination/paginationOptions.ts diff --git a/examples/grid-lite/components-react/src/App.tsx b/examples/grid-lite/components-react/src/App.tsx index ac64cd3..caa4be7 100644 --- a/examples/grid-lite/components-react/src/App.tsx +++ b/examples/grid-lite/components-react/src/App.tsx @@ -9,7 +9,8 @@ import { DataTable, ColumnDefaults, Column, - Description + Description, + Pagination } from '@highcharts/grid-lite-react'; function App() { @@ -61,6 +62,13 @@ function App() { console.info('(callback) grid:', grid); }; + // Pagination + // const [paginationEnabled, setPaginationEnabled] = useState(false); + + // const onPaginationClick = () => { + // setPaginationEnabled(true); + // }; + return ( <> Grid Description - {/* Grid Pagination */} + - +
+ + {/* */} +
); } diff --git a/examples/grid-lite/components-react/src/index.css b/examples/grid-lite/components-react/src/index.css index 04bacd8..9a63579 100644 --- a/examples/grid-lite/components-react/src/index.css +++ b/examples/grid-lite/components-react/src/index.css @@ -18,9 +18,16 @@ body { padding: 20px; } +#controls { + margin-top: 20px; + display: flex; + gap: 10px; +} + @media (prefers-color-scheme: dark) { body { background-color: #121212; color: #ffffff; } } + diff --git a/packages/grid-lite-react/src/index.ts b/packages/grid-lite-react/src/index.ts index 4272e3d..2929529 100644 --- a/packages/grid-lite-react/src/index.ts +++ b/packages/grid-lite-react/src/index.ts @@ -11,7 +11,7 @@ import GridLite from '@highcharts/grid-lite'; export { default as Grid } from './Grid'; export { default as GridLite } from './Grid'; -export { Caption, Data, ColumnDefaults, Column, Description } from '@highcharts/grid-shared-react'; +export { Caption, Data, ColumnDefaults, Column, Description, Pagination } from '@highcharts/grid-shared-react'; export { DataTable, DataConnector } from '@highcharts/grid-lite'; export { merge } from '@highcharts/grid-lite/es-modules/Shared/Utilities.js'; export type { @@ -26,6 +26,7 @@ export type { ColumnOptionsProps, ColumnDataType, ColumnSortingOrder, - CellValueGetterContext + CellValueGetterContext, + PaginationProps } from '@highcharts/grid-shared-react'; export type GridOptions = GridLite.Options; diff --git a/packages/grid-pro-react/src/index.ts b/packages/grid-pro-react/src/index.ts index a8a0200..9eaacc3 100644 --- a/packages/grid-pro-react/src/index.ts +++ b/packages/grid-pro-react/src/index.ts @@ -11,7 +11,7 @@ import GridPro from '@highcharts/grid-pro'; export { default as Grid } from './Grid'; export { default as GridPro } from './Grid'; -export { Caption, Data, ColumnDefaults, Column, Description } from '@highcharts/grid-shared-react'; +export { Caption, Data, ColumnDefaults, Column, Description, Pagination } from '@highcharts/grid-shared-react'; export { DataTable, DataConnector } from '@highcharts/grid-pro'; export { merge } from '@highcharts/grid-pro/es-modules/Shared/Utilities.js'; export type { @@ -26,6 +26,7 @@ export type { ColumnOptionsProps, ColumnDataType, ColumnSortingOrder, - CellValueGetterContext + CellValueGetterContext, + PaginationProps } from '@highcharts/grid-shared-react'; export type GridOptions = GridPro.Options; diff --git a/packages/grid-shared-react/src/components/options/index.ts b/packages/grid-shared-react/src/components/options/index.ts index dbbf7f4..67c6419 100644 --- a/packages/grid-shared-react/src/components/options/index.ts +++ b/packages/grid-shared-react/src/components/options/index.ts @@ -22,3 +22,5 @@ export type { } from './columns/columnProps'; export { Description } from './description'; export type { DescriptionProps } from './description'; +export { Pagination } from './pagination'; +export type { PaginationProps } from './pagination'; diff --git a/packages/grid-shared-react/src/components/options/pagination/Pagination.tsx b/packages/grid-shared-react/src/components/options/pagination/Pagination.tsx new file mode 100644 index 0000000..f2c9ad9 --- /dev/null +++ b/packages/grid-shared-react/src/components/options/pagination/Pagination.tsx @@ -0,0 +1,21 @@ +/** + * Grid React integration. + * Copyright (c) 2025, Highsoft + * + * A valid license is required for using this software. + * See highcharts.com/license + * + */ + +import type { PaginationProps } from './paginationProps'; + +export type { PaginationProps } from './paginationProps'; + +export function Pagination(_props: PaginationProps) { + return null; +} + +Pagination._GridReact = { + type: 'Grid_Option', + gridOption: 'pagination' +}; diff --git a/packages/grid-shared-react/src/components/options/pagination/index.ts b/packages/grid-shared-react/src/components/options/pagination/index.ts new file mode 100644 index 0000000..a225c83 --- /dev/null +++ b/packages/grid-shared-react/src/components/options/pagination/index.ts @@ -0,0 +1,11 @@ +/** + * Grid React integration. + * Copyright (c) 2025, Highsoft + * + * A valid license is required for using this software. + * See highcharts.com/license + * + */ + +export { Pagination } from './Pagination'; +export type { PaginationProps } from './Pagination'; diff --git a/packages/grid-shared-react/src/components/options/pagination/paginationProps.ts b/packages/grid-shared-react/src/components/options/pagination/paginationProps.ts new file mode 100644 index 0000000..a5f9a93 --- /dev/null +++ b/packages/grid-shared-react/src/components/options/pagination/paginationProps.ts @@ -0,0 +1,57 @@ +/** + * Grid React integration. + * Copyright (c) 2025, Highsoft + * + * A valid license is required for using this software. + * See highcharts.com/license + * + */ + +export interface PaginationProps { + /** + * Whether pagination should be rendered. + * Defaults to `true` when the `` component is used. + * Pass `false` to disable pagination while keeping other options. + */ + enabled?: boolean; + /** + * The current page number. + */ + page?: number; + /** + * Number of rows per page. + */ + pageSize?: number; + /** + * Alignment of pagination elements within the wrapper. + */ + align?: 'left' | 'center' | 'right' | 'distributed'; + /** + * Whether to show the page information text. + */ + pageInfo?: boolean; + /** + * Whether to show the page size selector. + */ + pageSizeSelector?: boolean; + /** + * Available options for the page size selector dropdown. + */ + pageSizeOptions?: number[]; + /** + * Whether to show numbered page buttons. + */ + pageButtons?: boolean; + /** + * Maximum number of page number buttons to show before using ellipsis. + */ + pageButtonsCount?: number; + /** + * Whether to show the first and last page navigation buttons. + */ + firstLast?: boolean; + /** + * Whether to show the previous and next page navigation buttons. + */ + previousNext?: boolean; +} diff --git a/packages/grid-shared-react/src/index.ts b/packages/grid-shared-react/src/index.ts index 3814da0..989215c 100644 --- a/packages/grid-shared-react/src/index.ts +++ b/packages/grid-shared-react/src/index.ts @@ -12,7 +12,7 @@ import { GridType, GridInstance } from './hooks/useGrid'; import type { GridProps, GridRefHandle } from './components/BaseGrid'; export { BaseGrid }; -export { Caption, Data, ColumnDefaults, Column, Description } from './components/options'; +export { Caption, Data, ColumnDefaults, Column, Description, Pagination } from './components/options'; export { getChildProps } from './utils/getChildProps'; export type { CaptionProps, @@ -24,6 +24,7 @@ export type { ColumnOptionsProps, ColumnDataType, ColumnSortingOrder, - CellValueGetterContext + CellValueGetterContext, + PaginationProps } from './components/options'; export type { GridType, GridInstance, GridProps, GridRefHandle }; diff --git a/packages/grid-shared-react/src/utils/getChildProps.test.tsx b/packages/grid-shared-react/src/utils/getChildProps.test.tsx deleted file mode 100644 index 1498a4e..0000000 --- a/packages/grid-shared-react/src/utils/getChildProps.test.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { describe, it, expect } from 'vitest'; -import { Data } from '../components/options/data/Data'; -import { getChildProps } from './getChildProps'; - -describe('getChildProps', () => { - it('maps Data columns to options.data.columns', () => { - const columns = { - name: ['Alice', 'Bob'], - age: [23, 34] - }; - - expect(getChildProps()).toEqual({ - data: { - columns - } - }); - }); - - it('maps all Data props to options.data', () => { - const columns = { - name: ['Alice'] - }; - const connector = { id: 'csv' }; - const dataTable = { id: 'table-1' }; - - expect( - getChildProps( - - ) - ).toEqual({ - data: { - providerType: 'local', - autogenerateColumns: false, - columns, - connector, - dataTable, - updateOnChange: true, - idColumn: 'id' - } - }); - }); -}); diff --git a/packages/grid-shared-react/src/utils/getChildProps.ts b/packages/grid-shared-react/src/utils/getChildProps.ts index 572376d..a33c900 100644 --- a/packages/grid-shared-react/src/utils/getChildProps.ts +++ b/packages/grid-shared-react/src/utils/getChildProps.ts @@ -9,7 +9,8 @@ import { Fragment, isValidElement, ReactElement, ReactNode } from 'react'; import type { BaseGridOptionsComponent, BaseGridOptions } from '../components/BaseGridOptions'; -import { normalizeColumnOptions } from './mappers/columnOptions'; +import { normalizeColumnOptions } from './mappers/column'; +import { normalizePaginationOptions } from './mappers/pagination'; function objInsert( obj: Record, @@ -203,6 +204,11 @@ export function getChildProps(children: ReactNode): Record { return; } + if (meta.gridOption === 'pagination') { + optionsFromChildren.pagination = normalizePaginationOptions(props); + return; + } + const optionParent = optionsFromChildren[meta.gridOption] ?? ( optionsFromChildren[meta.gridOption] = meta.isArrayType ? [] : {} ); diff --git a/packages/grid-shared-react/src/utils/mappers/columnOptions.ts b/packages/grid-shared-react/src/utils/mappers/column/columnOptions.ts similarity index 90% rename from packages/grid-shared-react/src/utils/mappers/columnOptions.ts rename to packages/grid-shared-react/src/utils/mappers/column/columnOptions.ts index 300c39c..f1ae833 100644 --- a/packages/grid-shared-react/src/utils/mappers/columnOptions.ts +++ b/packages/grid-shared-react/src/utils/mappers/column/columnOptions.ts @@ -7,7 +7,7 @@ * */ -import { mapPrefixedProps } from './mapPrefixedProps'; +import { mapPrefixedProps } from '../mapPrefixedProps'; /** Flat prop prefix → nested Grid option key for columns. */ const COLUMN_PROP_PREFIXES = { diff --git a/packages/grid-shared-react/src/utils/mappers/column/index.ts b/packages/grid-shared-react/src/utils/mappers/column/index.ts new file mode 100644 index 0000000..01a620a --- /dev/null +++ b/packages/grid-shared-react/src/utils/mappers/column/index.ts @@ -0,0 +1,10 @@ +/** + * Grid React integration. + * Copyright (c) 2025, Highsoft + * + * A valid license is required for using this software. + * See highcharts.com/license + * + */ + +export { normalizeColumnOptions } from './columnOptions'; diff --git a/packages/grid-shared-react/src/utils/mappers/pagination/index.ts b/packages/grid-shared-react/src/utils/mappers/pagination/index.ts new file mode 100644 index 0000000..de3e9f4 --- /dev/null +++ b/packages/grid-shared-react/src/utils/mappers/pagination/index.ts @@ -0,0 +1,10 @@ +/** + * Grid React integration. + * Copyright (c) 2025, Highsoft + * + * A valid license is required for using this software. + * See highcharts.com/license + * + */ + +export { normalizePaginationOptions } from './paginationOptions'; diff --git a/packages/grid-shared-react/src/utils/mappers/pagination/paginationOptions.ts b/packages/grid-shared-react/src/utils/mappers/pagination/paginationOptions.ts new file mode 100644 index 0000000..0db2d55 --- /dev/null +++ b/packages/grid-shared-react/src/utils/mappers/pagination/paginationOptions.ts @@ -0,0 +1,84 @@ +/** + * Grid React integration. + * Copyright (c) 2025, Highsoft + * + * A valid license is required for using this software. + * See highcharts.com/license + * + */ + +import type { PaginationProps } from '../../../components/options/pagination/paginationProps'; + +export function normalizePaginationOptions( + props: Record +): Record { + const { + pageInfo, + pageSizeSelector, + pageSizeOptions, + pageButtons, + pageButtonsCount, + firstLast, + previousNext, + enabled, + page, + pageSize, + align + } = props as PaginationProps; + + const result: Record = { + enabled: enabled ?? true + }; + + if (page !== void 0) { + result.page = page; + } + if (pageSize !== void 0) { + result.pageSize = pageSize; + } + if (align !== void 0) { + result.align = align; + } + + const controls: Record = {}; + + if (pageInfo !== void 0) { + controls.pageInfo = pageInfo; + } + + if (pageSizeSelector === false) { + controls.pageSizeSelector = false; + } else if (pageSizeOptions !== void 0) { + controls.pageSizeSelector = { + enabled: true, + options: pageSizeOptions + }; + } else if (pageSizeSelector !== void 0) { + controls.pageSizeSelector = pageSizeSelector; + } + + if (pageButtons === false) { + controls.pageButtons = false; + } else if (pageButtonsCount !== void 0) { + controls.pageButtons = { + enabled: true, + count: pageButtonsCount + }; + } else if (pageButtons !== void 0) { + controls.pageButtons = pageButtons; + } + + if (firstLast !== void 0) { + controls.firstLastButtons = firstLast; + } + + if (previousNext !== void 0) { + controls.previousNextButtons = previousNext; + } + + if (Object.keys(controls).length > 0) { + result.controls = controls; + } + + return result; +} From 5841659398105250603a0e745d8d9716819fa34c Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Mon, 6 Jul 2026 13:06:13 +0200 Subject: [PATCH 13/51] Cleaned up. --- .../src/components/options/pagination/Pagination.tsx | 2 -- .../src/components/options/pagination/index.ts | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/grid-shared-react/src/components/options/pagination/Pagination.tsx b/packages/grid-shared-react/src/components/options/pagination/Pagination.tsx index f2c9ad9..8a169d7 100644 --- a/packages/grid-shared-react/src/components/options/pagination/Pagination.tsx +++ b/packages/grid-shared-react/src/components/options/pagination/Pagination.tsx @@ -9,8 +9,6 @@ import type { PaginationProps } from './paginationProps'; -export type { PaginationProps } from './paginationProps'; - export function Pagination(_props: PaginationProps) { return null; } diff --git a/packages/grid-shared-react/src/components/options/pagination/index.ts b/packages/grid-shared-react/src/components/options/pagination/index.ts index a225c83..d5d79cb 100644 --- a/packages/grid-shared-react/src/components/options/pagination/index.ts +++ b/packages/grid-shared-react/src/components/options/pagination/index.ts @@ -8,4 +8,4 @@ */ export { Pagination } from './Pagination'; -export type { PaginationProps } from './Pagination'; +export type { PaginationProps } from './paginationProps'; From c6ad53e8f570611a89c9bcf5e93fab1806f217e4 Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Mon, 6 Jul 2026 14:39:26 +0200 Subject: [PATCH 14/51] Added header component. --- .../grid-lite/components-react/src/App.tsx | 10 +++++- packages/grid-lite-react/src/index.ts | 15 ++++++-- packages/grid-pro-react/src/index.ts | 15 ++++++-- .../src/components/options/header/Header.tsx | 19 ++++++++++ .../components/options/header/headerProps.ts | 36 +++++++++++++++++++ .../src/components/options/header/index.ts | 15 ++++++++ .../src/components/options/index.ts | 6 ++++ packages/grid-shared-react/src/index.ts | 15 ++++++-- .../src/utils/getChildProps.ts | 9 +++++ 9 files changed, 133 insertions(+), 7 deletions(-) create mode 100644 packages/grid-shared-react/src/components/options/header/Header.tsx create mode 100644 packages/grid-shared-react/src/components/options/header/headerProps.ts create mode 100644 packages/grid-shared-react/src/components/options/header/index.ts diff --git a/examples/grid-lite/components-react/src/App.tsx b/examples/grid-lite/components-react/src/App.tsx index caa4be7..cdb84e1 100644 --- a/examples/grid-lite/components-react/src/App.tsx +++ b/examples/grid-lite/components-react/src/App.tsx @@ -10,7 +10,8 @@ import { ColumnDefaults, Column, Description, - Pagination + Pagination, + Header } from '@highcharts/grid-lite-react'; function App() { @@ -98,6 +99,13 @@ function App() { cellRowHeader={false} /> Grid Caption v2.1 +
; +} + +export interface HeaderProps { + /** + * Header tree: column order, inclusion, and grouping. + * Each entry is a column id (`string`) or a {@link GroupedHeaderOptions} + * object. Maps to Grid Core `options.header`. + */ + header?: Array; +} diff --git a/packages/grid-shared-react/src/components/options/header/index.ts b/packages/grid-shared-react/src/components/options/header/index.ts new file mode 100644 index 0000000..e25312d --- /dev/null +++ b/packages/grid-shared-react/src/components/options/header/index.ts @@ -0,0 +1,15 @@ +/** + * Grid React integration. + * Copyright (c) 2025, Highsoft + * + * A valid license is required for using this software. + * See highcharts.com/license + * + */ + +export { Header } from './Header'; +export type { + HeaderProps, + GroupedHeaderOptions, + HeaderCellAccessibilityProps +} from './headerProps'; diff --git a/packages/grid-shared-react/src/components/options/index.ts b/packages/grid-shared-react/src/components/options/index.ts index 67c6419..36984b3 100644 --- a/packages/grid-shared-react/src/components/options/index.ts +++ b/packages/grid-shared-react/src/components/options/index.ts @@ -24,3 +24,9 @@ export { Description } from './description'; export type { DescriptionProps } from './description'; export { Pagination } from './pagination'; export type { PaginationProps } from './pagination'; +export { Header } from './header'; +export type { + HeaderProps, + GroupedHeaderOptions, + HeaderCellAccessibilityProps +} from './header'; diff --git a/packages/grid-shared-react/src/index.ts b/packages/grid-shared-react/src/index.ts index 989215c..4cfc3ca 100644 --- a/packages/grid-shared-react/src/index.ts +++ b/packages/grid-shared-react/src/index.ts @@ -12,7 +12,15 @@ import { GridType, GridInstance } from './hooks/useGrid'; import type { GridProps, GridRefHandle } from './components/BaseGrid'; export { BaseGrid }; -export { Caption, Data, ColumnDefaults, Column, Description, Pagination } from './components/options'; +export { + Caption, + Data, + ColumnDefaults, + Column, + Description, + Pagination, + Header +} from './components/options'; export { getChildProps } from './utils/getChildProps'; export type { CaptionProps, @@ -25,6 +33,9 @@ export type { ColumnDataType, ColumnSortingOrder, CellValueGetterContext, - PaginationProps + PaginationProps, + HeaderProps, + GroupedHeaderOptions, + HeaderCellAccessibilityProps } from './components/options'; export type { GridType, GridInstance, GridProps, GridRefHandle }; diff --git a/packages/grid-shared-react/src/utils/getChildProps.ts b/packages/grid-shared-react/src/utils/getChildProps.ts index a33c900..4564d86 100644 --- a/packages/grid-shared-react/src/utils/getChildProps.ts +++ b/packages/grid-shared-react/src/utils/getChildProps.ts @@ -209,6 +209,15 @@ export function getChildProps(children: ReactNode): Record { return; } + if (meta.gridOption === 'header') { + const { header, children: _ignored } = props; + + if (header !== void 0) { + optionsFromChildren.header = header; + } + return; + } + const optionParent = optionsFromChildren[meta.gridOption] ?? ( optionsFromChildren[meta.gridOption] = meta.isArrayType ? [] : {} ); From 85b3a1d9ac6b3ae34dacd06b8dfb8690c061bdce Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Tue, 7 Jul 2026 13:02:56 +0200 Subject: [PATCH 15/51] Added base for tailwind styling. --- .../grid-lite/components-react/package.json | 3 +- .../grid-lite/components-react/src/App.tsx | 208 +++++---- .../grid-lite/components-react/src/index.css | 23 +- .../grid-lite/components-react/vite.config.ts | 4 +- packages/grid-lite-react/src/Grid.tsx | 10 +- packages/grid-pro-react/src/Grid.tsx | 10 +- .../src/components/BaseGrid.tsx | 13 +- pnpm-lock.yaml | 428 +++++++++++++++--- 8 files changed, 519 insertions(+), 180 deletions(-) diff --git a/examples/grid-lite/components-react/package.json b/examples/grid-lite/components-react/package.json index cd5b45a..b98f691 100644 --- a/examples/grid-lite/components-react/package.json +++ b/examples/grid-lite/components-react/package.json @@ -16,11 +16,12 @@ "react-dom": ">=18" }, "devDependencies": { + "@tailwindcss/vite": "^4.3.2", "@types/react": ">=18", "@types/react-dom": ">=18", "@vitejs/plugin-react": "^4.2.0", + "tailwindcss": "^4.3.2", "typescript": "^5.0.0", "vite": "^5.0.0" } } - diff --git a/examples/grid-lite/components-react/src/App.tsx b/examples/grid-lite/components-react/src/App.tsx index cdb84e1..5f04361 100644 --- a/examples/grid-lite/components-react/src/App.tsx +++ b/examples/grid-lite/components-react/src/App.tsx @@ -71,102 +71,120 @@ function App() { // }; return ( - <> - - - - Grid Caption v2.1 -
- - - - - - Grid Description - - -
- - {/* */} +
+ + + + Grid Caption v2.1 +
+ + + + + + Grid Description + + +
+ + {/* */} +
- ); } diff --git a/examples/grid-lite/components-react/src/index.css b/examples/grid-lite/components-react/src/index.css index 9a63579..ba656e7 100644 --- a/examples/grid-lite/components-react/src/index.css +++ b/examples/grid-lite/components-react/src/index.css @@ -1,8 +1,4 @@ -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} +@import "tailwindcss"; body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', @@ -15,19 +11,4 @@ body { #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; - } -} - +} \ No newline at end of file diff --git a/examples/grid-lite/components-react/vite.config.ts b/examples/grid-lite/components-react/vite.config.ts index 2e3f28b..0ec7891 100644 --- a/examples/grid-lite/components-react/vite.config.ts +++ b/examples/grid-lite/components-react/vite.config.ts @@ -1,12 +1,13 @@ import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; +import tailwindcss from '@tailwindcss/vite'; import { resolve, dirname } from 'path'; import { fileURLToPath } from 'url'; const __dirname = dirname(fileURLToPath(import.meta.url)); export default defineConfig({ - plugins: [react()], + plugins: [react(), tailwindcss()], resolve: { alias: [ { @@ -24,6 +25,7 @@ export default defineConfig({ ] }, server: { + host: true, port: 3000 } }); diff --git a/packages/grid-lite-react/src/Grid.tsx b/packages/grid-lite-react/src/Grid.tsx index aa47803..ee6ede3 100644 --- a/packages/grid-lite-react/src/Grid.tsx +++ b/packages/grid-lite-react/src/Grid.tsx @@ -19,7 +19,7 @@ import '@highcharts/grid-lite/css/grid-lite.css'; import type { Options } from '@highcharts/grid-lite/es-modules/Grid/Core/Options'; export default function GridLite(props: GridProps) { - const { gridRef, children, options, ...gridProps } = props; + const { gridRef, children, options, theme, ...gridProps } = props; const childOptions = useMemo(() => getChildProps(children), [children]); const columnKey = useMemo(() => { const columns = childOptions.columns as Array<{ id?: string }> | undefined; @@ -27,8 +27,12 @@ export default function GridLite(props: GridProps) { return columns?.map((column) => column.id).join('\0') ?? ''; }, [childOptions]); const gridOptions = useMemo( - () => merge(childOptions, options ?? {}) as Options, - [childOptions, options] + () => merge( + childOptions, + options ?? {}, + theme ? { rendering: { theme } } : {} + ) as Options, + [childOptions, options, theme] ); return ( diff --git a/packages/grid-pro-react/src/Grid.tsx b/packages/grid-pro-react/src/Grid.tsx index c236cfd..7f5d317 100644 --- a/packages/grid-pro-react/src/Grid.tsx +++ b/packages/grid-pro-react/src/Grid.tsx @@ -19,7 +19,7 @@ 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 { gridRef, children, options, theme, ...gridProps } = props; const childOptions = useMemo(() => getChildProps(children), [children]); const columnKey = useMemo(() => { const columns = childOptions.columns as Array<{ id?: string }> | undefined; @@ -27,8 +27,12 @@ export default function GridPro(props: GridProps) { return columns?.map((column) => column.id).join('\0') ?? ''; }, [childOptions]); const gridOptions = useMemo( - () => merge(childOptions, options ?? {}) as Options, - [childOptions, options] + () => merge( + childOptions, + options ?? {}, + theme ? { rendering: { theme } } : {} + ) as Options, + [childOptions, options, theme] ); return ( diff --git a/packages/grid-shared-react/src/components/BaseGrid.tsx b/packages/grid-shared-react/src/components/BaseGrid.tsx index 4d51c20..11be983 100644 --- a/packages/grid-shared-react/src/components/BaseGrid.tsx +++ b/packages/grid-shared-react/src/components/BaseGrid.tsx @@ -32,6 +32,14 @@ export interface GridProps { * Grid configuration options */ options?: TOptions; + /** + * Optional CSS class name applied to the root grid container. + */ + className?: string; + /** + * Optional theme name passed to Grid Core. + */ + theme?: string; /** * Declarative option components (e.g. Caption) passed as children. */ @@ -53,13 +61,14 @@ export interface BaseGridProps { options?: TOptions; Grid: GridType; callback?: (grid: GridInstance) => void; + className?: string; } export const BaseGrid = forwardRef(function BaseGrid( props: BaseGridProps, ref: ForwardedRef> ) { - const { options, Grid, callback } = props; + const { options, Grid, callback, className } = props; const containerRef = useRef(null); const currGridRef = useGrid({ @@ -79,5 +88,5 @@ export const BaseGrid = forwardRef(function BaseGrid( [] ); - return
; + return
; }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ff81d18..e4aa239 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,7 +13,7 @@ importers: version: 9.39.1 '@stylistic/eslint-plugin': specifier: ^5.6.1 - version: 5.6.1(eslint@9.39.1) + version: 5.6.1(eslint@9.39.1(jiti@2.7.0)) '@testing-library/react': specifier: ^16.3.1 version: 16.3.1(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) @@ -22,13 +22,13 @@ importers: version: 20.19.26 '@vitest/browser': specifier: ^4.0.16 - version: 4.0.16(vite@7.2.7(@types/node@20.19.26))(vitest@4.0.16) + version: 4.0.16(vite@7.2.7(@types/node@20.19.26)(jiti@2.7.0)(lightningcss@1.32.0))(vitest@4.0.16) '@vitest/browser-playwright': specifier: ^4.0.16 - version: 4.0.16(playwright@1.57.0)(vite@7.2.7(@types/node@20.19.26))(vitest@4.0.16) + version: 4.0.16(playwright@1.57.0)(vite@7.2.7(@types/node@20.19.26)(jiti@2.7.0)(lightningcss@1.32.0))(vitest@4.0.16) eslint: specifier: ^9.39.1 - version: 9.39.1 + version: 9.39.1(jiti@2.7.0) globals: specifier: ^17.0.0 version: 17.0.0 @@ -43,10 +43,10 @@ importers: version: 5.9.3 typescript-eslint: specifier: ^8.48.0 - version: 8.49.0(eslint@9.39.1)(typescript@5.9.3) + version: 8.49.0(eslint@9.39.1(jiti@2.7.0))(typescript@5.9.3) vitest: specifier: ^4.0.16 - version: 4.0.16(@types/node@20.19.26)(@vitest/browser-playwright@4.0.16)(jsdom@27.4.0) + version: 4.0.16(@types/node@20.19.26)(@vitest/browser-playwright@4.0.16)(jiti@2.7.0)(jsdom@27.4.0)(lightningcss@1.32.0) examples/grid-lite/components-react: dependencies: @@ -63,6 +63,9 @@ importers: specifier: '>=18' version: 19.2.1(react@19.2.1) devDependencies: + '@tailwindcss/vite': + specifier: ^4.3.2 + version: 4.3.2(vite@5.4.21(@types/node@20.19.26)(lightningcss@1.32.0)) '@types/react': specifier: '>=18' version: 19.2.7 @@ -71,13 +74,16 @@ importers: 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)) + version: 4.7.0(vite@5.4.21(@types/node@20.19.26)(lightningcss@1.32.0)) + tailwindcss: + specifier: ^4.3.2 + version: 4.3.2 typescript: specifier: ^5.0.0 version: 5.9.3 vite: specifier: ^5.0.0 - version: 5.4.21(@types/node@20.19.26) + version: 5.4.21(@types/node@20.19.26)(lightningcss@1.32.0) examples/grid-lite/minimal-nextjs: dependencies: @@ -133,13 +139,13 @@ importers: 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)) + version: 4.7.0(vite@5.4.21(@types/node@20.19.26)(lightningcss@1.32.0)) typescript: specifier: ^5.0.0 version: 5.9.3 vite: specifier: ^5.0.0 - version: 5.4.21(@types/node@20.19.26) + version: 5.4.21(@types/node@20.19.26)(lightningcss@1.32.0) examples/grid-pro/minimal-nextjs: dependencies: @@ -195,13 +201,13 @@ importers: 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)) + version: 4.7.0(vite@5.4.21(@types/node@20.19.26)(lightningcss@1.32.0)) typescript: specifier: ^5.0.0 version: 5.9.3 vite: specifier: ^5.0.0 - version: 5.4.21(@types/node@20.19.26) + version: 5.4.21(@types/node@20.19.26)(lightningcss@1.32.0) packages/grid-lite-react: dependencies: @@ -1055,6 +1061,96 @@ packages: '@swc/helpers@0.5.5': resolution: {integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==} + '@tailwindcss/node@4.3.2': + resolution: {integrity: sha512-yWP/sqEcBLaD8JuA6zNwxoYKr75qxTioYwlRwekj5Jr/I5GXnoJfjetH/psLUIv74cYTH2lBUEzBkinthoYcBg==} + + '@tailwindcss/oxide-android-arm64@4.3.2': + resolution: {integrity: sha512-WHxqIuHpvZ5VtdX6GTl1Ik/Vp2YuN42Et+0CdeaVd/frQ9jAvGmvR8vLT+jk3e8/Q3x8kECB9+R17pgpp2BulA==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.3.2': + resolution: {integrity: sha512-GZypeUY/IDJW3877KeM+O67vbXr3MBnbtEL4aYhNErv/JWZhye2vGSWWG9tB6iiqR2MqRNkY8IOUy4NdSZV26w==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.3.2': + resolution: {integrity: sha512-UIIzmefR6KO1sDU7MzRqAxC8iBpft/VhkGjTjnhoS6k7Z3rQ9wEgA1ODSiyH/tcSYssulNm4Ci3hOeK1jH7ccQ==} + engines: {node: '>= 20'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.3.2': + resolution: {integrity: sha512-GN+uAmcI6DNspnCDwtOAZrTz6oukJnp337qZvxqCGLd3BHBzJpO0ZbTLRvJNdztOeAmTzewewGIMPb0tk2R4WA==} + engines: {node: '>= 20'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.2': + resolution: {integrity: sha512-4ABn7qSbdHRwTiDiuWNegCyb5+2FJ4vKIKc3DmKrvAFw7MU1Lm11dIkTPwUaFdTzc7IsOpDbqBrlh0x6y36U/w==} + engines: {node: '>= 20'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.3.2': + resolution: {integrity: sha512-wDgEIGwoM8w8pufh9LVt1PahDgNdKXrLC2qfAnV3vAmococ9RWbxeAw4pxPttd/TsJfwjyLf90Dg1y9y8I6Emw==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-musl@4.3.2': + resolution: {integrity: sha512-J5Nuk0uZQIiMTJj3LEx4sAA9tMFUoXQZFv1J6An+QGYe53HKRJuFDi0rpq/tuouCZeAbOBY3kQ6g8qeD4TUjtA==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-gnu@4.3.2': + resolution: {integrity: sha512-kqCZpSKOBEJO4mz7OqWoofBZeXTAwaVGPj0ErAj7CojmhKpWVWVOnrt9dE8odoIraZq4oj3ausM37kXi+Tow8w==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-musl@4.3.2': + resolution: {integrity: sha512-cixpqbh2toJDmkuCRI68nXA8ZxNmdK9Y+9v5h3MC3ZQKy/0BO8AWzlkWyRM7JAFSGBlfig4YVTPsK6MVgqz1uw==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-wasm32-wasi@4.3.2': + resolution: {integrity: sha512-4ec2Z/LOmRsAgU23CS4xeJfcJlmRg94A/XrbGRCF1gyU/zdDfRLYDVsS+ynSZCmGNxQ1jQriQOKMQeQxBA3Isw==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.3.2': + resolution: {integrity: sha512-Zyr/M0+XcYZu3bZrUytc7TXvrk0ftWfl8gN2MwekNDzhqhKRUucMPSeOzM0o0wH5AWOU49BsKRrfKxI2atCPMQ==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.3.2': + resolution: {integrity: sha512-QI9BO7KlNZsp2GuO0jwAAj5jCDABOKXRkCk2XuKTSaNEFSdfzqswYVTtCHBNKHLsqyjFyFkqlDiwkNbTYSssMQ==} + engines: {node: '>= 20'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.3.2': + resolution: {integrity: sha512-z8ZgnzX8gdNoWLBLqBPoh/sjnxkwvf9ZuWjnO0l0yIzbLa5/9S+eC5QxGZKRobVHIC3/1BoMWjHblqWjcgFgag==} + engines: {node: '>= 20'} + + '@tailwindcss/vite@4.3.2': + resolution: {integrity: sha512-eHpMeX4JXfVNJDEcsouTeCBubJBTcTLigeaw/NTUW6PB5ATKKXdyonnXgTBX2VuRbjz1hjfz6C5XAhr52ImQXA==} + peerDependencies: + vite: ^5.2.0 || ^6 || ^7 || ^8 + '@testing-library/dom@10.4.1': resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} engines: {node: '>=18'} @@ -1360,12 +1456,20 @@ packages: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + dom-accessibility-api@0.5.16: resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} electron-to-chromium@1.5.267: resolution: {integrity: sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==} + enhanced-resolve@5.21.6: + resolution: {integrity: sha512-aNnGCvbJ/RIyWo1IuhNdVjnNF+EjH9wpzpNHt+ci/m9He9LJvUN8wrCcXjp9cWsGNAuvSpVFTx/vraAFQ8qGjQ==} + engines: {node: '>=10.13.0'} + entities@6.0.1: resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} engines: {node: '>=0.12'} @@ -1572,6 +1676,10 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + jiti@2.7.0: + resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} + hasBin: true + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -1614,6 +1722,76 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + engines: {node: '>= 12.0.0'} + locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} @@ -1897,6 +2075,13 @@ packages: symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + tailwindcss@4.3.2: + resolution: {integrity: sha512-WtctNNSH8A9jlMIqxzuYumOHU5uGZyRv0Q5svQl+oEPy5w84YpBxdb7MdqyiSPQge5jTJ6zFQLq0PFygdccSBA==} + + tapable@2.3.3: + resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} + engines: {node: '>=6'} + tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} @@ -2444,9 +2629,9 @@ snapshots: '@esbuild/win32-x64@0.25.12': optional: true - '@eslint-community/eslint-utils@4.9.0(eslint@9.39.1)': + '@eslint-community/eslint-utils@4.9.0(eslint@9.39.1(jiti@2.7.0))': dependencies: - eslint: 9.39.1 + eslint: 9.39.1(jiti@2.7.0) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} @@ -2673,11 +2858,11 @@ snapshots: '@standard-schema/spec@1.0.0': {} - '@stylistic/eslint-plugin@5.6.1(eslint@9.39.1)': + '@stylistic/eslint-plugin@5.6.1(eslint@9.39.1(jiti@2.7.0))': dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1) + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@2.7.0)) '@typescript-eslint/types': 8.49.0 - eslint: 9.39.1 + eslint: 9.39.1(jiti@2.7.0) eslint-visitor-keys: 4.2.1 espree: 10.4.0 estraverse: 5.3.0 @@ -2690,6 +2875,74 @@ snapshots: '@swc/counter': 0.1.3 tslib: 2.8.1 + '@tailwindcss/node@4.3.2': + dependencies: + '@jridgewell/remapping': 2.3.5 + enhanced-resolve: 5.21.6 + jiti: 2.7.0 + lightningcss: 1.32.0 + magic-string: 0.30.21 + source-map-js: 1.2.1 + tailwindcss: 4.3.2 + + '@tailwindcss/oxide-android-arm64@4.3.2': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.3.2': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.3.2': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.3.2': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.2': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.3.2': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.3.2': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.3.2': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.3.2': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.3.2': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.3.2': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.3.2': + optional: true + + '@tailwindcss/oxide@4.3.2': + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.3.2 + '@tailwindcss/oxide-darwin-arm64': 4.3.2 + '@tailwindcss/oxide-darwin-x64': 4.3.2 + '@tailwindcss/oxide-freebsd-x64': 4.3.2 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.3.2 + '@tailwindcss/oxide-linux-arm64-gnu': 4.3.2 + '@tailwindcss/oxide-linux-arm64-musl': 4.3.2 + '@tailwindcss/oxide-linux-x64-gnu': 4.3.2 + '@tailwindcss/oxide-linux-x64-musl': 4.3.2 + '@tailwindcss/oxide-wasm32-wasi': 4.3.2 + '@tailwindcss/oxide-win32-arm64-msvc': 4.3.2 + '@tailwindcss/oxide-win32-x64-msvc': 4.3.2 + + '@tailwindcss/vite@4.3.2(vite@5.4.21(@types/node@20.19.26)(lightningcss@1.32.0))': + dependencies: + '@tailwindcss/node': 4.3.2 + '@tailwindcss/oxide': 4.3.2 + tailwindcss: 4.3.2 + vite: 5.4.21(@types/node@20.19.26)(lightningcss@1.32.0) + '@testing-library/dom@10.4.1': dependencies: '@babel/code-frame': 7.27.1 @@ -2759,15 +3012,15 @@ snapshots: '@types/resolve@1.20.2': {} - '@typescript-eslint/eslint-plugin@8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.1(jiti@2.7.0))(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.49.0(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/parser': 8.49.0(eslint@9.39.1(jiti@2.7.0))(typescript@5.9.3) '@typescript-eslint/scope-manager': 8.49.0 - '@typescript-eslint/type-utils': 8.49.0(eslint@9.39.1)(typescript@5.9.3) - '@typescript-eslint/utils': 8.49.0(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/type-utils': 8.49.0(eslint@9.39.1(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/utils': 8.49.0(eslint@9.39.1(jiti@2.7.0))(typescript@5.9.3) '@typescript-eslint/visitor-keys': 8.49.0 - eslint: 9.39.1 + eslint: 9.39.1(jiti@2.7.0) ignore: 7.0.5 natural-compare: 1.4.0 ts-api-utils: 2.1.0(typescript@5.9.3) @@ -2775,14 +3028,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.49.0(eslint@9.39.1)(typescript@5.9.3)': + '@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@2.7.0))(typescript@5.9.3)': dependencies: '@typescript-eslint/scope-manager': 8.49.0 '@typescript-eslint/types': 8.49.0 '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) '@typescript-eslint/visitor-keys': 8.49.0 debug: 4.4.3 - eslint: 9.39.1 + eslint: 9.39.1(jiti@2.7.0) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -2805,13 +3058,13 @@ snapshots: dependencies: typescript: 5.9.3 - '@typescript-eslint/type-utils@8.49.0(eslint@9.39.1)(typescript@5.9.3)': + '@typescript-eslint/type-utils@8.49.0(eslint@9.39.1(jiti@2.7.0))(typescript@5.9.3)': dependencies: '@typescript-eslint/types': 8.49.0 '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.49.0(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/utils': 8.49.0(eslint@9.39.1(jiti@2.7.0))(typescript@5.9.3) debug: 4.4.3 - eslint: 9.39.1 + eslint: 9.39.1(jiti@2.7.0) ts-api-utils: 2.1.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: @@ -2834,13 +3087,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.49.0(eslint@9.39.1)(typescript@5.9.3)': + '@typescript-eslint/utils@8.49.0(eslint@9.39.1(jiti@2.7.0))(typescript@5.9.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1) + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@2.7.0)) '@typescript-eslint/scope-manager': 8.49.0 '@typescript-eslint/types': 8.49.0 '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) - eslint: 9.39.1 + eslint: 9.39.1(jiti@2.7.0) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -2850,7 +3103,7 @@ snapshots: '@typescript-eslint/types': 8.49.0 eslint-visitor-keys: 4.2.1 - '@vitejs/plugin-react@4.7.0(vite@5.4.21(@types/node@20.19.26))': + '@vitejs/plugin-react@4.7.0(vite@5.4.21(@types/node@20.19.26)(lightningcss@1.32.0))': dependencies: '@babel/core': 7.28.5 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.5) @@ -2858,33 +3111,33 @@ snapshots: '@rolldown/pluginutils': 1.0.0-beta.27 '@types/babel__core': 7.20.5 react-refresh: 0.17.0 - vite: 5.4.21(@types/node@20.19.26) + vite: 5.4.21(@types/node@20.19.26)(lightningcss@1.32.0) transitivePeerDependencies: - supports-color - '@vitest/browser-playwright@4.0.16(playwright@1.57.0)(vite@7.2.7(@types/node@20.19.26))(vitest@4.0.16)': + '@vitest/browser-playwright@4.0.16(playwright@1.57.0)(vite@7.2.7(@types/node@20.19.26)(jiti@2.7.0)(lightningcss@1.32.0))(vitest@4.0.16)': dependencies: - '@vitest/browser': 4.0.16(vite@7.2.7(@types/node@20.19.26))(vitest@4.0.16) - '@vitest/mocker': 4.0.16(vite@7.2.7(@types/node@20.19.26)) + '@vitest/browser': 4.0.16(vite@7.2.7(@types/node@20.19.26)(jiti@2.7.0)(lightningcss@1.32.0))(vitest@4.0.16) + '@vitest/mocker': 4.0.16(vite@7.2.7(@types/node@20.19.26)(jiti@2.7.0)(lightningcss@1.32.0)) playwright: 1.57.0 tinyrainbow: 3.0.3 - vitest: 4.0.16(@types/node@20.19.26)(@vitest/browser-playwright@4.0.16)(jsdom@27.4.0) + vitest: 4.0.16(@types/node@20.19.26)(@vitest/browser-playwright@4.0.16)(jiti@2.7.0)(jsdom@27.4.0)(lightningcss@1.32.0) transitivePeerDependencies: - bufferutil - msw - utf-8-validate - vite - '@vitest/browser@4.0.16(vite@7.2.7(@types/node@20.19.26))(vitest@4.0.16)': + '@vitest/browser@4.0.16(vite@7.2.7(@types/node@20.19.26)(jiti@2.7.0)(lightningcss@1.32.0))(vitest@4.0.16)': dependencies: - '@vitest/mocker': 4.0.16(vite@7.2.7(@types/node@20.19.26)) + '@vitest/mocker': 4.0.16(vite@7.2.7(@types/node@20.19.26)(jiti@2.7.0)(lightningcss@1.32.0)) '@vitest/utils': 4.0.16 magic-string: 0.30.21 pixelmatch: 7.1.0 pngjs: 7.0.0 sirv: 3.0.2 tinyrainbow: 3.0.3 - vitest: 4.0.16(@types/node@20.19.26)(@vitest/browser-playwright@4.0.16)(jsdom@27.4.0) + vitest: 4.0.16(@types/node@20.19.26)(@vitest/browser-playwright@4.0.16)(jiti@2.7.0)(jsdom@27.4.0)(lightningcss@1.32.0) ws: 8.18.3 transitivePeerDependencies: - bufferutil @@ -2901,13 +3154,13 @@ snapshots: chai: 6.2.1 tinyrainbow: 3.0.3 - '@vitest/mocker@4.0.16(vite@7.2.7(@types/node@20.19.26))': + '@vitest/mocker@4.0.16(vite@7.2.7(@types/node@20.19.26)(jiti@2.7.0)(lightningcss@1.32.0))': dependencies: '@vitest/spy': 4.0.16 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.2.7(@types/node@20.19.26) + vite: 7.2.7(@types/node@20.19.26)(jiti@2.7.0)(lightningcss@1.32.0) '@vitest/pretty-format@4.0.16': dependencies: @@ -3059,10 +3312,17 @@ snapshots: dequal@2.0.3: {} + detect-libc@2.1.2: {} + dom-accessibility-api@0.5.16: {} electron-to-chromium@1.5.267: {} + enhanced-resolve@5.21.6: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.3 + entities@6.0.1: optional: true @@ -3136,9 +3396,9 @@ snapshots: eslint-visitor-keys@4.2.1: {} - eslint@9.39.1: + eslint@9.39.1(jiti@2.7.0): dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1) + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@2.7.0)) '@eslint-community/regexpp': 4.12.2 '@eslint/config-array': 0.21.1 '@eslint/config-helpers': 0.4.2 @@ -3172,6 +3432,8 @@ snapshots: minimatch: 3.1.2 natural-compare: 1.4.0 optionator: 0.9.4 + optionalDependencies: + jiti: 2.7.0 transitivePeerDependencies: - supports-color @@ -3314,6 +3576,8 @@ snapshots: isexe@2.0.0: {} + jiti@2.7.0: {} + js-tokens@4.0.0: {} js-yaml@4.1.1: @@ -3368,6 +3632,55 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 + lightningcss-android-arm64@1.32.0: + optional: true + + lightningcss-darwin-arm64@1.32.0: + optional: true + + lightningcss-darwin-x64@1.32.0: + optional: true + + lightningcss-freebsd-x64@1.32.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.32.0: + optional: true + + lightningcss-linux-arm64-gnu@1.32.0: + optional: true + + lightningcss-linux-arm64-musl@1.32.0: + optional: true + + lightningcss-linux-x64-gnu@1.32.0: + optional: true + + lightningcss-linux-x64-musl@1.32.0: + optional: true + + lightningcss-win32-arm64-msvc@1.32.0: + optional: true + + lightningcss-win32-x64-msvc@1.32.0: + optional: true + + lightningcss@1.32.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 + locate-path@6.0.0: dependencies: p-locate: 5.0.0 @@ -3633,6 +3946,10 @@ snapshots: symbol-tree@3.2.4: optional: true + tailwindcss@4.3.2: {} + + tapable@2.3.3: {} + tinybench@2.9.0: {} tinyexec@1.0.2: {} @@ -3674,13 +3991,13 @@ snapshots: dependencies: prelude-ls: 1.2.1 - typescript-eslint@8.49.0(eslint@9.39.1)(typescript@5.9.3): + typescript-eslint@8.49.0(eslint@9.39.1(jiti@2.7.0))(typescript@5.9.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3) - '@typescript-eslint/parser': 8.49.0(eslint@9.39.1)(typescript@5.9.3) + '@typescript-eslint/eslint-plugin': 8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.1(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/parser': 8.49.0(eslint@9.39.1(jiti@2.7.0))(typescript@5.9.3) '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.49.0(eslint@9.39.1)(typescript@5.9.3) - eslint: 9.39.1 + '@typescript-eslint/utils': 8.49.0(eslint@9.39.1(jiti@2.7.0))(typescript@5.9.3) + eslint: 9.39.1(jiti@2.7.0) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -3699,7 +4016,7 @@ snapshots: dependencies: punycode: 2.3.1 - vite@5.4.21(@types/node@20.19.26): + vite@5.4.21(@types/node@20.19.26)(lightningcss@1.32.0): dependencies: esbuild: 0.21.5 postcss: 8.5.6 @@ -3707,8 +4024,9 @@ snapshots: optionalDependencies: '@types/node': 20.19.26 fsevents: 2.3.3 + lightningcss: 1.32.0 - vite@7.2.7(@types/node@20.19.26): + vite@7.2.7(@types/node@20.19.26)(jiti@2.7.0)(lightningcss@1.32.0): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.3) @@ -3719,11 +4037,13 @@ snapshots: optionalDependencies: '@types/node': 20.19.26 fsevents: 2.3.3 + jiti: 2.7.0 + lightningcss: 1.32.0 - vitest@4.0.16(@types/node@20.19.26)(@vitest/browser-playwright@4.0.16)(jsdom@27.4.0): + vitest@4.0.16(@types/node@20.19.26)(@vitest/browser-playwright@4.0.16)(jiti@2.7.0)(jsdom@27.4.0)(lightningcss@1.32.0): dependencies: '@vitest/expect': 4.0.16 - '@vitest/mocker': 4.0.16(vite@7.2.7(@types/node@20.19.26)) + '@vitest/mocker': 4.0.16(vite@7.2.7(@types/node@20.19.26)(jiti@2.7.0)(lightningcss@1.32.0)) '@vitest/pretty-format': 4.0.16 '@vitest/runner': 4.0.16 '@vitest/snapshot': 4.0.16 @@ -3740,11 +4060,11 @@ snapshots: tinyexec: 1.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.0.3 - vite: 7.2.7(@types/node@20.19.26) + vite: 7.2.7(@types/node@20.19.26)(jiti@2.7.0)(lightningcss@1.32.0) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 20.19.26 - '@vitest/browser-playwright': 4.0.16(playwright@1.57.0)(vite@7.2.7(@types/node@20.19.26))(vitest@4.0.16) + '@vitest/browser-playwright': 4.0.16(playwright@1.57.0)(vite@7.2.7(@types/node@20.19.26)(jiti@2.7.0)(lightningcss@1.32.0))(vitest@4.0.16) jsdom: 27.4.0 transitivePeerDependencies: - jiti From fe9d1360ab6f9e9a95ca615a29e7f983e7097c2a Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Wed, 8 Jul 2026 09:42:11 +0200 Subject: [PATCH 16/51] Init changes for tailwind. --- .../grid-lite/components-react/src/App.tsx | 16 +- .../grid-lite/components-react/src/index.css | 3 + packages/grid-lite-react/src/Grid.tsx | 12 +- .../grid-lite-react/src/styles/grid-core.css | 680 ++++++++++++++++++ .../src/styles/grid-theme-default.css | 63 ++ packages/grid-pro-react/src/Grid.tsx | 10 +- .../src/components/BaseGrid.tsx | 3 +- 7 files changed, 771 insertions(+), 16 deletions(-) create mode 100644 packages/grid-lite-react/src/styles/grid-core.css create mode 100644 packages/grid-lite-react/src/styles/grid-theme-default.css diff --git a/examples/grid-lite/components-react/src/App.tsx b/examples/grid-lite/components-react/src/App.tsx index 5f04361..eabd17b 100644 --- a/examples/grid-lite/components-react/src/App.tsx +++ b/examples/grid-lite/components-react/src/App.tsx @@ -75,7 +75,7 @@ function App() { @@ -90,10 +90,10 @@ function App() { style={{ fontWeight: '400' }} sortingEnabled sortingOrderSequence={['asc', 'desc', null]} - filteringEnabled - filteringInline={true} - filteringCondition="contains" - filteringValue="" + // filteringEnabled + // filteringInline={true} + // filteringCondition="contains" + // filteringValue="" // headerClassName="demo-header-cell" headerFormat="{id}" // cellClassName="demo-body-cell" @@ -101,8 +101,8 @@ function App() { cellRowHeader={false} /> Grid Caption v2.1 + className="p-4 bg-blue-500 text-white text-lg font-bold" + >Grid styled by Tailwind CSS
Grid Description ) { - const { gridRef, children, options, theme, ...gridProps } = props; + const { gridRef, children, options, theme, className, ...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 containerTheme = useMemo( + () => [theme, className].filter(Boolean).join(' ') || void 0, + [theme, className] + ); const gridOptions = useMemo( () => merge( childOptions, options ?? {}, - theme ? { rendering: { theme } } : {} + containerTheme ? { rendering: { theme: containerTheme } } : {} ) as Options, - [childOptions, options, theme] + [childOptions, options, containerTheme] ); return ( diff --git a/packages/grid-lite-react/src/styles/grid-core.css b/packages/grid-lite-react/src/styles/grid-core.css new file mode 100644 index 0000000..ccd0493 --- /dev/null +++ b/packages/grid-lite-react/src/styles/grid-core.css @@ -0,0 +1,680 @@ +@import '@highcharts/grid-lite/css/modules/grid-base-variables.css'; +@import '@highcharts/grid-lite/css/modules/grid-popup-variables.css'; +@import '@highcharts/grid-lite/css/modules/grid-menu-variables.css'; +@import '@highcharts/grid-lite/css/modules/grid-link-variables.css'; +@import '@highcharts/grid-lite/css/modules/grid-input-variables.css'; +@import '@highcharts/grid-lite/css/modules/grid-button-variables.css'; +@import '@highcharts/grid-lite/css/modules/grid-icon-variables.css'; +@import '@highcharts/grid-lite/css/modules/grid-pagination-variables.css'; +@import '@highcharts/grid-lite/css/modules/grid-table-variables.css'; +/* Grid container */ +.hcg-container { + container-type: inline-size; + container-name: hcg; + position: relative; + display: flex; + flex-direction: column; + height: 100%; + overflow: hidden; + box-sizing: border-box; + color-scheme: light dark; + max-height: inherit; +} + +.highcharts-light .hcg-container { + color-scheme: light; +} + +.highcharts-dark .hcg-container { + color-scheme: dark; +} + +.hcg-container * { + box-sizing: border-box; +} + +.hcg-container:has(.hcg-no-data) { + justify-content: center; + align-items: center; +} + +/* ---------------------------------------------------------- + INPUT ELEMENTS +------------------------------------------------------------ */ + +.hcg-container .hcg-input { + width: 100%; + + &:disabled { + opacity: 0.4; + cursor: not-allowed; + } + + &::placeholder { + color: #767676; + } + + &:focus-visible { + outline: none; + border-color: transparent; + } + + &[type="checkbox"] { + --ig-input-checkbox-size: 1.333em; + + appearance: none; + width: var(--ig-input-checkbox-size); + height: var(--ig-input-checkbox-size); + cursor: pointer; + position: relative; + + &:checked::before { + content: ""; + position: absolute; + inset: 0; + margin: 0.05em; + mask: center/contain no-repeat; + mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 13l4 4L19 7'/%3E%3C/svg%3E"); + } + } + + &.hcg-icon-search { + padding-left: 25px; + appearance: none; + background-repeat: no-repeat; + background-position: left 10px center; + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12' fill='none'%3e%3cpath d='M10.5 10.5L7.50005 7.5M8.5 5C8.5 6.933 6.933 8.5 5 8.5C3.067 8.5 1.5 6.933 1.5 5C1.5 3.067 3.067 1.5 5 1.5C6.933 1.5 8.5 3.067 8.5 5Z' stroke='%23767676' stroke-linecap='round' stroke-linejoin='round'/%3e%3c/svg%3e"); + } + + select& { + appearance: none; + background-image: url("data:image/svg+xml,%3csvg width='12' height='12' viewBox='0 0 12 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M3.5 7.5L6 10L8.5 7.5M3.5 4.5L6 2L8.5 4.5' stroke='%23767676' stroke-linecap='round' stroke-linejoin='round'/%3e%3c/svg%3e"); + background-repeat: no-repeat; + background-position: right 5px center; + white-space: nowrap; + text-overflow: ellipsis; + } +} + +/* ---------------------------------------------------------- + BUTTON ELEMENT +------------------------------------------------------------ */ + +.hcg-container :is(.hcg-button, .hcg-icon) { + position: relative; + display: inline-flex; + vertical-align: middle; + align-items: center; + justify-content: center; + flex-direction: row; + line-height: 1; + gap: 2px; + cursor: pointer; + transition: background-color 0.2s ease, box-shadow 0.2s ease, border 0.2s ease; + + svg { + width: 0.9em; + height: 0.9em; + display: block; + } + + span { + display: inline-block; + line-height: 1; + } + + span:empty { + display: none; + } + + &.reverse { + flex-direction: row-reverse; + } + + &:focus-visible { + outline: none; + border-color: transparent; + } + + &:disabled { + opacity: 0.4; + cursor: not-allowed; + } +} + +/* ---------------------------------------------------------- + TABLE ELEMENTS +------------------------------------------------------------ */ + +/* */ +.hcg-container .hcg-table { + width: 100%; + border-collapse: separate; + border-spacing: 0; + overflow: hidden; + table-layout: fixed; + flex: 1; + + &.hcg-scrollable-content { + display: flex; + flex-direction: column; + min-height: 0; + } + + /* */ + &.hcg-virtualization thead { + display: block; + } + + thead th { + position: relative; + } + + /* */ + &.hcg-scrollable-content > tbody { + height: 100%; + overflow: auto; + min-height: 0; + flex: 1; + } + + &.hcg-virtualization > tbody { + display: block; + position: relative; + } + + > tbody > tr { + overflow: hidden; + width: 100%; + } + + > tbody > tr > :where(.hcg-cell) { + position: relative; + line-height: 1em; + overflow: hidden; + } + + > tbody > tr.hcg-mocked-row > :where(.hcg-cell) { + white-space: nowrap; + text-overflow: ellipsis; + } + + .hcg-last-header-cell-in-row, + tbody tr > :where(.hcg-cell):last-child { + border-right: none; + } + + tbody tr:last-of-type > :where(.hcg-cell) { + border-bottom: none; + } + + &.hcg-scrollable-content > tbody > tr { + display: block; + } + + &.hcg-virtualization > tbody > tr { + position: absolute; + } + + > tbody.hcg-rows-content-nowrap > tr > :where(.hcg-cell) { + white-space: nowrap; + text-overflow: ellipsis; + } + + > tbody > tr > :where(.hcg-cell):focus { + outline: none; + } +} + +/* ---------------------------------------------------------- + HEADER ELEMENTS +------------------------------------------------------------ */ +.hcg-container thead th { + .hcg-header-cell-container { + display: flex; + align-items: center; + justify-content: space-between; + } + + .hcg-header-cell-content { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .hcg-header-cell-container.hcg-no-width .hcg-header-cell-content { + visibility: hidden; + transition: none; + } + + .hcg-header-cell-icons { + display: flex; + overflow: hidden; + align-items: center; + max-width: 0; + opacity: 0; + cursor: pointer; + transition: max-width 0.3s ease, opacity 0.3s ease; + } + + .hcg-header-cell-icons .hcg-icon.hcg-icon-selected::after { + content: ""; + position: absolute; + top: 2px; + right: 2px; + width: 0.3em; + height: 0.3em; + border-radius: 50%; + background: currentColor; + } + + :is(:hover, :focus-visible) .hcg-header-cell-icons, + .hcg-header-cell-icons:has(.hcg-button:focus-visible, .hcg-icon:focus-visible, .hcg-button.hcg-button-selected, .hcg-icon.hcg-icon-highlighted, .hcg-icon.hcg-icon-selected), + .hcg-header-cell-container.hcg-no-width .hcg-header-cell-icons { + max-width: 100px; + opacity: 1; + } + + .hcg-header-cell-container.hcg-no-width .hcg-header-cell-menu-icon { + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + margin-left: 0; + } + + .hcg-header-cell-icons .hcg-header-cell-menu-icon .hcg-icon { + padding-inline: 3px; + } + + .hcg-header-cell-icons > :first-child { + margin-left: 5px; + } + + .hcg-column-resizer { + position: absolute; + display: flex; + align-items: center; + justify-content: center; + top: 0; + width: 9px; + right: -5px; + height: 100%; + user-select: none; + touch-action: none; + z-index: 10; + cursor: col-resize; + } + + .hcg-column-resizer.hovered::after { + content: ""; + height: 100%; + } +} + +/* ---------------------------------------------------------- + PAGINATION ELEMENTS +------------------------------------------------------------ */ + +.hcg-container .hcg-pagination { + display: flex; + align-items: center; + gap: 0.75rem; + flex-wrap: nowrap; + + > * { + flex: 1 1 0; + min-width: 0; + display: flex; + align-items: center; + } + + .hcg-pagination-info { + justify-content: flex-start; + } + + .hcg-pagination-controls { + justify-content: center; + gap: 2px; + + .hcg-pagination-pages { + display: flex; + flex-wrap: nowrap; + gap: 2px; + + .hcg-button { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 30px; + } + + span { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 20px; + } + } + } + + .hcg-pagination-page-size { + justify-content: flex-end; + text-align: right; + + select.hcg-input { + width: 60px; + margin-left: 8px; + } + } + + /* .hcg-pagination-nav-dropdown { + display: none; + min-width: 200px; + } */ + + &.hcg-pagination-left, + &.hcg-pagination-center, + &.hcg-pagination-right { + > * { + flex: 0 0 auto; + min-width: auto; + } + } + + &.hcg-pagination-left { justify-content: flex-start; } + &.hcg-pagination-center { justify-content: center; } + &.hcg-pagination-right { justify-content: flex-end; } + + &:not(:has(.hcg-pagination-info)) .hcg-pagination-controls { + justify-content: flex-start; + } + + &:not(:has(.hcg-pagination-page-size)) .hcg-pagination-controls { + justify-content: flex-end; + } +} + +@container hcg (max-width: 800px) { + .hcg-container .hcg-pagination { + flex-direction: column; + align-items: stretch; + --ig-pagination-stacked-align: center; + + &.hcg-pagination-left { --ig-pagination-stacked-align: flex-start; } + &.hcg-pagination-right { --ig-pagination-stacked-align: flex-end; } + + > * { + flex: 0 0 auto; + justify-content: var(--ig-pagination-stacked-align); + } + + .hcg-pagination-info, + .hcg-pagination-controls, + .hcg-pagination-page-size { + justify-content: var(--ig-pagination-stacked-align); + } + + .hcg-pagination-page-size { + text-align: center; + } + &.hcg-pagination-left .hcg-pagination-page-size { text-align: left; } + &.hcg-pagination-right .hcg-pagination-page-size { text-align: right; } + + &:not(:has(.hcg-pagination-page-size)) .hcg-pagination-controls, + &:not(:has(.hcg-pagination-info)) .hcg-pagination-controls { + justify-content: var(--ig-pagination-stacked-align); + } + } +} + +/* ---------------------------------------------------------- + CREDITS ELEMENT +------------------------------------------------------------ */ + +.hcg-credits, +.highcharts-light .hcg-credits { + display: block; + width: 114px; + height: 20px; + background-size: contain; + background-repeat: no-repeat; + background-image: + image-set( + /* stylelint-disable-next-line function-comma-newline-after */ + url("https://assets.highcharts.com/grid/logo_light.png") 1x, + url("https://assets.highcharts.com/grid/logo_lightx2.png") 2x + ); +} + +@media (prefers-color-scheme: dark) { + .hcg-credits { + background-image: + image-set( + /* stylelint-disable-next-line function-comma-newline-after */ + url("https://assets.highcharts.com/grid/logo_dark.png") 1x, + url("https://assets.highcharts.com/grid/logo_darkx2.png") 2x + ); + } +} + +.highcharts-dark .hcg-credits { + background-image: + image-set( + /* stylelint-disable-next-line function-comma-newline-after */ + url("https://assets.highcharts.com/grid/logo_dark.png") 1x, + url("https://assets.highcharts.com/grid/logo_darkx2.png") 2x + ); +} + +/* ---------------------------------------------------------- + POPUP ELEMENTS +------------------------------------------------------------ */ +.hcg-container .hcg-popup { + position: absolute; + z-index: 1000; + border-radius: 6px; + box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.08), 0 7px 7px 0 rgba(0, 0, 0, 0.07), 0 17px 10px 0 rgba(0, 0, 0, 0.04), 0 30px 12px 0 rgba(0, 0, 0, 0.01); + min-width: 200px; + overflow: auto; + border-width: 1px; + border-style: solid; + + .hcg-popup-content { + padding: 5px; + } + + .hcg-menu-header { + font-size: 0.75rem; + padding: 3px; + margin-bottom: 5px; + } + + .hcg-menu-header-category { + opacity: 0.5; + user-select: none; + } +} + +/* ---------------------------------------------------------- + MENU ELEMENTS +------------------------------------------------------------ */ + +.hcg-container .hcg-menu-container { + margin: 0; + display: flex; + flex-direction: column; + list-style: none; + row-gap: 5px; + padding: 0; + + .hcg-menu-item { + display: flex; + align-items: center; + gap: 2px; + width: 100%; + min-width: 185px; + min-height: 2rem; + padding: 8px 8px 8px 12px; + font-size: 0.75rem; + font-weight: 600; + background-color: transparent; + border: 1px solid transparent; + border-radius: 5px; + } + + .hcg-menu-item:not(:disabled) { + cursor: pointer; + } + + .hcg-menu-item:focus-visible { + outline: none; + } + + .hcg-menu-item-icon { + --icon-size: 16px; + + flex: 0 0 var(--icon-size); + width: var(--icon-size); + height: var(--icon-size); + display: inline-flex; + align-items: center; + justify-content: center; + opacity: 0.6; + } + + .hcg-menu-item.active .hcg-menu-item-icon, + .hcg-menu-item.highlighted .hcg-menu-item-icon, + .hcg-menu-item:not(:disabled):hover .hcg-menu-item-icon { + opacity: 1; + } + + .hcg-menu-item-label { + flex: 1 1 auto; + min-width: 0; + text-align: left; + padding-left: 0.75rem; + } + + .hcg-menu-divider { + border-top-width: 1px; + border-top-style: solid; + height: 0; + } +} + +/* ---------------------------------------------------------- + FILTERING ELEMENTS +------------------------------------------------------------ */ + +.hcg-header-cell:has(.hcg-column-filter-wrapper) { + overflow: hidden; +} + +.hcg-column-filter-wrapper { + width: 100%; + display: flex; + flex-flow: column; + row-gap: 5px; + min-width: 50px; +} + +.hcg-clear-filter-button { + appearance: none; + background: none; + border: 0; + padding: 0; + margin: 0; + display: inline; + vertical-align: baseline; + font: inherit; + font-size: 0.625rem; + white-space: nowrap; + font-weight: normal; + align-self: end; +} + +.hcg-clear-filter-button:hover { + text-decoration: underline; + cursor: pointer; +} + +.hcg-clear-filter-button:disabled, +.hcg-clear-filter-button:disabled:hover { + opacity: 0.5; + text-decoration: none; + cursor: default; +} + +/* ---------------------------------------------------------- + OTHER ELEMENTS +------------------------------------------------------------ */ + +/* Sorting */ +.hcg-table thead th.hcg-column-sortable { + cursor: pointer; +} + +/* Accessibility */ +.hcg-visually-hidden { + position: absolute; + width: 1px; + height: 1px; + overflow: hidden; + white-space: nowrap; + clip: rect(1px, 1px, 1px, 1px); + margin-top: -3px; + opacity: 0.01; +} + +/* Loader */ +.hcg-loading-wrapper { + display: flex; + align-items: center; + justify-content: center; + position: absolute; + width: 100%; + height: 100%; + gap: 10px; + color: light-dark(#000000, #ffffff); +} + +.hcg-loading-wrapper .hcg-spinner { + border-top-width: 5px; + border-top-style: solid; + border-top-color: light-dark(#000000, #ffffff); + border-radius: 50%; + width: 30px; + height: 30px; + animation: spin 1s linear infinite; +} + +@keyframes spin { + from { + transform: rotate(0deg); + } + + to { + transform: rotate(360deg); + } +} + +/* Start Grid CSS Helpers Classes */ + +.hcg-table thead tr th.hcg-right .hcg-header-cell-content, +.hcg-table tbody tr > :where(.hcg-cell).hcg-right { + text-align: right; +} + +.hcg-table thead tr th.hcg-center .hcg-header-cell-content, +.hcg-table tbody tr > :where(.hcg-cell).hcg-center { + text-align: center; +} + +.hcg-table thead tr th.hcg-left .hcg-header-cell-content, +.hcg-table tbody tr > :where(.hcg-cell).hcg-left { + text-align: left; +} + +/* End Grid CSS Helpers Classes */ diff --git a/packages/grid-lite-react/src/styles/grid-theme-default.css b/packages/grid-lite-react/src/styles/grid-theme-default.css new file mode 100644 index 0000000..7e85955 --- /dev/null +++ b/packages/grid-lite-react/src/styles/grid-theme-default.css @@ -0,0 +1,63 @@ +@import '@highcharts/grid-lite/css/modules/grid-theme-default.css'; + +.hcg-theme-default { + --hcg-description-color: var(--hcg-color); + --hcg-description-background: transparent; + --hcg-description-font-weight: normal; + --hcg-description-font-size: var(--hcg-font-size); + --hcg-description-font-family: inherit; + --hcg-description-line-height: normal; + --hcg-description-letter-spacing: normal; + --hcg-description-text-align: left; + --hcg-description-margin-top: 0; + --hcg-description-margin-right: 0; + --hcg-description-margin-bottom: 0; + --hcg-description-margin-left: 0; + --hcg-description-padding-top: var(--hcg-padding); + --hcg-description-padding-right: var(--hcg-padding); + --hcg-description-padding-bottom: 0; + --hcg-description-padding-left: var(--hcg-padding); +} + +.hcg-theme-default .hcg-caption { + color: var(--hcg-caption-color); + background: var(--hcg-caption-background); + font-weight: var(--hcg-caption-font-weight); + font-size: var(--hcg-caption-font-size); + font-family: var(--hcg-caption-font-family); + line-height: var(--hcg-caption-line-height); + letter-spacing: var(--hcg-caption-letter-spacing); + text-align: var(--hcg-caption-text-align); + margin-top: var(--hcg-caption-margin-top); + margin-right: var(--hcg-caption-margin-right); + margin-bottom: var(--hcg-caption-margin-bottom); + margin-left: var(--hcg-caption-margin-left); + padding-top: var(--hcg-caption-padding-top); + padding-right: var(--hcg-caption-padding-right); + padding-bottom: var(--hcg-caption-padding-bottom); + padding-left: var(--hcg-caption-padding-left); +} + +.hcg-theme-default.hcg-caption * { + font: inherit; + margin: var(--hcg-caption-child-margin); +} + +.hcg-theme-default .hcg-description { + color: var(--hcg-description-color); + background: var(--hcg-description-background); + font-weight: var(--hcg-description-font-weight); + font-size: var(--hcg-description-font-size); + font-family: var(--hcg-description-font-family); + line-height: var(--hcg-description-line-height); + letter-spacing: var(--hcg-description-letter-spacing); + text-align: var(--hcg-description-text-align); + margin-top: var(--hcg-description-margin-top); + margin-right: var(--hcg-description-margin-right); + margin-bottom: var(--hcg-description-margin-bottom); + margin-left: var(--hcg-description-margin-left); + padding-top: var(--hcg-description-padding-top); + padding-right: var(--hcg-description-padding-right); + padding-bottom: var(--hcg-description-padding-bottom); + padding-left: var(--hcg-description-padding-left); +} diff --git a/packages/grid-pro-react/src/Grid.tsx b/packages/grid-pro-react/src/Grid.tsx index 7f5d317..a70800b 100644 --- a/packages/grid-pro-react/src/Grid.tsx +++ b/packages/grid-pro-react/src/Grid.tsx @@ -19,20 +19,24 @@ 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, theme, ...gridProps } = props; + const { gridRef, children, options, theme, className, ...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 containerTheme = useMemo( + () => [theme, className].filter(Boolean).join(' ') || void 0, + [theme, className] + ); const gridOptions = useMemo( () => merge( childOptions, options ?? {}, - theme ? { rendering: { theme } } : {} + containerTheme ? { rendering: { theme: containerTheme } } : {} ) as Options, - [childOptions, options, theme] + [childOptions, options, containerTheme] ); return ( diff --git a/packages/grid-shared-react/src/components/BaseGrid.tsx b/packages/grid-shared-react/src/components/BaseGrid.tsx index 11be983..917eb36 100644 --- a/packages/grid-shared-react/src/components/BaseGrid.tsx +++ b/packages/grid-shared-react/src/components/BaseGrid.tsx @@ -33,7 +33,8 @@ export interface GridProps { */ options?: TOptions; /** - * Optional CSS class name applied to the root grid container. + * Optional CSS class names applied on the Grid container (`hcg-container`), + * merged with `theme` into `rendering.theme`. */ className?: string; /** From 605ce6c8a476b5e7a8cd73a527d0125aca671465 Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Wed, 8 Jul 2026 10:24:19 +0200 Subject: [PATCH 17/51] Added pagination position. --- .../grid-lite/components-react/src/App.tsx | 13 +++++++ .../src/utils/getChildProps.ts | 35 ++++++++++++++++++- 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/examples/grid-lite/components-react/src/App.tsx b/examples/grid-lite/components-react/src/App.tsx index cdb84e1..f24838d 100644 --- a/examples/grid-lite/components-react/src/App.tsx +++ b/examples/grid-lite/components-react/src/App.tsx @@ -77,6 +77,19 @@ function App() { // gridRef={grid} callback={onGridCallback} > + {/* */} { const resolvedChildren = flattenChildren(children) .map((child) => resolveOptionChild(child)) .filter((child): child is ReactElement => child !== null); + const firstNonPaginationIndex = getFirstNonPaginationIndex(resolvedChildren); function handleChildren( childNodes: ReactNode, @@ -205,7 +206,13 @@ export function getChildProps(children: ReactNode): Record { } if (meta.gridOption === 'pagination') { - optionsFromChildren.pagination = normalizePaginationOptions(props); + const pagination = normalizePaginationOptions(props); + pagination.position = isTopPaginationChild( + child, + resolvedChildren, + firstNonPaginationIndex + ) ? 'top' : 'bottom'; + optionsFromChildren.pagination = pagination; return; } @@ -275,6 +282,32 @@ function applyDeclarativeColumnDefaults( } } +function getFirstNonPaginationIndex(children: ReactElement[]): number { + return children.findIndex((child) => { + const component = getOptionComponent(child.type); + + return component?._GridReact.gridOption !== 'pagination'; + }); +} + +function isTopPaginationChild( + child: ReactElement, + children: ReactElement[], + firstNonPaginationIndex: number +): boolean { + const childIndex = children.indexOf(child); + + if (childIndex === -1) { + return false; + } + + if (firstNonPaginationIndex === -1) { + return true; + } + + return childIndex < firstNonPaginationIndex; +} + function isOptionElement(child: ReactElement): boolean { return getOptionComponent(child.type) !== null; } From 76f09c9a4db251923bf18d433055cb1c4fc92493 Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Wed, 8 Jul 2026 10:51:16 +0200 Subject: [PATCH 18/51] Optymized pagination position. --- .../src/utils/getChildProps.ts | 28 +++++-------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/packages/grid-shared-react/src/utils/getChildProps.ts b/packages/grid-shared-react/src/utils/getChildProps.ts index ea9712e..cc4880d 100644 --- a/packages/grid-shared-react/src/utils/getChildProps.ts +++ b/packages/grid-shared-react/src/utils/getChildProps.ts @@ -142,7 +142,6 @@ export function getChildProps(children: ReactNode): Record { const resolvedChildren = flattenChildren(children) .map((child) => resolveOptionChild(child)) .filter((child): child is ReactElement => child !== null); - const firstNonPaginationIndex = getFirstNonPaginationIndex(resolvedChildren); function handleChildren( childNodes: ReactNode, @@ -207,11 +206,9 @@ export function getChildProps(children: ReactNode): Record { if (meta.gridOption === 'pagination') { const pagination = normalizePaginationOptions(props); - pagination.position = isTopPaginationChild( - child, - resolvedChildren, - firstNonPaginationIndex - ) ? 'top' : 'bottom'; + pagination.position = isTopPaginationChild(child, resolvedChildren) ? + 'top' : + 'bottom'; optionsFromChildren.pagination = pagination; return; } @@ -282,18 +279,9 @@ function applyDeclarativeColumnDefaults( } } -function getFirstNonPaginationIndex(children: ReactElement[]): number { - return children.findIndex((child) => { - const component = getOptionComponent(child.type); - - return component?._GridReact.gridOption !== 'pagination'; - }); -} - function isTopPaginationChild( child: ReactElement, - children: ReactElement[], - firstNonPaginationIndex: number + children: ReactElement[] ): boolean { const childIndex = children.indexOf(child); @@ -301,11 +289,9 @@ function isTopPaginationChild( return false; } - if (firstNonPaginationIndex === -1) { - return true; - } - - return childIndex < firstNonPaginationIndex; + return children + .slice(0, childIndex) + .every((candidate) => getOptionComponent(candidate.type)?._GridReact.gridOption === 'pagination'); } function isOptionElement(child: ReactElement): boolean { From 6d16a7568bb13eab1a085d1f6b6598fff956e69c Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Wed, 8 Jul 2026 11:54:30 +0200 Subject: [PATCH 19/51] Added components tests. --- packages/grid-lite-react/tests/Grid.test.tsx | 23 +++ packages/grid-pro-react/tests/Grid.test.tsx | 23 +++ .../tests/createGridTests.tsx | 138 ++++++++++++++++++ .../tests/options/Data.test.tsx | 38 +++++ .../tests/options/Header.test.tsx | 13 ++ .../tests/options/Pagination.test.tsx | 63 ++++++++ 6 files changed, 298 insertions(+) create mode 100644 packages/grid-lite-react/tests/Grid.test.tsx create mode 100644 packages/grid-pro-react/tests/Grid.test.tsx create mode 100644 packages/grid-shared-react/tests/createGridTests.tsx create mode 100644 packages/grid-shared-react/tests/options/Data.test.tsx create mode 100644 packages/grid-shared-react/tests/options/Header.test.tsx create mode 100644 packages/grid-shared-react/tests/options/Pagination.test.tsx diff --git a/packages/grid-lite-react/tests/Grid.test.tsx b/packages/grid-lite-react/tests/Grid.test.tsx new file mode 100644 index 0000000..140e4eb --- /dev/null +++ b/packages/grid-lite-react/tests/Grid.test.tsx @@ -0,0 +1,23 @@ +import { createGridTests } from '@highcharts/grid-shared-react/tests/createGridTests'; +import { Grid, GridOptions } from '../src/index'; + +createGridTests( + 'Grid Lite', + Grid, + { + dataTable: { + columns: { + name: ['Alice', 'Bob'], + age: [30, 25] + } + } + }, + { + dataTable: { + columns: { + name: ['Charlie', 'Diana'], + age: [40, 35] + } + } + } +); diff --git a/packages/grid-pro-react/tests/Grid.test.tsx b/packages/grid-pro-react/tests/Grid.test.tsx new file mode 100644 index 0000000..65e49d7 --- /dev/null +++ b/packages/grid-pro-react/tests/Grid.test.tsx @@ -0,0 +1,23 @@ +import { createGridTests } from '@highcharts/grid-shared-react/tests/createGridTests'; +import { Grid, GridOptions } from '../src/index'; + +createGridTests( + 'Grid Pro', + Grid, + { + dataTable: { + columns: { + name: ['Alice', 'Bob'], + age: [30, 25] + } + } + }, + { + dataTable: { + columns: { + name: ['Charlie', 'Diana'], + age: [40, 35] + } + } + } +); diff --git a/packages/grid-shared-react/tests/createGridTests.tsx b/packages/grid-shared-react/tests/createGridTests.tsx new file mode 100644 index 0000000..acf05db --- /dev/null +++ b/packages/grid-shared-react/tests/createGridTests.tsx @@ -0,0 +1,138 @@ +import { render, waitFor, fireEvent } from '@testing-library/react'; +import { + useRef, + useState, + type ComponentType +} from 'react'; +import { describe, it, expect, vi } from 'vitest'; +import { GridProps, GridRefHandle } from '../src/components/BaseGrid'; +import { GridInstance } from '../src/hooks/useGrid'; + +/** + * Creates a standard test suite for a Grid component. + * Use this to avoid duplicating tests between + * grid-lite-react and grid-pro-react. + */ +export function createGridTests( + name: string, + GridComponent: ComponentType>, + testOptions: TOptions, + updatedOptions: TOptions +) { + + describe(name, () => { + it('renders a container div and initializes grid', async () => { + let gridInstance: GridInstance | null = null; + + const onGridReady = (grid: GridInstance) => { + gridInstance = grid; + }; + + const { container } = render( + + ); + + expect(container.firstChild).toBeInstanceOf(HTMLDivElement); + + await waitFor(() => { + expect(gridInstance).not.toBeNull(); + }); + }); + + it('provides grid instance via gridRef prop', async () => { + let gridRef: React.RefObject | null>; + let initialized = false; + + function TestComponent() { + gridRef = useRef>(null); + return ( + { initialized = true; }} + /> + ); + } + + render(); + + await waitFor(() => { + expect(initialized).toBe(true); + expect(gridRef.current?.grid).toBeDefined(); + }); + }); + + it('calls callback when grid is initialized', async () => { + const callback = vi.fn(); + render(); + + await waitFor(() => { + expect(callback).toHaveBeenCalled(); + }); + }); + + it('updates grid when options change', async () => { + let gridInstance: GridInstance | null = null; + + function TestComponent() { + const [opts, setOpts] = useState(testOptions); + + const onGridReady = (grid: GridInstance) => { + gridInstance = grid; + }; + + return ( + <> + + + + ); + } + + const { getByTestId, container } = render(); + + // Wait for initial grid creation + await waitFor(() => { + expect(gridInstance).not.toBeNull(); + }); + + // Trigger options change + fireEvent.click(getByTestId('update-options')); + + // Wait for the grid to update with new data + await waitFor(() => { + const cells = container.querySelectorAll('td[data-value]'); + const values = Array.from(cells).map(c => c.getAttribute('data-value')); + expect(values).toContain('Charlie'); + }); + }); + + it('calls destroy() on unmount', async () => { + let destroySpy: ReturnType | null = null; + + const onGridReady = (grid: GridInstance) => { + destroySpy = vi.spyOn(grid, 'destroy'); + }; + + const { unmount } = render( + + ); + + // Wait for grid to initialize + await waitFor(() => { + expect(destroySpy).not.toBeNull(); + }); + + // Unmount and verify destroy was called + unmount(); + + expect(destroySpy).toHaveBeenCalledTimes(1); + }); + + }); +} diff --git a/packages/grid-shared-react/tests/options/Data.test.tsx b/packages/grid-shared-react/tests/options/Data.test.tsx new file mode 100644 index 0000000..a6f165a --- /dev/null +++ b/packages/grid-shared-react/tests/options/Data.test.tsx @@ -0,0 +1,38 @@ +import { describe, it, expect } from 'vitest'; +import { Data } from '../../src/components/options/data/Data'; +import { getChildProps } from '../../src/utils/getChildProps'; + +describe('Data', () => { + it('maps columns to options.data.columns', () => { + const columns = { + product: ['Apples', 'Oranges'], + price: [1.2, 2.4] + }; + + expect( + getChildProps( + + ) + ).toEqual({ + data: { + columns, + providerType: 'local', + autogenerateColumns: true + } + }); + }); + + it('maps dataTable to options.data.dataTable', () => { + const dataTable = { id: 'table-1', rows: [] }; + + expect(getChildProps()).toEqual({ + data: { + dataTable + } + }); + }); +}); diff --git a/packages/grid-shared-react/tests/options/Header.test.tsx b/packages/grid-shared-react/tests/options/Header.test.tsx new file mode 100644 index 0000000..58fa656 --- /dev/null +++ b/packages/grid-shared-react/tests/options/Header.test.tsx @@ -0,0 +1,13 @@ +import { describe, it, expect } from 'vitest'; +import { Header } from '../../src/components/options/header/Header'; +import { getChildProps } from '../../src/utils/getChildProps'; + +describe('Header', () => { + it('maps header prop to options.header', () => { + const header = ['product', { columnId: 'price', format: '{value} USD' }]; + + expect(getChildProps(
)).toEqual({ + header + }); + }); +}); diff --git a/packages/grid-shared-react/tests/options/Pagination.test.tsx b/packages/grid-shared-react/tests/options/Pagination.test.tsx new file mode 100644 index 0000000..1608d8d --- /dev/null +++ b/packages/grid-shared-react/tests/options/Pagination.test.tsx @@ -0,0 +1,63 @@ +import { describe, it, expect } from 'vitest'; +import { Data } from '../../src/components/options/data/Data'; +import { Pagination } from '../../src/components/options/pagination/Pagination'; +import { getChildProps } from '../../src/utils/getChildProps'; + +describe('Pagination', () => { + it('normalizes pagination props into options.pagination', () => { + expect( + getChildProps( + + ) + ).toEqual({ + pagination: { + enabled: false, + page: 2, + pageSize: 25, + align: 'center', + position: 'top', + controls: { + pageInfo: true, + pageSizeSelector: { + enabled: true, + options: [10, 25, 50] + }, + pageButtons: { + enabled: true, + count: 5 + }, + firstLastButtons: true, + previousNextButtons: false + } + } + }); + }); + + it('sets position to top for the first pagination and bottom after other options', () => { + const top = getChildProps( + <> + + + + ); + const bottom = getChildProps( + <> + + + + ); + + expect(top.pagination).toMatchObject({ position: 'top' }); + expect(bottom.pagination).toMatchObject({ position: 'bottom' }); + }); +}); From 3796a7e671809de7fca60418e54079d18fbf5f87 Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Wed, 8 Jul 2026 12:30:24 +0200 Subject: [PATCH 20/51] Added linter to PR runner. --- .github/workflows/test.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c844038..cc0a7ed 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -26,6 +26,9 @@ jobs: - name: Build packages run: pnpm build + - name: Run linter + run: pnpm lint + - name: Install Playwright browsers run: pnpm exec playwright install --with-deps chromium From aa121c873d307376b62c8d84fcfb9086c2cbe3ee Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Wed, 8 Jul 2026 13:08:31 +0200 Subject: [PATCH 21/51] Rephrased rules in linter. --- eslint.config.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/eslint.config.js b/eslint.config.js index 6be1776..d7605c1 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -24,7 +24,13 @@ export default defineConfig( '@stylistic/quotes': ['error', 'single', { avoidEscape: true }], '@stylistic/brace-style': ['error', '1tbs', { allowSingleLine: true }], '@stylistic/eol-last': ['error', 'always'], - '@stylistic/no-trailing-spaces': ['error'] + '@stylistic/no-trailing-spaces': ['error'], + '@stylistic/max-len': ['error', { + code: 80, + ignoreUrls: true, + ignoreStrings: true, + ignoreTemplateLiterals: true + }] }, }, { From 0e1a9099ff47a032eac09c31db5fa46ce6b4aa31 Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Thu, 9 Jul 2026 10:08:57 +0200 Subject: [PATCH 22/51] Added husky precommit action. --- .github/workflows/test.yml | 27 +++++++++++++++++++++++---- .husky/pre-commit | 2 ++ vitest.config.ts | 5 ++++- 3 files changed, 29 insertions(+), 5 deletions(-) create mode 100755 .husky/pre-commit diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cc0a7ed..3cceffc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,9 +1,31 @@ -name: Tests +name: CI on: pull_request: jobs: + lint: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install pnpm + uses: pnpm/action-setup@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'pnpm' + + - name: Install dependencies + run: pnpm install + + - name: Run linter + run: pnpm lint + test: runs-on: ubuntu-latest @@ -26,9 +48,6 @@ jobs: - name: Build packages run: pnpm build - - name: Run linter - run: pnpm lint - - name: Install Playwright browsers run: pnpm exec playwright install --with-deps chromium diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 0000000..acd310a --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,2 @@ +pnpm lint +pnpm test diff --git a/vitest.config.ts b/vitest.config.ts index 8ad4fee..e23f4e9 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -3,7 +3,10 @@ import { playwright } from '@vitest/browser-playwright'; export default defineConfig({ test: { - include: ['packages/*/src/**/*.test.{ts,tsx}'], + include: [ + 'packages/*/src/**/*.test.{ts,tsx}', + 'packages/*/tests/**/*.test.{ts,tsx}' + ], globals: true, css: true, browser: { From 7b50a33103f9c477ed7f1a22657a586cf537db79 Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Thu, 9 Jul 2026 10:12:07 +0200 Subject: [PATCH 23/51] Linted. --- .../src/components/BaseGridOptions.ts | 6 +- .../src/test/createGridTests.tsx | 137 ------------------ .../src/utils/getChildProps.ts | 60 +++++--- .../src/utils/mappers/mapPrefixedProps.ts | 8 +- 4 files changed, 52 insertions(+), 159 deletions(-) delete mode 100644 packages/grid-shared-react/src/test/createGridTests.tsx diff --git a/packages/grid-shared-react/src/components/BaseGridOptions.ts b/packages/grid-shared-react/src/components/BaseGridOptions.ts index 9440edc..aabeb10 100644 --- a/packages/grid-shared-react/src/components/BaseGridOptions.ts +++ b/packages/grid-shared-react/src/components/BaseGridOptions.ts @@ -8,7 +8,8 @@ */ /** - * Metadata attached to declarative option components rendered as BaseGrid children. + * Metadata attached to declarative option components + * rendered as BaseGrid children. */ export interface BaseGridOptions { type: 'Grid_Option'; @@ -25,7 +26,8 @@ export interface BaseGridOptions { } /** - * A React component that maps JSX props to a Grid options path via `_GridReact`. + * A React component that maps JSX props to a Grid options path + * via `_GridReact`. */ export interface BaseGridOptionsComponent { _GridReact: BaseGridOptions; diff --git a/packages/grid-shared-react/src/test/createGridTests.tsx b/packages/grid-shared-react/src/test/createGridTests.tsx deleted file mode 100644 index 1faed12..0000000 --- a/packages/grid-shared-react/src/test/createGridTests.tsx +++ /dev/null @@ -1,137 +0,0 @@ -import { render, waitFor, fireEvent } from '@testing-library/react'; -import { - useRef, - useState, - type ComponentType -} from 'react'; -import { describe, it, expect, vi } from 'vitest'; -import { GridProps, GridRefHandle } from '../components/BaseGrid'; -import { GridInstance } from '../hooks/useGrid'; - -/** - * Creates a standard test suite for a Grid component. - * Use this to avoid duplicating tests between grid-lite-react and grid-pro-react. - */ -export function createGridTests( - name: string, - GridComponent: ComponentType>, - testOptions: TOptions, - updatedOptions: TOptions -) { - - describe(name, () => { - it('renders a container div and initializes grid', async () => { - let gridInstance: GridInstance | null = null; - - const onGridReady = (grid: GridInstance) => { - gridInstance = grid; - }; - - const { container } = render( - - ); - - expect(container.firstChild).toBeInstanceOf(HTMLDivElement); - - await waitFor(() => { - expect(gridInstance).not.toBeNull(); - }); - }); - - it('provides grid instance via gridRef prop', async () => { - let gridRef: React.RefObject | null>; - let initialized = false; - - function TestComponent() { - gridRef = useRef>(null); - return ( - { initialized = true; }} - /> - ); - } - - render(); - - await waitFor(() => { - expect(initialized).toBe(true); - expect(gridRef.current?.grid).toBeDefined(); - }); - }); - - it('calls callback when grid is initialized', async () => { - const callback = vi.fn(); - render(); - - await waitFor(() => { - expect(callback).toHaveBeenCalled(); - }); - }); - - it('updates grid when options change', async () => { - let gridInstance: GridInstance | null = null; - - function TestComponent() { - const [opts, setOpts] = useState(testOptions); - - const onGridReady = (grid: GridInstance) => { - gridInstance = grid; - }; - - return ( - <> - - - - ); - } - - const { getByTestId, container } = render(); - - // Wait for initial grid creation - await waitFor(() => { - expect(gridInstance).not.toBeNull(); - }); - - // Trigger options change - fireEvent.click(getByTestId('update-options')); - - // Wait for the grid to update with new data - await waitFor(() => { - const cells = container.querySelectorAll('td[data-value]'); - const values = Array.from(cells).map(c => c.getAttribute('data-value')); - expect(values).toContain('Charlie'); - }); - }); - - it('calls destroy() on unmount', async () => { - let destroySpy: ReturnType | null = null; - - const onGridReady = (grid: GridInstance) => { - destroySpy = vi.spyOn(grid, 'destroy'); - }; - - const { unmount } = render( - - ); - - // Wait for grid to initialize - await waitFor(() => { - expect(destroySpy).not.toBeNull(); - }); - - // Unmount and verify destroy was called - unmount(); - - expect(destroySpy).toHaveBeenCalledTimes(1); - }); - - }); -} diff --git a/packages/grid-shared-react/src/utils/getChildProps.ts b/packages/grid-shared-react/src/utils/getChildProps.ts index cc4880d..bc1c5a6 100644 --- a/packages/grid-shared-react/src/utils/getChildProps.ts +++ b/packages/grid-shared-react/src/utils/getChildProps.ts @@ -59,7 +59,9 @@ function getOptionComponent(type: unknown): BaseGridOptionsComponent | null { return component._GridReact ? type as BaseGridOptionsComponent : null; } -function getChildPropsFromElement(child: ReactElement): Record { +function getChildPropsFromElement( + child: ReactElement +): Record { return (child.props ?? {}) as Record; } @@ -87,7 +89,8 @@ function flattenChildren(childNodes: ReactNode): ReactNode[] { } if (isReactElement(childNodes) && childNodes.type === Fragment) { - return flattenChildren((childNodes.props as { children?: ReactNode }).children); + const fragmentProps = childNodes.props as { children?: ReactNode }; + return flattenChildren(fragmentProps.children); } return [childNodes]; @@ -115,7 +118,15 @@ function getEffectiveMeta( } function parseColumnElement(child: ReactElement): Record { - const { children: _ignored, columnId, id: _cssId, ...props } = getChildPropsFromElement(child); + const { + children, + id, + columnId, + ...props + } = getChildPropsFromElement(child); + void children; + void id; + const options = normalizeColumnOptions(props); // columnId selects the column; Core expects the same value as `id`. @@ -178,7 +189,10 @@ export function getChildProps(children: ReactNode): Record { } } - function handleChild(child: ReactElement, parentMeta?: BaseGridOptions): void { + function handleChild( + child: ReactElement, + parentMeta?: BaseGridOptions + ): void { const component = getOptionComponent(child.type); if (!component) { @@ -206,18 +220,17 @@ export function getChildProps(children: ReactNode): Record { if (meta.gridOption === 'pagination') { const pagination = normalizePaginationOptions(props); - pagination.position = isTopPaginationChild(child, resolvedChildren) ? - 'top' : - 'bottom'; + pagination.position = isTopPaginationChild( + child, + resolvedChildren + ) ? 'top' : 'bottom'; optionsFromChildren.pagination = pagination; return; } if (meta.gridOption === 'header') { - const { header, children: _ignored } = props; - - if (header !== void 0) { - optionsFromChildren.header = header; + if (props.header !== void 0) { + optionsFromChildren.header = props.header; } return; } @@ -226,7 +239,9 @@ export function getChildProps(children: ReactNode): Record { optionsFromChildren[meta.gridOption] = meta.isArrayType ? [] : {} ); const parentIsArray = Array.isArray(optionParent); - const insertInto = parentIsArray ? {} : optionParent as Record; + const insertInto = parentIsArray + ? {} + : optionParent as Record; if (meta.defaultOptions) { Object.assign(insertInto, meta.defaultOptions); @@ -243,7 +258,10 @@ export function getChildProps(children: ReactNode): Record { } if (parentIsArray) { - (optionsFromChildren[meta.gridOption] as unknown[]).push(insertInto); + const optionItems = optionsFromChildren[ + meta.gridOption + ] as unknown[]; + optionItems.push(insertInto); } } @@ -258,7 +276,8 @@ export function getChildProps(children: ReactNode): Record { /** * When declarative `` components are present, only those columns - * should render unless `data.autogenerateColumns` is set explicitly on ``. + * should render unless `data.autogenerateColumns` is set + * explicitly on ``. */ function applyDeclarativeColumnDefaults( optionsFromChildren: Record @@ -291,7 +310,11 @@ function isTopPaginationChild( return children .slice(0, childIndex) - .every((candidate) => getOptionComponent(candidate.type)?._GridReact.gridOption === 'pagination'); + .every((candidate) => { + const gridOption = getOptionComponent(candidate.type) + ?._GridReact.gridOption; + return gridOption === 'pagination'; + }); } function isOptionElement(child: ReactElement): boolean { @@ -313,9 +336,10 @@ function resolveOptionChild(child: ReactNode): ReactElement | null { return null; } - const rendered = (child.type as (props: Record) => ReactNode)( - getChildPropsFromElement(child) - ); + const renderChild = child.type as ( + props: Record + ) => ReactNode; + const rendered = renderChild(getChildPropsFromElement(child)); if (isReactElement(rendered) && getOptionComponent(rendered.type)) { return rendered; diff --git a/packages/grid-shared-react/src/utils/mappers/mapPrefixedProps.ts b/packages/grid-shared-react/src/utils/mappers/mapPrefixedProps.ts index 9fdd271..82418e3 100644 --- a/packages/grid-shared-react/src/utils/mappers/mapPrefixedProps.ts +++ b/packages/grid-shared-react/src/utils/mappers/mapPrefixedProps.ts @@ -27,11 +27,15 @@ export function mapPrefixedProps( ): Record { const result = { ...props }; const groups: Record> = {}; - const prefixes = Object.keys(prefixToGroup).sort((a, b) => b.length - a.length); + const prefixes = Object.keys(prefixToGroup) + .sort((a, b) => b.length - a.length); for (const flatKey of Object.keys(result)) { const prefix = prefixes.find( - (candidate) => flatKey.startsWith(candidate) && flatKey.length > candidate.length + (candidate) => ( + flatKey.startsWith(candidate) + && flatKey.length > candidate.length + ) ); if (!prefix) { From 108a85a5725f2b70d6110a167b80d4501496c6cd Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Thu, 9 Jul 2026 10:13:09 +0200 Subject: [PATCH 24/51] Linted useGrid hook. --- .../grid-shared-react/src/hooks/useGrid.ts | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/packages/grid-shared-react/src/hooks/useGrid.ts b/packages/grid-shared-react/src/hooks/useGrid.ts index c5b752f..f5e1756 100644 --- a/packages/grid-shared-react/src/hooks/useGrid.ts +++ b/packages/grid-shared-react/src/hooks/useGrid.ts @@ -24,7 +24,11 @@ export interface GridInstance { * directly depending on their types. */ export interface GridType { - grid(container: HTMLDivElement, options?: TOptions, async?: boolean): GridInstance | Promise>; + grid( + container: HTMLDivElement, + options?: TOptions, + async?: boolean + ): GridInstance | Promise>; } export interface UseGridOptions { @@ -62,7 +66,8 @@ export function useGrid({ return; } - // StrictMode cleanup runs before re-mount; allow init to complete if re-mounted. + // StrictMode cleanup runs before re-mount; + // allow init to complete if re-mounted. destroyOnInitRef.current = false; // Prevent double initialization @@ -73,21 +78,24 @@ export function useGrid({ const initGrid = async () => { try { - // Use pending options if available (from rapid updates during init) + // Use pending options if available + // (from rapid updates during init) const initOptions = pendingOptionsRef.current ?? options; pendingOptionsRef.current = void 0; const grid = await Grid.grid(container, initOptions, true); if (destroyOnInitRef.current) { - // Component unmounted while we were initializing - destroy immediately + // Component unmounted while initializing - + // destroy immediately grid.destroy(); return; } currGridRef.current = grid; - // Apply any pending options that came in while we were initializing + // Apply pending options that came in + // while we were initializing if (pendingOptionsRef.current !== void 0) { grid.update(pendingOptionsRef.current, true, true); pendingOptionsRef.current = void 0; @@ -122,7 +130,8 @@ export function useGrid({ } if (currGridRef.current) { - // Declarative React options replace the previous snapshot (oneToOne). + // Declarative React options replace the previous + // snapshot (oneToOne). currGridRef.current.update(options, true, true); } else { // Grid still initializing, queue the update From bfc063a898f993f97ba7638d6a56386023a8aaa3 Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Thu, 9 Jul 2026 10:33:29 +0200 Subject: [PATCH 25/51] Fixed packages. --- package.json | 71 ++++++++++++++++++++++++++------------------------ pnpm-lock.yaml | 10 +++++++ 2 files changed, 47 insertions(+), 34 deletions(-) diff --git a/package.json b/package.json index 5d1eb21..0aaabc9 100644 --- a/package.json +++ b/package.json @@ -1,36 +1,39 @@ { - "name": "highcharts-grid-react", - "description": "Monorepo for Highcharts Grid Pro & Lite React libraries.", - "private": true, - "type": "module", - "scripts": { - "test": "vitest run", - "test:watch": "vitest", - "pretest:e2e": "pnpm build", - "test:e2e": "vitest run --config vitest.e2e.config.ts", - "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", - "clean": "pnpm -r --filter './{packages,examples}/*' run clean && rimraf node_modules", - "release:preflight": "pnpm check && pnpm build", - "release:prepare": "node scripts/release.js", - "release": "pnpm release:preflight && pnpm publish -r --access public" - }, - "packageManager": "pnpm@10.27.0", - "devDependencies": { - "@eslint/js": "^9.39.1", - "@stylistic/eslint-plugin": "^5.6.1", - "@testing-library/react": "^16.3.1", - "@types/node": "^20.0.0", - "@vitest/browser": "^4.0.16", - "@vitest/browser-playwright": "^4.0.16", - "eslint": "^9.39.1", - "globals": "^17.0.0", - "playwright": "^1.57.0", - "rimraf": "^6.1.2", - "typescript": "^5.9.3", - "typescript-eslint": "^8.48.0", - "vitest": "^4.0.16" - } + "name": "highcharts-grid-react", + "description": "Monorepo for Highcharts Grid Pro & Lite React libraries.", + "private": true, + "type": "module", + "scripts": { + "test": "vitest run", + "pretest": "pnpm build", + "test:watch": "vitest", + "pretest:e2e": "pnpm build", + "test:e2e": "vitest run --config vitest.e2e.config.ts", + "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", + "clean": "pnpm -r --filter './{packages,examples}/*' run clean && rimraf node_modules", + "release:preflight": "pnpm check && pnpm build", + "release:prepare": "node scripts/release.js", + "release": "pnpm release:preflight && pnpm publish -r --access public", + "prepare": "husky" + }, + "packageManager": "pnpm@10.27.0", + "devDependencies": { + "@eslint/js": "^9.39.1", + "@stylistic/eslint-plugin": "^5.6.1", + "@testing-library/react": "^16.3.1", + "@types/node": "^20.0.0", + "@vitest/browser": "^4.0.16", + "@vitest/browser-playwright": "^4.0.16", + "eslint": "^9.39.1", + "globals": "^17.0.0", + "husky": "^9.1.7", + "playwright": "^1.57.0", + "rimraf": "^6.1.2", + "typescript": "^5.9.3", + "typescript-eslint": "^8.48.0", + "vitest": "^4.0.16" + } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ff81d18..648ccc3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,6 +32,9 @@ importers: globals: specifier: ^17.0.0 version: 17.0.0 + husky: + specifier: ^9.1.7 + version: 9.1.7 playwright: specifier: ^1.57.0 version: 1.57.0 @@ -1532,6 +1535,11 @@ packages: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} + husky@9.1.7: + resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==} + engines: {node: '>=18'} + hasBin: true + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -3282,6 +3290,8 @@ snapshots: - supports-color optional: true + husky@9.1.7: {} + ignore@5.3.2: {} ignore@7.0.5: {} From 9dd5e0e26932c813cd2dcbd325c7875c85d59ae0 Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Thu, 9 Jul 2026 10:37:26 +0200 Subject: [PATCH 26/51] Linted Grid. --- packages/grid-lite-react/src/Grid.tsx | 3 ++- packages/grid-pro-react/src/Grid.tsx | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/grid-lite-react/src/Grid.tsx b/packages/grid-lite-react/src/Grid.tsx index aa47803..44b5643 100644 --- a/packages/grid-lite-react/src/Grid.tsx +++ b/packages/grid-lite-react/src/Grid.tsx @@ -22,7 +22,8 @@ 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; + const columns = childOptions.columns as + Array<{ id?: string }> | undefined; return columns?.map((column) => column.id).join('\0') ?? ''; }, [childOptions]); diff --git a/packages/grid-pro-react/src/Grid.tsx b/packages/grid-pro-react/src/Grid.tsx index c236cfd..c9e596f 100644 --- a/packages/grid-pro-react/src/Grid.tsx +++ b/packages/grid-pro-react/src/Grid.tsx @@ -22,7 +22,8 @@ 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; + const columns = childOptions.columns as + Array<{ id?: string }> | undefined; return columns?.map((column) => column.id).join('\0') ?? ''; }, [childOptions]); From 2ade77e12270fb0df96370aac2ec5d219db196dc Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Thu, 9 Jul 2026 10:41:59 +0200 Subject: [PATCH 27/51] Cleaned up. --- .../src/__tests__/Grid.test.tsx | 23 ------------------- .../src/__tests__/Grid.test.tsx | 23 ------------------- 2 files changed, 46 deletions(-) delete mode 100644 packages/grid-lite-react/src/__tests__/Grid.test.tsx delete mode 100644 packages/grid-pro-react/src/__tests__/Grid.test.tsx diff --git a/packages/grid-lite-react/src/__tests__/Grid.test.tsx b/packages/grid-lite-react/src/__tests__/Grid.test.tsx deleted file mode 100644 index 770322a..0000000 --- a/packages/grid-lite-react/src/__tests__/Grid.test.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { createGridTests } from '@highcharts/grid-shared-react/src/test/createGridTests'; -import { Grid, GridOptions } from '../index'; - -createGridTests( - 'Grid Lite', - Grid, - { - dataTable: { - columns: { - name: ['Alice', 'Bob'], - age: [30, 25] - } - } - }, - { - dataTable: { - columns: { - name: ['Charlie', 'Diana'], - age: [40, 35] - } - } - } -); diff --git a/packages/grid-pro-react/src/__tests__/Grid.test.tsx b/packages/grid-pro-react/src/__tests__/Grid.test.tsx deleted file mode 100644 index e44d6d9..0000000 --- a/packages/grid-pro-react/src/__tests__/Grid.test.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { createGridTests } from '@highcharts/grid-shared-react/src/test/createGridTests'; -import { Grid, GridOptions } from '../index'; - -createGridTests( - 'Grid Pro', - Grid, - { - dataTable: { - columns: { - name: ['Alice', 'Bob'], - age: [30, 25] - } - } - }, - { - dataTable: { - columns: { - name: ['Charlie', 'Diana'], - age: [40, 35] - } - } - } -); From 8bcf49e4931bc8b0e1fbaab87da5f7c396a818ec Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Thu, 9 Jul 2026 12:13:11 +0200 Subject: [PATCH 28/51] Added Caption test. --- .../tests/options/Caption.test.tsx | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 packages/grid-shared-react/tests/options/Caption.test.tsx diff --git a/packages/grid-shared-react/tests/options/Caption.test.tsx b/packages/grid-shared-react/tests/options/Caption.test.tsx new file mode 100644 index 0000000..832944e --- /dev/null +++ b/packages/grid-shared-react/tests/options/Caption.test.tsx @@ -0,0 +1,21 @@ +import { describe, it, expect } from 'vitest'; +import { Caption } from '../../src/components/options/caption/Caption'; +import { getChildProps } from '../../src/utils/getChildProps'; + +describe('Caption', () => { + it('maps caption props and children into options.caption', () => { + expect( + getChildProps( +
+ ) + ).toEqual({ + caption: { + className: 'grid-caption', + htmlTag: 'h2', + text: 'Sales table' + } + }); + }); +}); From 41c7bf5629752f86a201695ed47c01193432f342 Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Thu, 9 Jul 2026 14:12:04 +0200 Subject: [PATCH 29/51] Added Description test. --- .../tests/options/Description.test.tsx | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 packages/grid-shared-react/tests/options/Description.test.tsx diff --git a/packages/grid-shared-react/tests/options/Description.test.tsx b/packages/grid-shared-react/tests/options/Description.test.tsx new file mode 100644 index 0000000..145a800 --- /dev/null +++ b/packages/grid-shared-react/tests/options/Description.test.tsx @@ -0,0 +1,20 @@ +import { describe, it, expect } from 'vitest'; +import { Description } from '../../src/components/options/description/Description'; +import { getChildProps } from '../../src/utils/getChildProps'; + +describe('Description', () => { + it('maps description props and children into options.description', () => { + expect( + getChildProps( + + Monthly sales overview + + ) + ).toEqual({ + description: { + className: 'grid-description', + text: 'Monthly sales overview' + } + }); + }); +}); From 0c973162f03b7b9d52d044c1149d6285f6c98ad1 Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Fri, 10 Jul 2026 09:18:34 +0200 Subject: [PATCH 30/51] Added tests for Coolumns and ColumnDefaults. --- .../tests/options/Column.test.tsx | 54 +++++++++++++++++++ .../tests/options/ColumnDefaults.test.tsx | 27 ++++++++++ 2 files changed, 81 insertions(+) create mode 100644 packages/grid-shared-react/tests/options/Column.test.tsx create mode 100644 packages/grid-shared-react/tests/options/ColumnDefaults.test.tsx diff --git a/packages/grid-shared-react/tests/options/Column.test.tsx b/packages/grid-shared-react/tests/options/Column.test.tsx new file mode 100644 index 0000000..f52c073 --- /dev/null +++ b/packages/grid-shared-react/tests/options/Column.test.tsx @@ -0,0 +1,54 @@ +import { describe, it, expect } from 'vitest'; +import { Column } from '../../src/components/options/columns/Column'; +import { getChildProps } from '../../src/utils/getChildProps'; + +describe('Column', () => { + it('maps column props into options.columns', () => { + expect( + getChildProps( + + ) + ).toEqual({ + columns: [{ + width: 120, + sorting: { + enabled: true, + order: 'asc' + }, + header: { + format: '{value} USD' + }, + id: 'price' + }], + data: { + autogenerateColumns: false + } + }); + }); + + it('maps multiple columns into options.columns array', () => { + expect( + getChildProps( + <> + + + + ) + ).toEqual({ + columns: [ + { width: 200, id: 'product' }, + { width: 120, id: 'price' } + ], + data: { + autogenerateColumns: false + } + }); + }); +}); diff --git a/packages/grid-shared-react/tests/options/ColumnDefaults.test.tsx b/packages/grid-shared-react/tests/options/ColumnDefaults.test.tsx new file mode 100644 index 0000000..1a43555 --- /dev/null +++ b/packages/grid-shared-react/tests/options/ColumnDefaults.test.tsx @@ -0,0 +1,27 @@ +import { describe, it, expect } from 'vitest'; +import { ColumnDefaults } from '../../src/components/options/columns/ColumnDefaults'; +import { getChildProps } from '../../src/utils/getChildProps'; + +describe('ColumnDefaults', () => { + it('maps column defaults props into options.columnDefaults', () => { + expect( + getChildProps( + + ) + ).toEqual({ + columnDefaults: { + width: 160, + sorting: { + enabled: true + }, + cells: { + format: '{value}' + } + } + }); + }); +}); From 9da7362f232ecb27931d5ff9c9e137e6fcdf306b Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Mon, 13 Jul 2026 10:29:33 +0200 Subject: [PATCH 31/51] 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 32/51] 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 33/51] 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 ( + <> + + + +
+ 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 34/51] 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 35/51] 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 36/51] 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 37/51] 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 38/51] 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 39/51] 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 ( <> - +
+
- Grid Description + Grid Description -
+
{/* */}
- +
); } From e4628a15b72494e51895623c50533f5b722be2ae Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Wed, 29 Jul 2026 09:51:41 +0200 Subject: [PATCH 42/51] Added tailwind classes to grid-lite demo. --- .../grid-lite/components-react/src/App.tsx | 257 ++++++++++-------- .../grid-lite/components-react/src/index.css | 91 ++++++- 2 files changed, 231 insertions(+), 117 deletions(-) diff --git a/examples/grid-lite/components-react/src/App.tsx b/examples/grid-lite/components-react/src/App.tsx index 3d21c75..7ec87da 100644 --- a/examples/grid-lite/components-react/src/App.tsx +++ b/examples/grid-lite/components-react/src/App.tsx @@ -34,11 +34,29 @@ function App() { // ==== DATA ==== // Data Columns - const [dataSource, setDataSource] = useState({ - name: ['COLUMNS', 'Bob', 'Charlie', 'David', 'Eve'], - age: [23, 34, 45, 56, 67], - city: ['New York', 'Oslo', 'Paris', 'Tokyo', 'London'], - salary: [50000, 60000, 70000, 80000, 90000] + const [dataSource] = useState({ + name: [ + 'Alice Nguyen', 'Bob Berg', 'Charlie Dupont', 'David Sato', 'Eve Shaw', + 'John Hale', 'Jane Ortiz', 'Jim Novak', 'Jill Meyer', 'Jack Quinn', + 'Nora Ellis', 'Omar Khan', 'Priya Shah', 'Quinn Blake', 'Ruth Adler', + 'Sam Okonkwo', 'Tina Rossi', 'Uma Patel', 'Victor Lang', 'Wendy Cho' + ], + age: [ + 23, 34, 45, 56, 67, 30, 25, 35, 40, 45, + 28, 31, 39, 42, 51, 27, 33, 36, 44, 48 + ], + city: [ + 'New York', 'Oslo', 'Paris', 'Tokyo', 'London', + 'New York', 'Oslo', 'Paris', 'Tokyo', 'London', + 'Berlin', 'Toronto', 'Mumbai', 'Sydney', 'Zurich', + 'Lagos', 'Rome', 'Lisbon', 'Seoul', 'Chicago' + ], + salary: [ + 50000, 60000, 70000, 80000, 90000, + 40000, 35000, 45000, 50000, 55000, + 62000, 71000, 48000, 53000, 88000, + 41000, 59000, 64000, 76000, 82000 + ] }); // Data Table @@ -52,15 +70,15 @@ function App() { // }); // ==== ACTIONS ==== - const onButtonClick = () => { - // console.info('(ref) grid:', grid.current?.grid); - 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 onButtonClick = () => { + // // console.info('(ref) grid:', grid.current?.grid); + // 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); @@ -74,104 +92,119 @@ function App() { // }; return ( -
- - - -
-
- - - - - - Grid Description - - -
- - {/* */} +
+
+ + + +
+
+ + + + + + + Filter, sort, and page through sample employee rows styled with + utility classes. + + + + {/*
+ + +
*/} ); diff --git a/examples/grid-lite/components-react/src/index.css b/examples/grid-lite/components-react/src/index.css index c050702..bcb41c2 100644 --- a/examples/grid-lite/components-react/src/index.css +++ b/examples/grid-lite/components-react/src/index.css @@ -1,8 +1,19 @@ @import "tailwindcss"; -/* Optional: load default Grid theme tokens + themed caption styles */ -/* @import "@highcharts/grid-lite-react/src/styles/grid-theme-default.css"; */ +/* Sample viewer theme toggle + system preference */ +@custom-variant dark { + &:where(.highcharts-dark, .highcharts-dark *) { + @slot; + } + @media (prefers-color-scheme: dark) { + &:where(:not(.highcharts-light):not(.highcharts-light *)) { + @slot; + } + } +} + +/* Load grid-theme-default.css only when using Core theme tokens (non-empty `theme`). */ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', @@ -11,7 +22,77 @@ body { -moz-osx-font-smoothing: grayscale; } -#root { +.hcg-container { width: 100%; - min-height: 100vh; -} \ No newline at end of file + height: 600px; +} + +/* + * Pagination buttons / select — Core internals, styled via cascade. + */ +.hcg-pagination-controls { + @apply border-l border-r border-slate-200 dark:border-slate-700; +} + +.hcg-pagination-controls .hcg-pagination-pages { + @apply gap-2; +} + +.hcg-pagination-controls .hcg-button { + @apply size-7 rounded border border-transparent text-sm leading-none + text-slate-700 dark:text-slate-200; +} + +.hcg-pagination-controls .hcg-button:hover:not(:disabled) { + @apply bg-slate-200 dark:bg-slate-700; +} + +.hcg-pagination-controls .hcg-button-selected, +.hcg-pagination-controls .hcg-button-selected:hover:not(:disabled) { + @apply bg-teal-700 text-white dark:bg-teal-600; +} + +.demo-pag-size select.hcg-input { + @apply rounded border border-slate-300 bg-white py-1 pl-2 pr-5 text-sm + text-slate-700 dark:border-slate-600 dark:bg-slate-900 dark:text-slate-200; +} + +/* + * Filter popup — Core internals, styled via cascade. + */ +.demo-grid .hcg-container .hcg-popup { + @apply rounded-lg border border-slate-200 bg-white text-sm text-slate-700 + shadow-lg dark:border-slate-700 dark:bg-slate-900 dark:text-slate-200 + dark:shadow-black/40; +} + +.demo-grid .hcg-container .hcg-menu-header { + @apply mb-2 px-1 text-xs font-semibold text-slate-500 dark:text-slate-400; +} + +.demo-grid .hcg-container .hcg-column-filter-wrapper { + @apply gap-2; +} + +.demo-grid .hcg-container .hcg-column-filter-wrapper .hcg-input { + @apply w-full rounded border border-slate-300 bg-white px-2 py-1.5 text-sm + text-slate-700 dark:border-slate-600 dark:bg-slate-900 dark:text-slate-200; +} + +.demo-grid .hcg-container .hcg-column-filter-wrapper select.hcg-input { + @apply pr-5; +} + +.demo-grid .hcg-container .hcg-clear-filter-button { + @apply text-xs font-medium text-slate-600 no-underline dark:text-slate-300; +} + +.demo-grid .hcg-container .hcg-clear-filter-button:hover:not(:disabled) { + @apply text-slate-900 underline dark:text-slate-50; +} + +@container hcg (max-width: 800px) { + .hcg-pagination-controls { + @apply justify-center border-r-0 border-l-0 mt-2 mb-2; + } +} From 60aeadae54af8e930aea9793dee2bfb47ff79146 Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Mon, 10 Aug 2026 10:50:45 +0200 Subject: [PATCH 43/51] Update grid-lite and grid-pro packages to 3.1.0. Pin examples and wrappers to the released CSS fixes, and use exact Vite aliases for workspace packages. Co-authored-by: Cursor --- .../grid-lite/components-react/package.json | 2 +- .../grid-lite/components-react/vite.config.ts | 6 +- .../grid-lite/minimal-nextjs/package.json | 3 +- examples/grid-lite/minimal-react/package.json | 3 +- .../grid-pro/components-react/package.json | 2 +- .../grid-pro/components-react/vite.config.ts | 5 +- examples/grid-pro/minimal-nextjs/package.json | 3 +- examples/grid-pro/minimal-react/package.json | 3 +- packages/grid-lite-react/package.json | 2 +- packages/grid-pro-react/package.json | 2 +- pnpm-lock.yaml | 315 +++++++++++------- 11 files changed, 200 insertions(+), 146 deletions(-) diff --git a/examples/grid-lite/components-react/package.json b/examples/grid-lite/components-react/package.json index b98f691..794c1e7 100644 --- a/examples/grid-lite/components-react/package.json +++ b/examples/grid-lite/components-react/package.json @@ -10,7 +10,7 @@ "clean": "rimraf dist node_modules" }, "dependencies": { - "@highcharts/grid-lite": ">=3.0.0", + "@highcharts/grid-lite": "3.1.0", "@highcharts/grid-lite-react": "workspace:*", "react": ">=18", "react-dom": ">=18" diff --git a/examples/grid-lite/components-react/vite.config.ts b/examples/grid-lite/components-react/vite.config.ts index 0ec7891..40dd699 100644 --- a/examples/grid-lite/components-react/vite.config.ts +++ b/examples/grid-lite/components-react/vite.config.ts @@ -11,11 +11,12 @@ export default defineConfig({ resolve: { alias: [ { - find: '@highcharts/grid-lite-react', + // Exact match — string aliases are prefix-based and break subpath ids. + find: /^@highcharts\/grid-lite-react$/, replacement: resolve(__dirname, '../../../packages/grid-lite-react/src/index.ts') }, { - find: '@highcharts/grid-shared-react', + find: /^@highcharts\/grid-shared-react$/, replacement: resolve(__dirname, '../../../packages/grid-shared-react/src/index.ts') }, { @@ -29,4 +30,3 @@ export default defineConfig({ port: 3000 } }); - diff --git a/examples/grid-lite/minimal-nextjs/package.json b/examples/grid-lite/minimal-nextjs/package.json index 0437b25..17c1988 100644 --- a/examples/grid-lite/minimal-nextjs/package.json +++ b/examples/grid-lite/minimal-nextjs/package.json @@ -10,7 +10,7 @@ "clean": "rimraf .next node_modules" }, "dependencies": { - "@highcharts/grid-lite": ">=3.0.0", + "@highcharts/grid-lite": "3.1.0", "@highcharts/grid-lite-react": "workspace:*", "next": "^14.0.0", "react": ">=18", @@ -23,4 +23,3 @@ "typescript": "^5.0.0" } } - diff --git a/examples/grid-lite/minimal-react/package.json b/examples/grid-lite/minimal-react/package.json index cd5b45a..2a56a5c 100644 --- a/examples/grid-lite/minimal-react/package.json +++ b/examples/grid-lite/minimal-react/package.json @@ -10,7 +10,7 @@ "clean": "rimraf dist node_modules" }, "dependencies": { - "@highcharts/grid-lite": ">=3.0.0", + "@highcharts/grid-lite": "3.1.0", "@highcharts/grid-lite-react": "workspace:*", "react": ">=18", "react-dom": ">=18" @@ -23,4 +23,3 @@ "vite": "^5.0.0" } } - diff --git a/examples/grid-pro/components-react/package.json b/examples/grid-pro/components-react/package.json index e70810f..4166d1a 100644 --- a/examples/grid-pro/components-react/package.json +++ b/examples/grid-pro/components-react/package.json @@ -10,7 +10,7 @@ "clean": "rimraf dist node_modules" }, "dependencies": { - "@highcharts/grid-pro": ">=3.0.0", + "@highcharts/grid-pro": "3.1.0", "@highcharts/grid-pro-react": "workspace:*", "react": ">=18", "react-dom": ">=18" diff --git a/examples/grid-pro/components-react/vite.config.ts b/examples/grid-pro/components-react/vite.config.ts index 524f9cd..a28be86 100644 --- a/examples/grid-pro/components-react/vite.config.ts +++ b/examples/grid-pro/components-react/vite.config.ts @@ -10,11 +10,12 @@ export default defineConfig({ resolve: { alias: [ { - find: '@highcharts/grid-pro-react', + // Exact match — string aliases are prefix-based and break subpath ids. + find: /^@highcharts\/grid-pro-react$/, replacement: resolve(__dirname, '../../../packages/grid-pro-react/src/index.ts') }, { - find: '@highcharts/grid-shared-react', + find: /^@highcharts\/grid-shared-react$/, replacement: resolve(__dirname, '../../../packages/grid-shared-react/src/index.ts') }, { diff --git a/examples/grid-pro/minimal-nextjs/package.json b/examples/grid-pro/minimal-nextjs/package.json index 9a126ce..da4ca30 100644 --- a/examples/grid-pro/minimal-nextjs/package.json +++ b/examples/grid-pro/minimal-nextjs/package.json @@ -10,7 +10,7 @@ "clean": "rimraf .next node_modules" }, "dependencies": { - "@highcharts/grid-pro": ">=3.0.0", + "@highcharts/grid-pro": "3.1.0", "@highcharts/grid-pro-react": "workspace:*", "next": "^14.0.0", "react": ">=18", @@ -23,4 +23,3 @@ "typescript": "^5.0.0" } } - diff --git a/examples/grid-pro/minimal-react/package.json b/examples/grid-pro/minimal-react/package.json index 915fd3f..edc3b3b 100644 --- a/examples/grid-pro/minimal-react/package.json +++ b/examples/grid-pro/minimal-react/package.json @@ -10,7 +10,7 @@ "clean": "rimraf dist node_modules" }, "dependencies": { - "@highcharts/grid-pro": ">=3.0.0", + "@highcharts/grid-pro": "3.1.0", "@highcharts/grid-pro-react": "workspace:*", "react": ">=18", "react-dom": ">=18" @@ -23,4 +23,3 @@ "vite": "^5.0.0" } } - diff --git a/packages/grid-lite-react/package.json b/packages/grid-lite-react/package.json index 186cb2c..a619e75 100644 --- a/packages/grid-lite-react/package.json +++ b/packages/grid-lite-react/package.json @@ -37,7 +37,7 @@ "prepublishOnly": "pnpm build" }, "dependencies": { - "@highcharts/grid-lite": ">=3.0.0" + "@highcharts/grid-lite": "3.1.0" }, "devDependencies": { "@highcharts/grid-shared-react": "workspace:*", diff --git a/packages/grid-pro-react/package.json b/packages/grid-pro-react/package.json index 7012236..ef043ee 100644 --- a/packages/grid-pro-react/package.json +++ b/packages/grid-pro-react/package.json @@ -38,7 +38,7 @@ "prepublishOnly": "pnpm build" }, "dependencies": { - "@highcharts/grid-pro": ">=3.0.0" + "@highcharts/grid-pro": "3.1.0" }, "devDependencies": { "@highcharts/grid-shared-react": "workspace:*", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f3bbcb8..7b1f7b0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -54,8 +54,8 @@ importers: examples/grid-lite/components-react: dependencies: '@highcharts/grid-lite': - specifier: '>=3.0.0' - version: 3.0.0 + specifier: 3.1.0 + version: 3.1.0 '@highcharts/grid-lite-react': specifier: workspace:* version: link:../../../packages/grid-lite-react @@ -91,8 +91,8 @@ importers: examples/grid-lite/minimal-nextjs: dependencies: '@highcharts/grid-lite': - specifier: '>=3.0.0' - version: 3.0.0 + specifier: 3.1.0 + version: 3.1.0 '@highcharts/grid-lite-react': specifier: workspace:* version: link:../../../packages/grid-lite-react @@ -122,8 +122,8 @@ importers: examples/grid-lite/minimal-react: dependencies: '@highcharts/grid-lite': - specifier: '>=3.0.0' - version: 3.0.0 + specifier: 3.1.0 + version: 3.1.0 '@highcharts/grid-lite-react': specifier: workspace:* version: link:../../../packages/grid-lite-react @@ -153,8 +153,8 @@ importers: examples/grid-pro/components-react: dependencies: '@highcharts/grid-pro': - specifier: '>=3.0.0' - version: 3.0.0 + specifier: 3.1.0 + version: 3.1.0 '@highcharts/grid-pro-react': specifier: workspace:* version: link:../../../packages/grid-pro-react @@ -184,8 +184,8 @@ importers: examples/grid-pro/minimal-nextjs: dependencies: '@highcharts/grid-pro': - specifier: '>=3.0.0' - version: 3.0.0 + specifier: 3.1.0 + version: 3.1.0 '@highcharts/grid-pro-react': specifier: workspace:* version: link:../../../packages/grid-pro-react @@ -215,8 +215,8 @@ importers: examples/grid-pro/minimal-react: dependencies: '@highcharts/grid-pro': - specifier: '>=3.0.0' - version: 3.0.0 + specifier: 3.1.0 + version: 3.1.0 '@highcharts/grid-pro-react': specifier: workspace:* version: link:../../../packages/grid-pro-react @@ -246,8 +246,8 @@ importers: packages/grid-lite-react: dependencies: '@highcharts/grid-lite': - specifier: '>=3.0.0' - version: 3.0.0 + specifier: 3.1.0 + version: 3.1.0 devDependencies: '@highcharts/grid-shared-react': specifier: workspace:* @@ -283,8 +283,8 @@ importers: packages/grid-pro-react: dependencies: '@highcharts/grid-pro': - specifier: '>=3.0.0' - version: 3.0.0 + specifier: 3.1.0 + version: 3.1.0 devDependencies: '@highcharts/grid-shared-react': specifier: workspace:* @@ -349,14 +349,14 @@ importers: packages: - '@acemir/cssom@0.9.30': - resolution: {integrity: sha512-9CnlMCI0LmCIq0olalQqdWrJHPzm0/tw3gzOA9zJSgvFX7Xau3D24mAGa4BtwxwY69nsuJW6kQqqCzf/mEcQgg==} + '@acemir/cssom@0.9.31': + resolution: {integrity: sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA==} - '@asamuzakjp/css-color@4.1.1': - resolution: {integrity: sha512-B0Hv6G3gWGMn0xKJ0txEi/jM5iFpT3MfDxmhZFb4W047GvytCf1DHQ1D69W3zHI4yWe2aTZAA0JnbMZ7Xc8DuQ==} + '@asamuzakjp/css-color@4.1.2': + resolution: {integrity: sha512-NfBUvBaYgKIuq6E/RBLY1m0IohzNHAYyaJGuTK79Z23uNwmz2jl1mPsC5ZxCCxylinKhT1Amn5oNTlx1wN8cQg==} - '@asamuzakjp/dom-selector@6.7.6': - resolution: {integrity: sha512-hBaJER6A9MpdG3WgdlOolHmbOYvSk46y7IQN/1+iqiCuUu6iWdQrs9DGKF8ocqsEqWujWf/V7b7vaDgiUmIvUg==} + '@asamuzakjp/dom-selector@6.8.1': + resolution: {integrity: sha512-MvRz1nCqW0fsy8Qz4dnLIvhOlMzqDVBabZx6lH+YywFDdjXhMY37SmpV1XFX3JzG5GWHn63j6HX6QPr3lZXHvQ==} '@asamuzakjp/nwsapi@2.3.9': resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==} @@ -365,6 +365,10 @@ packages: resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} + '@babel/compat-data@7.28.5': resolution: {integrity: sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==} engines: {node: '>=6.9.0'} @@ -407,6 +411,10 @@ packages: resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-option@7.27.1': resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} engines: {node: '>=6.9.0'} @@ -436,6 +444,10 @@ packages: resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} engines: {node: '>=6.9.0'} + '@babel/runtime@7.29.7': + resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} + engines: {node: '>=6.9.0'} + '@babel/template@7.27.2': resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} engines: {node: '>=6.9.0'} @@ -448,37 +460,41 @@ packages: resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} engines: {node: '>=6.9.0'} - '@csstools/color-helpers@5.1.0': - resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==} - engines: {node: '>=18'} + '@csstools/color-helpers@6.1.0': + resolution: {integrity: sha512-064IFJdjTfUqnjpCVpMOdbr8FLQBhinbZj6yRv2An2E41O/pLEXqfFRWqGq/SxlE5PEUYTlvWsG2r8MswAVvkg==} + engines: {node: '>=20.19.0'} - '@csstools/css-calc@2.1.4': - resolution: {integrity: sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==} - engines: {node: '>=18'} + '@csstools/css-calc@3.3.0': + resolution: {integrity: sha512-c5ihYsPkdG6JCkU2zTMm4+k6r7RXuGxtWYhu5DHMIiF1FHzrfmHL5so11AoFpUv/tu61xfcmT4AmKoFfMPoqdQ==} + engines: {node: '>=20.19.0'} peerDependencies: - '@csstools/css-parser-algorithms': ^3.0.5 - '@csstools/css-tokenizer': ^3.0.4 + '@csstools/css-parser-algorithms': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.0 - '@csstools/css-color-parser@3.1.0': - resolution: {integrity: sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==} - engines: {node: '>=18'} + '@csstools/css-color-parser@4.1.10': + resolution: {integrity: sha512-UZhQLIUyJaaMepqehrCODwCg2KW25vFvLWBmqYFaPclYvvxzj/sG8LBOhBFCp11i9uE7t1EyS+RAoV9tztPFyw==} + engines: {node: '>=20.19.0'} peerDependencies: - '@csstools/css-parser-algorithms': ^3.0.5 - '@csstools/css-tokenizer': ^3.0.4 + '@csstools/css-parser-algorithms': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.0 - '@csstools/css-parser-algorithms@3.0.5': - resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==} - engines: {node: '>=18'} + '@csstools/css-parser-algorithms@4.0.0': + resolution: {integrity: sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==} + engines: {node: '>=20.19.0'} peerDependencies: - '@csstools/css-tokenizer': ^3.0.4 + '@csstools/css-tokenizer': ^4.0.0 - '@csstools/css-syntax-patches-for-csstree@1.0.22': - resolution: {integrity: sha512-qBcx6zYlhleiFfdtzkRgwNC7VVoAwfK76Vmsw5t+PbvtdknO9StgRk7ROvq9so1iqbdW4uLIDAsXRsTfUrIoOw==} - engines: {node: '>=18'} + '@csstools/css-syntax-patches-for-csstree@1.1.7': + resolution: {integrity: sha512-fQ+05118eQS1cofO3aJpB5efgpBZMvIzwr/sbC8kDLVA5XLG8q1kJV5yzrUAI1f7lvhPnm8fgIjzFB8/O/5Dig==} + peerDependencies: + css-tree: ^3.2.1 + peerDependenciesMeta: + css-tree: + optional: true - '@csstools/css-tokenizer@3.0.4': - resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} - engines: {node: '>=18'} + '@csstools/css-tokenizer@4.0.0': + resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==} + engines: {node: '>=20.19.0'} '@esbuild/aix-ppc64@0.21.5': resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} @@ -812,20 +828,20 @@ packages: resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@exodus/bytes@1.8.0': - resolution: {integrity: sha512-8JPn18Bcp8Uo1T82gR8lh2guEOa5KKU/IEKvvdp0sgmi7coPBWf1Doi1EXsGZb2ehc8ym/StJCjffYV+ne7sXQ==} + '@exodus/bytes@1.15.1': + resolution: {integrity: sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} peerDependencies: - '@exodus/crypto': ^1.0.0-rc.4 + '@noble/hashes': ^1.8.0 || ^2.0.0 peerDependenciesMeta: - '@exodus/crypto': + '@noble/hashes': optional: true - '@highcharts/grid-lite@3.0.0': - resolution: {integrity: sha512-e42KWpkMCQUmjzZPmpRW9GyM60+MvsSVi/4PejchFJEDpwaPJ/06baAq5hZO9VF09+qG1RDpaCwRof2A9rfhfg==} + '@highcharts/grid-lite@3.1.0': + resolution: {integrity: sha512-WEYEjhlSK1kSuakRjqjiAclCydxxc1lrJsrkdt9xLojf3UKiSq3m35qTLLwrJDFlzr24I614qTS8q21eTnQj8Q==} - '@highcharts/grid-pro@3.0.0': - resolution: {integrity: sha512-zGE7EzfRxUx8w+oyKgUo6b8GrWme1i8AfKpuTjIrrxso2xLqYEpFozeJ9j5U1bwizKb39PMc7VyiK5407BZasQ==} + '@highcharts/grid-pro@3.1.0': + resolution: {integrity: sha512-QzAPfZUIjbrBBDPg17jmVauaLO5d4ogGW8LIfffKMX1Jtk1waKjBzHg0PdsvX/NZ/vNkt7L2RrMtl+kBaDopxg==} '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} @@ -1452,19 +1468,19 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} - css-tree@3.1.0: - resolution: {integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==} + css-tree@3.2.1: + resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} - cssstyle@5.3.6: - resolution: {integrity: sha512-legscpSpgSAeGEe0TNcai97DKt9Vd9AsAdOL7Uoetb52Ar/8eJm3LIa39qpv8wWzLFlNG4vVvppQM+teaMPj3A==} + cssstyle@5.3.7: + resolution: {integrity: sha512-7D2EPVltRrsTkhpQmksIu+LxeWAIEk6wRDMJ1qljlv+CKHJM+cJLlfhWIzNA44eAsHXSNe3+vO6DW1yCYx8SuQ==} engines: {node: '>=20'} csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} - data-urls@6.0.0: - resolution: {integrity: sha512-BnBS08aLUM+DKamupXs3w2tJJoqU+AkaE/+6vQxi/G/DPmIZFJJp9Dkb1kM03AZx8ADehDUZgsNxju3mPXZYIA==} + data-urls@6.0.1: + resolution: {integrity: sha512-euIQENZg6x8mj3fO6o9+fOW8MimUI4PpD/fZBhJfeioZVy9TUpM4UY7KjQNVZFlqwJ0UdzRDzkycB997HEq1BQ==} engines: {node: '>=20'} debug@4.4.3: @@ -1504,9 +1520,9 @@ packages: resolution: {integrity: sha512-aNnGCvbJ/RIyWo1IuhNdVjnNF+EjH9wpzpNHt+ci/m9He9LJvUN8wrCcXjp9cWsGNAuvSpVFTx/vraAFQ8qGjQ==} engines: {node: '>=10.13.0'} - entities@6.0.1: - resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} - engines: {node: '>=0.12'} + entities@8.0.0: + resolution: {integrity: sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==} + engines: {node: '>=20.19.0'} es-module-lexer@1.7.0: resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} @@ -1842,6 +1858,10 @@ packages: resolution: {integrity: sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==} engines: {node: 20 || >=22} + lru-cache@11.5.2: + resolution: {integrity: sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==} + engines: {node: 20 || >=22} + lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} @@ -1852,8 +1872,8 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - mdn-data@2.12.2: - resolution: {integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==} + mdn-data@2.27.1: + resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} minimatch@10.1.1: resolution: {integrity: sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==} @@ -1929,8 +1949,8 @@ packages: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} - parse5@8.0.0: - resolution: {integrity: sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==} + parse5@8.0.1: + resolution: {integrity: sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==} path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} @@ -2136,19 +2156,19 @@ packages: resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==} engines: {node: '>=14.0.0'} - tldts-core@7.0.19: - resolution: {integrity: sha512-lJX2dEWx0SGH4O6p+7FPwYmJ/bu1JbcGJ8RLaG9b7liIgZ85itUVEPbMtWRVrde/0fnDPEPHW10ZsKW3kVsE9A==} + tldts-core@7.4.10: + resolution: {integrity: sha512-KnQjp53ZekKgm/r3l+u8kJGGzYgrWdP8+Mql7a4vijh2WE0IrZWspQj/TpTxDho/YxO+AnOZnIjQcCD+q6iJsw==} - tldts@7.0.19: - resolution: {integrity: sha512-8PWx8tvC4jDB39BQw1m4x8y5MH1BcQ5xHeL2n7UVFulMPH/3Q0uiamahFJ3lXA0zO2SUyRXuVVbWSDmstlt9YA==} + tldts@7.4.10: + resolution: {integrity: sha512-GgouD1B+sWwvkaEq8vXC15DjQitxbvs12oIXELpconwm+Tg3zfcEv4jgzq3vtKverDXsg3VI8aRgNL2Nra0Iog==} hasBin: true totalist@3.0.1: resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} engines: {node: '>=6'} - tough-cookie@6.0.0: - resolution: {integrity: sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==} + tough-cookie@6.0.2: + resolution: {integrity: sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==} engines: {node: '>=16'} tr46@6.0.0: @@ -2309,6 +2329,10 @@ packages: resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} engines: {node: '>=18'} + whatwg-mimetype@5.0.0: + resolution: {integrity: sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==} + engines: {node: '>=20'} + whatwg-url@15.1.0: resolution: {integrity: sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==} engines: {node: '>=20'} @@ -2339,6 +2363,18 @@ packages: utf-8-validate: optional: true + ws@8.21.3: + resolution: {integrity: sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + xml-name-validator@5.0.0: resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} engines: {node: '>=18'} @@ -2355,25 +2391,25 @@ packages: snapshots: - '@acemir/cssom@0.9.30': + '@acemir/cssom@0.9.31': optional: true - '@asamuzakjp/css-color@4.1.1': + '@asamuzakjp/css-color@4.1.2': dependencies: - '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) - '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) - '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) - '@csstools/css-tokenizer': 3.0.4 - lru-cache: 11.2.4 + '@csstools/css-calc': 3.3.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-color-parser': 4.1.10(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + lru-cache: 11.5.2 optional: true - '@asamuzakjp/dom-selector@6.7.6': + '@asamuzakjp/dom-selector@6.8.1': dependencies: '@asamuzakjp/nwsapi': 2.3.9 bidi-js: 1.0.3 - css-tree: 3.1.0 + css-tree: 3.2.1 is-potential-custom-element-name: 1.0.1 - lru-cache: 11.2.4 + lru-cache: 11.5.2 optional: true '@asamuzakjp/nwsapi@2.3.9': @@ -2385,6 +2421,12 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.1.1 + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + '@babel/compat-data@7.28.5': {} '@babel/core@7.28.5': @@ -2447,6 +2489,8 @@ snapshots: '@babel/helper-validator-identifier@7.28.5': {} + '@babel/helper-validator-identifier@7.29.7': {} + '@babel/helper-validator-option@7.27.1': {} '@babel/helpers@7.28.4': @@ -2470,6 +2514,8 @@ snapshots: '@babel/runtime@7.28.4': {} + '@babel/runtime@7.29.7': {} + '@babel/template@7.27.2': dependencies: '@babel/code-frame': 7.27.1 @@ -2493,32 +2539,34 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 - '@csstools/color-helpers@5.1.0': + '@csstools/color-helpers@6.1.0': optional: true - '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + '@csstools/css-calc@3.3.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': dependencies: - '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) - '@csstools/css-tokenizer': 3.0.4 + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 optional: true - '@csstools/css-color-parser@3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + '@csstools/css-color-parser@4.1.10(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': dependencies: - '@csstools/color-helpers': 5.1.0 - '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) - '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) - '@csstools/css-tokenizer': 3.0.4 + '@csstools/color-helpers': 6.1.0 + '@csstools/css-calc': 3.3.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 optional: true - '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)': + '@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0)': dependencies: - '@csstools/css-tokenizer': 3.0.4 + '@csstools/css-tokenizer': 4.0.0 optional: true - '@csstools/css-syntax-patches-for-csstree@1.0.22': + '@csstools/css-syntax-patches-for-csstree@1.1.7(css-tree@3.2.1)': + optionalDependencies: + css-tree: 3.2.1 optional: true - '@csstools/css-tokenizer@3.0.4': + '@csstools/css-tokenizer@4.0.0': optional: true '@esbuild/aix-ppc64@0.21.5': @@ -2714,12 +2762,12 @@ snapshots: '@eslint/core': 0.17.0 levn: 0.4.1 - '@exodus/bytes@1.8.0': + '@exodus/bytes@1.15.1': optional: true - '@highcharts/grid-lite@3.0.0': {} + '@highcharts/grid-lite@3.1.0': {} - '@highcharts/grid-pro@3.0.0': {} + '@highcharts/grid-pro@3.1.0': {} '@humanfs/core@0.19.1': {} @@ -2984,8 +3032,8 @@ snapshots: '@testing-library/dom@10.4.1': dependencies: - '@babel/code-frame': 7.27.1 - '@babel/runtime': 7.28.4 + '@babel/code-frame': 7.29.7 + '@babel/runtime': 7.29.7 '@types/aria-query': 5.0.4 aria-query: 5.3.0 dom-accessibility-api: 0.5.16 @@ -3316,25 +3364,25 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 - css-tree@3.1.0: + css-tree@3.2.1: dependencies: - mdn-data: 2.12.2 + mdn-data: 2.27.1 source-map-js: 1.2.1 optional: true - cssstyle@5.3.6: + cssstyle@5.3.7: dependencies: - '@asamuzakjp/css-color': 4.1.1 - '@csstools/css-syntax-patches-for-csstree': 1.0.22 - css-tree: 3.1.0 - lru-cache: 11.2.4 + '@asamuzakjp/css-color': 4.1.2 + '@csstools/css-syntax-patches-for-csstree': 1.1.7(css-tree@3.2.1) + css-tree: 3.2.1 + lru-cache: 11.5.2 optional: true csstype@3.2.3: {} - data-urls@6.0.0: + data-urls@6.0.1: dependencies: - whatwg-mimetype: 4.0.0 + whatwg-mimetype: 5.0.0 whatwg-url: 15.1.0 optional: true @@ -3362,7 +3410,7 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.3.3 - entities@6.0.1: + entities@8.0.0: optional: true es-module-lexer@1.7.0: {} @@ -3562,9 +3610,9 @@ snapshots: html-encoding-sniffer@6.0.0: dependencies: - '@exodus/bytes': 1.8.0 + '@exodus/bytes': 1.15.1 transitivePeerDependencies: - - '@exodus/crypto' + - '@noble/hashes' optional: true http-proxy-agent@7.0.2: @@ -3627,28 +3675,28 @@ snapshots: jsdom@27.4.0: dependencies: - '@acemir/cssom': 0.9.30 - '@asamuzakjp/dom-selector': 6.7.6 - '@exodus/bytes': 1.8.0 - cssstyle: 5.3.6 - data-urls: 6.0.0 + '@acemir/cssom': 0.9.31 + '@asamuzakjp/dom-selector': 6.8.1 + '@exodus/bytes': 1.15.1 + cssstyle: 5.3.7 + data-urls: 6.0.1 decimal.js: 10.6.0 html-encoding-sniffer: 6.0.0 http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6 is-potential-custom-element-name: 1.0.1 - parse5: 8.0.0 + parse5: 8.0.1 saxes: 6.0.0 symbol-tree: 3.2.4 - tough-cookie: 6.0.0 + tough-cookie: 6.0.2 w3c-xmlserializer: 5.0.0 webidl-conversions: 8.0.1 whatwg-mimetype: 4.0.0 whatwg-url: 15.1.0 - ws: 8.18.3 + ws: 8.21.3 xml-name-validator: 5.0.0 transitivePeerDependencies: - - '@exodus/crypto' + - '@noble/hashes' - bufferutil - supports-color - utf-8-validate @@ -3730,6 +3778,9 @@ snapshots: lru-cache@11.2.4: {} + lru-cache@11.5.2: + optional: true + lru-cache@5.1.1: dependencies: yallist: 3.1.1 @@ -3740,7 +3791,7 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 - mdn-data@2.12.2: + mdn-data@2.27.1: optional: true minimatch@10.1.1: @@ -3817,9 +3868,9 @@ snapshots: dependencies: callsites: 3.1.0 - parse5@8.0.0: + parse5@8.0.1: dependencies: - entities: 6.0.1 + entities: 8.0.0 optional: true path-exists@4.0.0: {} @@ -4002,19 +4053,19 @@ snapshots: tinyrainbow@3.0.3: {} - tldts-core@7.0.19: + tldts-core@7.4.10: optional: true - tldts@7.0.19: + tldts@7.4.10: dependencies: - tldts-core: 7.0.19 + tldts-core: 7.4.10 optional: true totalist@3.0.1: {} - tough-cookie@6.0.0: + tough-cookie@6.0.2: dependencies: - tldts: 7.0.19 + tldts: 7.4.10 optional: true tr46@6.0.0: @@ -4131,6 +4182,9 @@ snapshots: whatwg-mimetype@4.0.0: optional: true + whatwg-mimetype@5.0.0: + optional: true + whatwg-url@15.1.0: dependencies: tr46: 6.0.0 @@ -4150,6 +4204,9 @@ snapshots: ws@8.18.3: {} + ws@8.21.3: + optional: true + xml-name-validator@5.0.0: optional: true From 35a7a1429d8040786e785fa19f40cf244b085426 Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Mon, 10 Aug 2026 10:58:40 +0200 Subject: [PATCH 44/51] Updated mappers and builders. --- .../src/utils/buildGridOptions.ts | 23 +++- .../src/utils/buildGridOptions.ts | 22 +++- .../src/utils/mappers/grid/gridOptions.ts | 1 + .../mappers/pagination/paginationOptions.ts | 60 +++++++++- .../src/utils/mergeClassNames.ts | 32 ++++++ .../src/utils/normalizeChildOptions.ts | 106 +++++++++++++++++- 6 files changed, 224 insertions(+), 20 deletions(-) create mode 100644 packages/grid-shared-react/src/utils/mergeClassNames.ts diff --git a/packages/grid-lite-react/src/utils/buildGridOptions.ts b/packages/grid-lite-react/src/utils/buildGridOptions.ts index 262df76..5bfd183 100644 --- a/packages/grid-lite-react/src/utils/buildGridOptions.ts +++ b/packages/grid-lite-react/src/utils/buildGridOptions.ts @@ -13,20 +13,33 @@ import type { Options } from '@highcharts/grid-lite/es-modules/Grid/Core/Options /** * Builds final Grid Lite options from raw declarative child options. + * + * `theme` → `rendering.theme` + * `tableClassName` → `rendering.table.className` (`.hcg-table`) + * + * `className` is React-only on the mount container + * (parent of `.hcg-container`). */ export function buildGridOptions( childOptions: Record, options?: Options, theme?: string, - className?: string + tableClassName?: string ): Options { - const containerTheme = [theme, className] - .filter(Boolean) - .join(' ') || void 0; + const rendering: Record = {}; + + if (theme !== void 0) { + rendering.theme = theme; + } + if (tableClassName !== void 0) { + rendering.table = { className: tableClassName }; + } return merge( normalizeChildOptions(childOptions), options ?? {}, - containerTheme ? { rendering: { theme: containerTheme } } : {} + // Skip empty `{ rendering: {} }` so merge does not inject a blank + // rendering block when theme / tableClassName were omitted. + Object.keys(rendering).length ? { rendering } : {} ) as Options; } diff --git a/packages/grid-pro-react/src/utils/buildGridOptions.ts b/packages/grid-pro-react/src/utils/buildGridOptions.ts index 9276355..107b096 100644 --- a/packages/grid-pro-react/src/utils/buildGridOptions.ts +++ b/packages/grid-pro-react/src/utils/buildGridOptions.ts @@ -19,6 +19,12 @@ import { /** * Builds final Grid Pro options from raw declarative child options. + * + * `theme` → `rendering.theme` + * `tableClassName` → `rendering.table.className` (`.hcg-table`) + * + * `className` is React-only on the mount container + * (parent of `.hcg-container`). */ export function buildGridOptions( gridKey: string, @@ -29,14 +35,22 @@ export function buildGridOptions( const declarativeOptions = mergePaginationEventProps( mergeColumnEventProps(normalizeChildOptions(childOptions)) ); - const containerTheme = [props.theme, props.className] - .filter(Boolean) - .join(' ') || void 0; + const rendering: Record = {}; + + if (props.theme !== void 0) { + rendering.theme = props.theme; + } + if (props.tableClassName !== void 0) { + rendering.table = { className: props.tableClassName }; + } + const result = merge( true, {}, merge(declarativeOptions, options ?? {}), - containerTheme ? { rendering: { theme: containerTheme } } : {}, + // Skip empty `{ rendering: {} }` so merge does not inject a blank + // rendering block when theme / tableClassName were omitted. + Object.keys(rendering).length ? { rendering } : {}, normalizeGridEventProps(props) ) as GridProOptions; diff --git a/packages/grid-pro-react/src/utils/mappers/grid/gridOptions.ts b/packages/grid-pro-react/src/utils/mappers/grid/gridOptions.ts index 39b406a..49e42ce 100644 --- a/packages/grid-pro-react/src/utils/mappers/grid/gridOptions.ts +++ b/packages/grid-pro-react/src/utils/mappers/grid/gridOptions.ts @@ -107,6 +107,7 @@ export function getGridEventPropDeps(props: GridProProps): unknown[] { props.gridKey, props.theme, props.className, + props.tableClassName, ...GRID_EVENT_PROP_KEYS.map((key) => props[key]) ]; } 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 03c9f03..6730758 100644 --- a/packages/grid-shared-react/src/utils/mappers/pagination/paginationOptions.ts +++ b/packages/grid-shared-react/src/utils/mappers/pagination/paginationOptions.ts @@ -7,6 +7,29 @@ * */ +function withClassName( + value: unknown, + className: string | undefined +): unknown { + if (className === void 0) { + return value; + } + + // Disabled control is not rendered — keep boolean, drop className. + if (value === false) { + return false; + } + + if (value !== null && typeof value === 'object') { + return { ...value as Record, className }; + } + + return { + enabled: value === void 0 ? true : Boolean(value), + className + }; +} + export function normalizePaginationOptions( props: Record ): Record { @@ -22,6 +45,10 @@ export function normalizePaginationOptions( page, pageSize, align, + className, + infoClassName, + controlsClassName, + sizeClassName, ...rest } = props; @@ -38,22 +65,41 @@ export function normalizePaginationOptions( if (align !== void 0) { result.align = align; } + if (typeof className === 'string') { + result.className = className; + } const controls: Record = {}; - if (pageInfo !== void 0) { - controls.pageInfo = pageInfo; + if (typeof controlsClassName === 'string') { + controls.className = controlsClassName; + } + + const pageInfoValue = withClassName(pageInfo, asString(infoClassName)); + if (pageInfoValue !== void 0) { + controls.pageInfo = pageInfoValue; } + let pageSizeSelectorValue: unknown = pageSizeSelector; + if (pageSizeSelector === false) { - controls.pageSizeSelector = false; + pageSizeSelectorValue = false; } else if (pageSizeOptions !== void 0) { - controls.pageSizeSelector = { + pageSizeSelectorValue = { enabled: true, options: pageSizeOptions }; } else if (pageSizeSelector !== void 0) { - controls.pageSizeSelector = pageSizeSelector; + pageSizeSelectorValue = pageSizeSelector; + } + + pageSizeSelectorValue = withClassName( + pageSizeSelectorValue, + asString(sizeClassName) + ); + + if (pageSizeSelectorValue !== void 0) { + controls.pageSizeSelector = pageSizeSelectorValue; } if (pageButtons === false) { @@ -81,3 +127,7 @@ export function normalizePaginationOptions( return { ...result, ...rest }; } + +function asString(value: unknown): string | undefined { + return typeof value === 'string' ? value : void 0; +} diff --git a/packages/grid-shared-react/src/utils/mergeClassNames.ts b/packages/grid-shared-react/src/utils/mergeClassNames.ts new file mode 100644 index 0000000..d4f6737 --- /dev/null +++ b/packages/grid-shared-react/src/utils/mergeClassNames.ts @@ -0,0 +1,32 @@ +/** + * Grid React integration. + * Copyright (c) 2025, Highsoft + * + * A valid license is required for using this software. + * See highcharts.com/license + * + */ + +/** + * Joins CSS class tokens from defaults and overrides without duplicates. + * Used so ColumnDefaults `*ClassName` still applies when a Column sets its + * own `*ClassName`. + */ +export function mergeClassNames( + ...classNames: Array<(string | undefined | null)> +): string | undefined { + const tokens: string[] = []; + + for (const value of classNames) { + if (typeof value !== 'string' || !value.trim()) { + continue; + } + for (const token of value.trim().split(/\s+/)) { + if (token && !tokens.includes(token)) { + tokens.push(token); + } + } + } + + return tokens.length ? tokens.join(' ') : void 0; +} diff --git a/packages/grid-shared-react/src/utils/normalizeChildOptions.ts b/packages/grid-shared-react/src/utils/normalizeChildOptions.ts index ef45c01..5eb09b7 100644 --- a/packages/grid-shared-react/src/utils/normalizeChildOptions.ts +++ b/packages/grid-shared-react/src/utils/normalizeChildOptions.ts @@ -8,12 +8,20 @@ */ import { isObject } from './isObject'; +import { mergeClassNames } from './mergeClassNames'; 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`. + * + * Also merges `columnDefaults` `className` / `header.className` / + * `cells.className` into each column so Column overrides do not wipe + * defaults (same semantics as Core `createOptionsProxy`). + * + * Lifts `rowClassName` / `evenRowClassName` from ColumnDefaults onto + * `rendering.rows` (not column options). */ export function normalizeChildOptions( raw: Record @@ -21,15 +29,49 @@ export function normalizeChildOptions( const result = { ...raw }; if (isObject(result.columnDefaults)) { - result.columnDefaults = normalizeColumnOptions({ - ...result.columnDefaults - }); + const defaults = { ...result.columnDefaults }; + const rowClassName = defaults.rowClassName; + const evenRowClassName = defaults.evenRowClassName; + delete defaults.rowClassName; + delete defaults.evenRowClassName; + + result.columnDefaults = normalizeColumnOptions(defaults); + + const rows: Record = {}; + if (typeof rowClassName === 'string') { + rows.className = rowClassName; + } + if (typeof evenRowClassName === 'string') { + rows.evenClassName = evenRowClassName; + } + + if (Object.keys(rows).length) { + const rendering = isObject(result.rendering) ? + { ...result.rendering } : + {}; + const existingRows = isObject(rendering.rows) ? + { ...rendering.rows as Record } : + {}; + rendering.rows = { ...existingRows, ...rows }; + result.rendering = rendering; + } } if (Array.isArray(result.columns)) { - result.columns = result.columns.map((column) => ( - isObject(column) ? normalizeColumnOptions({ ...column }) : column - )); + const defaults = isObject(result.columnDefaults) ? + result.columnDefaults as Record : + void 0; + + result.columns = result.columns.map((column) => { + if (!isObject(column)) { + return column; + } + + const normalized = normalizeColumnOptions({ ...column }); + return defaults ? + mergeColumnClassNames(normalized, defaults) : + normalized; + }); } if (isObject(result.pagination)) { @@ -46,3 +88,55 @@ export function normalizeChildOptions( return result; } + +function mergeColumnClassNames( + column: Record, + defaults: Record +): Record { + const result = { ...column }; + const className = mergeClassNames( + asString(defaults.className), + asString(column.className) + ); + + if (className !== void 0) { + result.className = className; + } + + result.header = mergeNestedClassName( + column.header, + defaults.header + ); + result.cells = mergeNestedClassName( + column.cells, + defaults.cells + ); + + return result; +} + +function mergeNestedClassName( + target: unknown, + defaults: unknown +): Record | unknown { + const targetObj = isObject(target) ? { ...target } : {}; + const defaultsObj = isObject(defaults) ? defaults : {}; + const className = mergeClassNames( + asString(defaultsObj.className), + asString(targetObj.className) + ); + + if (className === void 0 && !isObject(target)) { + return target; + } + + if (className !== void 0) { + targetObj.className = className; + } + + return targetObj; +} + +function asString(value: unknown): string | undefined { + return typeof value === 'string' ? value : void 0; +} From ae6dea058ddd041feced0fea93693800770cf0ff Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Mon, 10 Aug 2026 11:01:26 +0200 Subject: [PATCH 45/51] Updated CSS and package.json. --- examples/grid-lite/components-react/src/index.css | 1 - packages/grid-lite-react/package.json | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/grid-lite/components-react/src/index.css b/examples/grid-lite/components-react/src/index.css index bcb41c2..93cfec9 100644 --- a/examples/grid-lite/components-react/src/index.css +++ b/examples/grid-lite/components-react/src/index.css @@ -13,7 +13,6 @@ } } -/* Load grid-theme-default.css only when using Core theme tokens (non-empty `theme`). */ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', diff --git a/packages/grid-lite-react/package.json b/packages/grid-lite-react/package.json index a619e75..a24a3d1 100644 --- a/packages/grid-lite-react/package.json +++ b/packages/grid-lite-react/package.json @@ -30,7 +30,7 @@ "url": "https://github.com/highcharts/grid-react/issues" }, "scripts": { - "build": "pnpm clean:dist && rollup -c && cp -R src/styles dist/styles", + "build": "pnpm clean:dist && rollup -c", "lint": "eslint src --ext .ts,.tsx", "clean:dist": "rimraf dist", "clean": "rimraf node_modules dist", From f5f5ce7bebcbc3e3714b495645df6be527f9db81 Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Mon, 10 Aug 2026 12:50:36 +0200 Subject: [PATCH 46/51] Fixed styles, mappers. --- .../grid-lite-react/src/styles/grid-core.css | 680 ------------------ .../src/styles/grid-theme-default.css | 63 -- .../src/utils/mappers/grid/gridOptions.ts | 2 +- .../tests/mappers/gridOptions.test.tsx | 73 ++ 4 files changed, 74 insertions(+), 744 deletions(-) delete mode 100644 packages/grid-lite-react/src/styles/grid-core.css delete mode 100644 packages/grid-lite-react/src/styles/grid-theme-default.css diff --git a/packages/grid-lite-react/src/styles/grid-core.css b/packages/grid-lite-react/src/styles/grid-core.css deleted file mode 100644 index ccd0493..0000000 --- a/packages/grid-lite-react/src/styles/grid-core.css +++ /dev/null @@ -1,680 +0,0 @@ -@import '@highcharts/grid-lite/css/modules/grid-base-variables.css'; -@import '@highcharts/grid-lite/css/modules/grid-popup-variables.css'; -@import '@highcharts/grid-lite/css/modules/grid-menu-variables.css'; -@import '@highcharts/grid-lite/css/modules/grid-link-variables.css'; -@import '@highcharts/grid-lite/css/modules/grid-input-variables.css'; -@import '@highcharts/grid-lite/css/modules/grid-button-variables.css'; -@import '@highcharts/grid-lite/css/modules/grid-icon-variables.css'; -@import '@highcharts/grid-lite/css/modules/grid-pagination-variables.css'; -@import '@highcharts/grid-lite/css/modules/grid-table-variables.css'; -/* Grid container */ -.hcg-container { - container-type: inline-size; - container-name: hcg; - position: relative; - display: flex; - flex-direction: column; - height: 100%; - overflow: hidden; - box-sizing: border-box; - color-scheme: light dark; - max-height: inherit; -} - -.highcharts-light .hcg-container { - color-scheme: light; -} - -.highcharts-dark .hcg-container { - color-scheme: dark; -} - -.hcg-container * { - box-sizing: border-box; -} - -.hcg-container:has(.hcg-no-data) { - justify-content: center; - align-items: center; -} - -/* ---------------------------------------------------------- - INPUT ELEMENTS ------------------------------------------------------------- */ - -.hcg-container .hcg-input { - width: 100%; - - &:disabled { - opacity: 0.4; - cursor: not-allowed; - } - - &::placeholder { - color: #767676; - } - - &:focus-visible { - outline: none; - border-color: transparent; - } - - &[type="checkbox"] { - --ig-input-checkbox-size: 1.333em; - - appearance: none; - width: var(--ig-input-checkbox-size); - height: var(--ig-input-checkbox-size); - cursor: pointer; - position: relative; - - &:checked::before { - content: ""; - position: absolute; - inset: 0; - margin: 0.05em; - mask: center/contain no-repeat; - mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 13l4 4L19 7'/%3E%3C/svg%3E"); - } - } - - &.hcg-icon-search { - padding-left: 25px; - appearance: none; - background-repeat: no-repeat; - background-position: left 10px center; - background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12' fill='none'%3e%3cpath d='M10.5 10.5L7.50005 7.5M8.5 5C8.5 6.933 6.933 8.5 5 8.5C3.067 8.5 1.5 6.933 1.5 5C1.5 3.067 3.067 1.5 5 1.5C6.933 1.5 8.5 3.067 8.5 5Z' stroke='%23767676' stroke-linecap='round' stroke-linejoin='round'/%3e%3c/svg%3e"); - } - - select& { - appearance: none; - background-image: url("data:image/svg+xml,%3csvg width='12' height='12' viewBox='0 0 12 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M3.5 7.5L6 10L8.5 7.5M3.5 4.5L6 2L8.5 4.5' stroke='%23767676' stroke-linecap='round' stroke-linejoin='round'/%3e%3c/svg%3e"); - background-repeat: no-repeat; - background-position: right 5px center; - white-space: nowrap; - text-overflow: ellipsis; - } -} - -/* ---------------------------------------------------------- - BUTTON ELEMENT ------------------------------------------------------------- */ - -.hcg-container :is(.hcg-button, .hcg-icon) { - position: relative; - display: inline-flex; - vertical-align: middle; - align-items: center; - justify-content: center; - flex-direction: row; - line-height: 1; - gap: 2px; - cursor: pointer; - transition: background-color 0.2s ease, box-shadow 0.2s ease, border 0.2s ease; - - svg { - width: 0.9em; - height: 0.9em; - display: block; - } - - span { - display: inline-block; - line-height: 1; - } - - span:empty { - display: none; - } - - &.reverse { - flex-direction: row-reverse; - } - - &:focus-visible { - outline: none; - border-color: transparent; - } - - &:disabled { - opacity: 0.4; - cursor: not-allowed; - } -} - -/* ---------------------------------------------------------- - TABLE ELEMENTS ------------------------------------------------------------- */ - -/*
+ Sales table + Grid Pro ComponentsGrid Caption v2.1Grid Caption v2.1 Grid Caption v2.1Team directory
*/ -.hcg-container .hcg-table { - width: 100%; - border-collapse: separate; - border-spacing: 0; - overflow: hidden; - table-layout: fixed; - flex: 1; - - &.hcg-scrollable-content { - display: flex; - flex-direction: column; - min-height: 0; - } - - /* */ - &.hcg-virtualization thead { - display: block; - } - - thead th { - position: relative; - } - - /* */ - &.hcg-scrollable-content > tbody { - height: 100%; - overflow: auto; - min-height: 0; - flex: 1; - } - - &.hcg-virtualization > tbody { - display: block; - position: relative; - } - - > tbody > tr { - overflow: hidden; - width: 100%; - } - - > tbody > tr > :where(.hcg-cell) { - position: relative; - line-height: 1em; - overflow: hidden; - } - - > tbody > tr.hcg-mocked-row > :where(.hcg-cell) { - white-space: nowrap; - text-overflow: ellipsis; - } - - .hcg-last-header-cell-in-row, - tbody tr > :where(.hcg-cell):last-child { - border-right: none; - } - - tbody tr:last-of-type > :where(.hcg-cell) { - border-bottom: none; - } - - &.hcg-scrollable-content > tbody > tr { - display: block; - } - - &.hcg-virtualization > tbody > tr { - position: absolute; - } - - > tbody.hcg-rows-content-nowrap > tr > :where(.hcg-cell) { - white-space: nowrap; - text-overflow: ellipsis; - } - - > tbody > tr > :where(.hcg-cell):focus { - outline: none; - } -} - -/* ---------------------------------------------------------- - HEADER ELEMENTS ------------------------------------------------------------- */ -.hcg-container thead th { - .hcg-header-cell-container { - display: flex; - align-items: center; - justify-content: space-between; - } - - .hcg-header-cell-content { - flex: 1; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - - .hcg-header-cell-container.hcg-no-width .hcg-header-cell-content { - visibility: hidden; - transition: none; - } - - .hcg-header-cell-icons { - display: flex; - overflow: hidden; - align-items: center; - max-width: 0; - opacity: 0; - cursor: pointer; - transition: max-width 0.3s ease, opacity 0.3s ease; - } - - .hcg-header-cell-icons .hcg-icon.hcg-icon-selected::after { - content: ""; - position: absolute; - top: 2px; - right: 2px; - width: 0.3em; - height: 0.3em; - border-radius: 50%; - background: currentColor; - } - - :is(:hover, :focus-visible) .hcg-header-cell-icons, - .hcg-header-cell-icons:has(.hcg-button:focus-visible, .hcg-icon:focus-visible, .hcg-button.hcg-button-selected, .hcg-icon.hcg-icon-highlighted, .hcg-icon.hcg-icon-selected), - .hcg-header-cell-container.hcg-no-width .hcg-header-cell-icons { - max-width: 100px; - opacity: 1; - } - - .hcg-header-cell-container.hcg-no-width .hcg-header-cell-menu-icon { - position: absolute; - left: 50%; - top: 50%; - transform: translate(-50%, -50%); - margin-left: 0; - } - - .hcg-header-cell-icons .hcg-header-cell-menu-icon .hcg-icon { - padding-inline: 3px; - } - - .hcg-header-cell-icons > :first-child { - margin-left: 5px; - } - - .hcg-column-resizer { - position: absolute; - display: flex; - align-items: center; - justify-content: center; - top: 0; - width: 9px; - right: -5px; - height: 100%; - user-select: none; - touch-action: none; - z-index: 10; - cursor: col-resize; - } - - .hcg-column-resizer.hovered::after { - content: ""; - height: 100%; - } -} - -/* ---------------------------------------------------------- - PAGINATION ELEMENTS ------------------------------------------------------------- */ - -.hcg-container .hcg-pagination { - display: flex; - align-items: center; - gap: 0.75rem; - flex-wrap: nowrap; - - > * { - flex: 1 1 0; - min-width: 0; - display: flex; - align-items: center; - } - - .hcg-pagination-info { - justify-content: flex-start; - } - - .hcg-pagination-controls { - justify-content: center; - gap: 2px; - - .hcg-pagination-pages { - display: flex; - flex-wrap: nowrap; - gap: 2px; - - .hcg-button { - display: inline-flex; - align-items: center; - justify-content: center; - min-width: 30px; - } - - span { - display: inline-flex; - align-items: center; - justify-content: center; - min-width: 20px; - } - } - } - - .hcg-pagination-page-size { - justify-content: flex-end; - text-align: right; - - select.hcg-input { - width: 60px; - margin-left: 8px; - } - } - - /* .hcg-pagination-nav-dropdown { - display: none; - min-width: 200px; - } */ - - &.hcg-pagination-left, - &.hcg-pagination-center, - &.hcg-pagination-right { - > * { - flex: 0 0 auto; - min-width: auto; - } - } - - &.hcg-pagination-left { justify-content: flex-start; } - &.hcg-pagination-center { justify-content: center; } - &.hcg-pagination-right { justify-content: flex-end; } - - &:not(:has(.hcg-pagination-info)) .hcg-pagination-controls { - justify-content: flex-start; - } - - &:not(:has(.hcg-pagination-page-size)) .hcg-pagination-controls { - justify-content: flex-end; - } -} - -@container hcg (max-width: 800px) { - .hcg-container .hcg-pagination { - flex-direction: column; - align-items: stretch; - --ig-pagination-stacked-align: center; - - &.hcg-pagination-left { --ig-pagination-stacked-align: flex-start; } - &.hcg-pagination-right { --ig-pagination-stacked-align: flex-end; } - - > * { - flex: 0 0 auto; - justify-content: var(--ig-pagination-stacked-align); - } - - .hcg-pagination-info, - .hcg-pagination-controls, - .hcg-pagination-page-size { - justify-content: var(--ig-pagination-stacked-align); - } - - .hcg-pagination-page-size { - text-align: center; - } - &.hcg-pagination-left .hcg-pagination-page-size { text-align: left; } - &.hcg-pagination-right .hcg-pagination-page-size { text-align: right; } - - &:not(:has(.hcg-pagination-page-size)) .hcg-pagination-controls, - &:not(:has(.hcg-pagination-info)) .hcg-pagination-controls { - justify-content: var(--ig-pagination-stacked-align); - } - } -} - -/* ---------------------------------------------------------- - CREDITS ELEMENT ------------------------------------------------------------- */ - -.hcg-credits, -.highcharts-light .hcg-credits { - display: block; - width: 114px; - height: 20px; - background-size: contain; - background-repeat: no-repeat; - background-image: - image-set( - /* stylelint-disable-next-line function-comma-newline-after */ - url("https://assets.highcharts.com/grid/logo_light.png") 1x, - url("https://assets.highcharts.com/grid/logo_lightx2.png") 2x - ); -} - -@media (prefers-color-scheme: dark) { - .hcg-credits { - background-image: - image-set( - /* stylelint-disable-next-line function-comma-newline-after */ - url("https://assets.highcharts.com/grid/logo_dark.png") 1x, - url("https://assets.highcharts.com/grid/logo_darkx2.png") 2x - ); - } -} - -.highcharts-dark .hcg-credits { - background-image: - image-set( - /* stylelint-disable-next-line function-comma-newline-after */ - url("https://assets.highcharts.com/grid/logo_dark.png") 1x, - url("https://assets.highcharts.com/grid/logo_darkx2.png") 2x - ); -} - -/* ---------------------------------------------------------- - POPUP ELEMENTS ------------------------------------------------------------- */ -.hcg-container .hcg-popup { - position: absolute; - z-index: 1000; - border-radius: 6px; - box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.08), 0 7px 7px 0 rgba(0, 0, 0, 0.07), 0 17px 10px 0 rgba(0, 0, 0, 0.04), 0 30px 12px 0 rgba(0, 0, 0, 0.01); - min-width: 200px; - overflow: auto; - border-width: 1px; - border-style: solid; - - .hcg-popup-content { - padding: 5px; - } - - .hcg-menu-header { - font-size: 0.75rem; - padding: 3px; - margin-bottom: 5px; - } - - .hcg-menu-header-category { - opacity: 0.5; - user-select: none; - } -} - -/* ---------------------------------------------------------- - MENU ELEMENTS ------------------------------------------------------------- */ - -.hcg-container .hcg-menu-container { - margin: 0; - display: flex; - flex-direction: column; - list-style: none; - row-gap: 5px; - padding: 0; - - .hcg-menu-item { - display: flex; - align-items: center; - gap: 2px; - width: 100%; - min-width: 185px; - min-height: 2rem; - padding: 8px 8px 8px 12px; - font-size: 0.75rem; - font-weight: 600; - background-color: transparent; - border: 1px solid transparent; - border-radius: 5px; - } - - .hcg-menu-item:not(:disabled) { - cursor: pointer; - } - - .hcg-menu-item:focus-visible { - outline: none; - } - - .hcg-menu-item-icon { - --icon-size: 16px; - - flex: 0 0 var(--icon-size); - width: var(--icon-size); - height: var(--icon-size); - display: inline-flex; - align-items: center; - justify-content: center; - opacity: 0.6; - } - - .hcg-menu-item.active .hcg-menu-item-icon, - .hcg-menu-item.highlighted .hcg-menu-item-icon, - .hcg-menu-item:not(:disabled):hover .hcg-menu-item-icon { - opacity: 1; - } - - .hcg-menu-item-label { - flex: 1 1 auto; - min-width: 0; - text-align: left; - padding-left: 0.75rem; - } - - .hcg-menu-divider { - border-top-width: 1px; - border-top-style: solid; - height: 0; - } -} - -/* ---------------------------------------------------------- - FILTERING ELEMENTS ------------------------------------------------------------- */ - -.hcg-header-cell:has(.hcg-column-filter-wrapper) { - overflow: hidden; -} - -.hcg-column-filter-wrapper { - width: 100%; - display: flex; - flex-flow: column; - row-gap: 5px; - min-width: 50px; -} - -.hcg-clear-filter-button { - appearance: none; - background: none; - border: 0; - padding: 0; - margin: 0; - display: inline; - vertical-align: baseline; - font: inherit; - font-size: 0.625rem; - white-space: nowrap; - font-weight: normal; - align-self: end; -} - -.hcg-clear-filter-button:hover { - text-decoration: underline; - cursor: pointer; -} - -.hcg-clear-filter-button:disabled, -.hcg-clear-filter-button:disabled:hover { - opacity: 0.5; - text-decoration: none; - cursor: default; -} - -/* ---------------------------------------------------------- - OTHER ELEMENTS ------------------------------------------------------------- */ - -/* Sorting */ -.hcg-table thead th.hcg-column-sortable { - cursor: pointer; -} - -/* Accessibility */ -.hcg-visually-hidden { - position: absolute; - width: 1px; - height: 1px; - overflow: hidden; - white-space: nowrap; - clip: rect(1px, 1px, 1px, 1px); - margin-top: -3px; - opacity: 0.01; -} - -/* Loader */ -.hcg-loading-wrapper { - display: flex; - align-items: center; - justify-content: center; - position: absolute; - width: 100%; - height: 100%; - gap: 10px; - color: light-dark(#000000, #ffffff); -} - -.hcg-loading-wrapper .hcg-spinner { - border-top-width: 5px; - border-top-style: solid; - border-top-color: light-dark(#000000, #ffffff); - border-radius: 50%; - width: 30px; - height: 30px; - animation: spin 1s linear infinite; -} - -@keyframes spin { - from { - transform: rotate(0deg); - } - - to { - transform: rotate(360deg); - } -} - -/* Start Grid CSS Helpers Classes */ - -.hcg-table thead tr th.hcg-right .hcg-header-cell-content, -.hcg-table tbody tr > :where(.hcg-cell).hcg-right { - text-align: right; -} - -.hcg-table thead tr th.hcg-center .hcg-header-cell-content, -.hcg-table tbody tr > :where(.hcg-cell).hcg-center { - text-align: center; -} - -.hcg-table thead tr th.hcg-left .hcg-header-cell-content, -.hcg-table tbody tr > :where(.hcg-cell).hcg-left { - text-align: left; -} - -/* End Grid CSS Helpers Classes */ diff --git a/packages/grid-lite-react/src/styles/grid-theme-default.css b/packages/grid-lite-react/src/styles/grid-theme-default.css deleted file mode 100644 index 7e85955..0000000 --- a/packages/grid-lite-react/src/styles/grid-theme-default.css +++ /dev/null @@ -1,63 +0,0 @@ -@import '@highcharts/grid-lite/css/modules/grid-theme-default.css'; - -.hcg-theme-default { - --hcg-description-color: var(--hcg-color); - --hcg-description-background: transparent; - --hcg-description-font-weight: normal; - --hcg-description-font-size: var(--hcg-font-size); - --hcg-description-font-family: inherit; - --hcg-description-line-height: normal; - --hcg-description-letter-spacing: normal; - --hcg-description-text-align: left; - --hcg-description-margin-top: 0; - --hcg-description-margin-right: 0; - --hcg-description-margin-bottom: 0; - --hcg-description-margin-left: 0; - --hcg-description-padding-top: var(--hcg-padding); - --hcg-description-padding-right: var(--hcg-padding); - --hcg-description-padding-bottom: 0; - --hcg-description-padding-left: var(--hcg-padding); -} - -.hcg-theme-default .hcg-caption { - color: var(--hcg-caption-color); - background: var(--hcg-caption-background); - font-weight: var(--hcg-caption-font-weight); - font-size: var(--hcg-caption-font-size); - font-family: var(--hcg-caption-font-family); - line-height: var(--hcg-caption-line-height); - letter-spacing: var(--hcg-caption-letter-spacing); - text-align: var(--hcg-caption-text-align); - margin-top: var(--hcg-caption-margin-top); - margin-right: var(--hcg-caption-margin-right); - margin-bottom: var(--hcg-caption-margin-bottom); - margin-left: var(--hcg-caption-margin-left); - padding-top: var(--hcg-caption-padding-top); - padding-right: var(--hcg-caption-padding-right); - padding-bottom: var(--hcg-caption-padding-bottom); - padding-left: var(--hcg-caption-padding-left); -} - -.hcg-theme-default.hcg-caption * { - font: inherit; - margin: var(--hcg-caption-child-margin); -} - -.hcg-theme-default .hcg-description { - color: var(--hcg-description-color); - background: var(--hcg-description-background); - font-weight: var(--hcg-description-font-weight); - font-size: var(--hcg-description-font-size); - font-family: var(--hcg-description-font-family); - line-height: var(--hcg-description-line-height); - letter-spacing: var(--hcg-description-letter-spacing); - text-align: var(--hcg-description-text-align); - margin-top: var(--hcg-description-margin-top); - margin-right: var(--hcg-description-margin-right); - margin-bottom: var(--hcg-description-margin-bottom); - margin-left: var(--hcg-description-margin-left); - padding-top: var(--hcg-description-padding-top); - padding-right: var(--hcg-description-padding-right); - padding-bottom: var(--hcg-description-padding-bottom); - padding-left: var(--hcg-description-padding-left); -} diff --git a/packages/grid-pro-react/src/utils/mappers/grid/gridOptions.ts b/packages/grid-pro-react/src/utils/mappers/grid/gridOptions.ts index 49e42ce..4d62502 100644 --- a/packages/grid-pro-react/src/utils/mappers/grid/gridOptions.ts +++ b/packages/grid-pro-react/src/utils/mappers/grid/gridOptions.ts @@ -16,7 +16,7 @@ import type { import type { AfterTreeRowToggleEvent, BeforeTreeRowToggleEvent -} from '@highcharts/grid-pro/es-modules/Grid/Pro/TreeView/TreeProjectionController.js'; +} from '@highcharts/grid-pro/es-modules/Grid/Pro/TreeView/Projection/TreeProjectionController.js'; import { mapEventsProps } from '../../mapEventsProps'; /** diff --git a/packages/grid-pro-react/tests/mappers/gridOptions.test.tsx b/packages/grid-pro-react/tests/mappers/gridOptions.test.tsx index 60a8f82..dc07360 100644 --- a/packages/grid-pro-react/tests/mappers/gridOptions.test.tsx +++ b/packages/grid-pro-react/tests/mappers/gridOptions.test.tsx @@ -75,6 +75,78 @@ describe('buildGridOptions', () => { expect(options.gridKey).toBe('GRID-KEY'); expect(options.columns).toEqual([{ id: 'name' }]); }); + + it('omits rendering.theme when theme prop is undefined', () => { + const options = buildGridOptions( + 'GRID-KEY', + {}, + void 0, + { + gridKey: 'GRID-KEY', + className: 'rounded-md border' + } as GridProProps + ); + + expect(options.rendering?.theme).toBeUndefined(); + expect( + (options.rendering as { className?: string } | undefined)?.className + ).toBeUndefined(); + }); + + it('passes empty theme to disable Core default', () => { + const options = buildGridOptions( + 'GRID-KEY', + {}, + void 0, + { + gridKey: 'GRID-KEY', + theme: '', + className: 'rounded-md border' + } as GridProProps + ); + + expect(options.rendering?.theme).toBe(''); + expect( + (options.rendering as { className?: string } | undefined)?.className + ).toBeUndefined(); + }); + + it('passes custom theme without putting className into Core options', () => { + const options = buildGridOptions( + 'GRID-KEY', + {}, + void 0, + { + gridKey: 'GRID-KEY', + theme: 'myTheme', + className: 'rounded-md border' + } as GridProProps + ); + + expect(options.rendering?.theme).toBe('myTheme'); + expect( + (options.rendering as { className?: string } | undefined)?.className + ).toBeUndefined(); + }); + + it('maps tableClassName to rendering.table.className', () => { + const options = buildGridOptions( + 'GRID-KEY', + {}, + void 0, + { + gridKey: 'GRID-KEY', + theme: '', + className: 'p-8', + tableClassName: 'border border-slate-300' + } as GridProProps + ); + + expect(options.rendering?.theme).toBe(''); + expect(options.rendering?.table?.className).toBe( + 'border border-slate-300' + ); + }); }); describe('getGridEventPropDeps', () => { @@ -90,6 +162,7 @@ describe('getGridEventPropDeps', () => { 'KEY', void 0, void 0, + void 0, ...GRID_EVENT_PROP_KEYS.map( (key: keyof GridProProps) => props[key] ) From cf2fed614d8489598b3024fe4b24fe331969e2cd Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Tue, 11 Aug 2026 11:14:55 +0200 Subject: [PATCH 47/51] Fixed Grid component and the base. --- packages/grid-lite-react/src/Grid.tsx | 10 ++++++---- packages/grid-pro-react/src/Grid.tsx | 5 +++-- .../grid-shared-react/src/components/BaseGrid.tsx | 13 ++++++++++--- packages/grid-shared-react/src/index.ts | 2 ++ 4 files changed, 21 insertions(+), 9 deletions(-) diff --git a/packages/grid-lite-react/src/Grid.tsx b/packages/grid-lite-react/src/Grid.tsx index e42fd46..d6aec44 100644 --- a/packages/grid-lite-react/src/Grid.tsx +++ b/packages/grid-lite-react/src/Grid.tsx @@ -12,7 +12,7 @@ import { useDeclarativeGridOptions } from '@highcharts/grid-shared-react'; import Grid from '@highcharts/grid-lite/es-modules/masters/grid-lite.src'; -import './styles/grid-core.css'; +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'; @@ -24,7 +24,8 @@ export default function GridLite(props: GridProps) { options, callback, theme, - className + className, + tableClassName } = props; const { gridOptions, columnKey } = useDeclarativeGridOptions( children, @@ -33,9 +34,9 @@ export default function GridLite(props: GridProps) { childOptions, opts, theme, - className + tableClassName ), - [theme, className] + [theme, tableClassName] ); return ( @@ -45,6 +46,7 @@ export default function GridLite(props: GridProps) { Grid={Grid} callback={callback} ref={gridRef} + className={className} /> ); } diff --git a/packages/grid-pro-react/src/Grid.tsx b/packages/grid-pro-react/src/Grid.tsx index cbd0d46..7400e61 100644 --- a/packages/grid-pro-react/src/Grid.tsx +++ b/packages/grid-pro-react/src/Grid.tsx @@ -20,12 +20,12 @@ import { import { buildGridOptions } from './utils/buildGridOptions'; export default function GridPro(props: GridProProps) { - const { gridKey, gridRef, children, options, callback } = props; + const { gridRef, children, options, callback, className } = props; const { gridOptions, columnKey } = useDeclarativeGridOptions( children, options, (childOptions, opts) => buildGridOptions( - gridKey, + props.gridKey, childOptions, opts, props @@ -40,6 +40,7 @@ export default function GridPro(props: GridProProps) { Grid={Grid} callback={callback} ref={gridRef} + className={className} /> ); } diff --git a/packages/grid-shared-react/src/components/BaseGrid.tsx b/packages/grid-shared-react/src/components/BaseGrid.tsx index 917eb36..b9df08f 100644 --- a/packages/grid-shared-react/src/components/BaseGrid.tsx +++ b/packages/grid-shared-react/src/components/BaseGrid.tsx @@ -33,12 +33,19 @@ export interface GridProps { */ options?: TOptions; /** - * Optional CSS class names applied on the Grid container (`hcg-container`), - * merged with `theme` into `rendering.theme`. + * Optional CSS class names on the React mount container (parent of + * `.hcg-container`). Independent of `theme`. */ className?: string; /** - * Optional theme name passed to Grid Core. + * Optional CSS class names mapped to Core `rendering.table.className` on + * `.hcg-table`. Independent of `className` / `theme`. + */ + tableClassName?: string; + /** + * Optional theme name passed to Grid Core as `rendering.theme`. + * Omitted → Core default (`hcg-theme-default`). + * Defined (including `''`) → that value only. */ theme?: string; /** diff --git a/packages/grid-shared-react/src/index.ts b/packages/grid-shared-react/src/index.ts index e9f5bd8..23f0e41 100644 --- a/packages/grid-shared-react/src/index.ts +++ b/packages/grid-shared-react/src/index.ts @@ -23,6 +23,7 @@ export { } from './components/options'; export { getChildProps } from './utils/getChildProps'; export { isObject } from './utils/isObject'; +export { mergeClassNames } from './utils/mergeClassNames'; export { normalizeChildOptions } from './utils/normalizeChildOptions'; export { useDeclarativeGridOptions } from './hooks/useDeclarativeGridOptions'; export type { @@ -33,6 +34,7 @@ export type { DataColumnValue, ColumnProps, ColumnOptionsProps, + ColumnDefaultsProps, ColumnDataType, ColumnSortingOrder, CellValueGetterContext, From bcc20544857800974d572fbac007a400972d2684 Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Tue, 11 Aug 2026 13:33:47 +0200 Subject: [PATCH 48/51] Fixed pagination and ColumnDefaults. --- .../options/columns/ColumnDefaults.tsx | 19 +++++++++++++++++- .../src/components/options/index.ts | 1 + .../options/pagination/paginationProps.ts | 20 +++++++++++++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/packages/grid-shared-react/src/components/options/columns/ColumnDefaults.tsx b/packages/grid-shared-react/src/components/options/columns/ColumnDefaults.tsx index d8569ba..d5a1faf 100644 --- a/packages/grid-shared-react/src/components/options/columns/ColumnDefaults.tsx +++ b/packages/grid-shared-react/src/components/options/columns/ColumnDefaults.tsx @@ -9,7 +9,24 @@ import type { ColumnOptionsProps } from './columnProps'; -export function ColumnDefaults(_props: ColumnOptionsProps) { +/** + * ColumnDefaults props include shared column options plus grid-level row + * class hooks (lifted to `rendering.rows` during normalize). + */ +export interface ColumnDefaultsProps extends ColumnOptionsProps { + /** + * CSS class names on every body ``. + * Maps to Core `rendering.rows.className`. + */ + rowClassName?: string; + /** + * CSS class names on even body `` (Core `.hcg-row-even` parity). + * Maps to Core `rendering.rows.evenClassName`. + */ + evenRowClassName?: string; +} + +export function ColumnDefaults(_props: ColumnDefaultsProps) { return null; } diff --git a/packages/grid-shared-react/src/components/options/index.ts b/packages/grid-shared-react/src/components/options/index.ts index 36984b3..6966a89 100644 --- a/packages/grid-shared-react/src/components/options/index.ts +++ b/packages/grid-shared-react/src/components/options/index.ts @@ -12,6 +12,7 @@ export type { CaptionProps } from './caption'; export { Data } from './data/Data'; export type { DataProps, DataColumns, DataColumnValue } from './data/Data'; export { ColumnDefaults } from './columns/ColumnDefaults'; +export type { ColumnDefaultsProps } from './columns/ColumnDefaults'; export { Column } from './columns/Column'; export type { ColumnProps, diff --git a/packages/grid-shared-react/src/components/options/pagination/paginationProps.ts b/packages/grid-shared-react/src/components/options/pagination/paginationProps.ts index a5f9a93..3e31579 100644 --- a/packages/grid-shared-react/src/components/options/pagination/paginationProps.ts +++ b/packages/grid-shared-react/src/components/options/pagination/paginationProps.ts @@ -14,6 +14,26 @@ export interface PaginationProps { * Pass `false` to disable pagination while keeping other options. */ enabled?: boolean; + /** + * Additional CSS class name(s) for the pagination container + * (`.hcg-pagination`). + */ + className?: string; + /** + * Additional CSS class name(s) for the page info element + * (`.hcg-pagination-info`). + */ + infoClassName?: string; + /** + * Additional CSS class name(s) for the controls container + * (`.hcg-pagination-controls`). + */ + controlsClassName?: string; + /** + * Additional CSS class name(s) for the page size container + * (`.hcg-pagination-page-size`). + */ + sizeClassName?: string; /** * The current page number. */ From efcd81835991bdfa96a61b566965c791fee20c12 Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Tue, 11 Aug 2026 15:51:07 +0200 Subject: [PATCH 49/51] Added tests. --- .../tests/buildGridOptions.test.ts | 46 ++++++++++++++++ .../tests/options/ColumnDefaults.test.tsx | 52 +++++++++++++++++++ .../tests/options/Pagination.test.tsx | 34 ++++++++++++ 3 files changed, 132 insertions(+) create mode 100644 packages/grid-lite-react/tests/buildGridOptions.test.ts diff --git a/packages/grid-lite-react/tests/buildGridOptions.test.ts b/packages/grid-lite-react/tests/buildGridOptions.test.ts new file mode 100644 index 0000000..6fe9dcd --- /dev/null +++ b/packages/grid-lite-react/tests/buildGridOptions.test.ts @@ -0,0 +1,46 @@ +import { describe, it, expect } from 'vitest'; +import { buildGridOptions } from '../src/utils/buildGridOptions'; + +describe('buildGridOptions theme', () => { + it('omits rendering.theme when theme prop is undefined', () => { + const options = buildGridOptions({}); + + expect(options.rendering?.theme).toBeUndefined(); + }); + + it('passes empty theme to disable Core default', () => { + const options = buildGridOptions({}, void 0, ''); + + expect(options.rendering?.theme).toBe(''); + }); + + it('passes custom theme as-is', () => { + const options = buildGridOptions({}, void 0, 'myTheme'); + + expect(options.rendering?.theme).toBe('myTheme'); + }); + + it('does not put className into Core options', () => { + // className is React-only on the mount container + const options = buildGridOptions({}, void 0, 'hcg-theme-default'); + + expect(options.rendering?.theme).toBe('hcg-theme-default'); + expect( + (options.rendering as { className?: string } | undefined)?.className + ).toBeUndefined(); + }); + + it('maps tableClassName to rendering.table.className', () => { + const options = buildGridOptions( + {}, + void 0, + '', + 'border border-slate-300 w-full' + ); + + expect(options.rendering?.theme).toBe(''); + expect(options.rendering?.table?.className).toBe( + 'border border-slate-300 w-full' + ); + }); +}); diff --git a/packages/grid-shared-react/tests/options/ColumnDefaults.test.tsx b/packages/grid-shared-react/tests/options/ColumnDefaults.test.tsx index 9cbfff9..ae83c94 100644 --- a/packages/grid-shared-react/tests/options/ColumnDefaults.test.tsx +++ b/packages/grid-shared-react/tests/options/ColumnDefaults.test.tsx @@ -1,4 +1,5 @@ import { describe, it, expect } from 'vitest'; +import { Column } from '../../src/components/options/columns/Column'; import { ColumnDefaults } from '../../src/components/options/columns/ColumnDefaults'; import { getChildProps } from '../../src/utils/getChildProps'; import { normalizeChildOptions } from '../../src/utils/normalizeChildOptions'; @@ -47,4 +48,55 @@ describe('ColumnDefaults normalization', () => { } }); }); + + it('merges columnDefaults classNames into column overrides', () => { + const options = normalizeChildOptions( + getChildProps( + <> + + + + ) + ); + + expect(options.columns).toEqual([ + { + id: 'name', + className: 'hcg-name-column', + header: { + className: 'p-4 text-left border-b hcg-name-header' + }, + cells: { + className: 'p-4 text-left border-b hcg-name-cell' + } + } + ]); + }); + + it('lifts rowClassName and evenRowClassName to rendering.rows', () => { + const options = normalizeChildOptions( + getChildProps( + + ) + ); + + expect(options.columnDefaults).toEqual({}); + expect(options.rendering).toEqual({ + rows: { + className: 'hover:bg-slate-50', + evenClassName: 'bg-slate-50' + } + }); + }); }); diff --git a/packages/grid-shared-react/tests/options/Pagination.test.tsx b/packages/grid-shared-react/tests/options/Pagination.test.tsx index 400343c..fed4546 100644 --- a/packages/grid-shared-react/tests/options/Pagination.test.tsx +++ b/packages/grid-shared-react/tests/options/Pagination.test.tsx @@ -96,4 +96,38 @@ describe('Pagination normalization', () => { } }); }); + + it('maps className props onto pagination and controls', () => { + expect( + normalizeChildOptions( + getChildProps( + + ) + ) + ).toEqual({ + pagination: { + enabled: true, + className: 'mt-4', + position: 'top', + controls: { + className: 'gap-2', + pageInfo: { + enabled: true, + className: 'text-sm text-slate-500' + }, + pageSizeSelector: { + enabled: true, + options: [3, 5, 10], + className: 'text-sm' + } + } + } + }); + }); }); From 31051aa84907d8b4a806af2107562e18ec3aaed0 Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Thu, 13 Aug 2026 14:03:08 +0200 Subject: [PATCH 50/51] Redesigned README.md files. --- README.md | 202 ++++++++++++------------ packages/grid-lite-react/README.md | 219 ++++++++++++++------------ packages/grid-pro-react/README.md | 238 +++++++++++++++++------------ 3 files changed, 361 insertions(+), 298 deletions(-) diff --git a/README.md b/README.md index 18b57d8..e335b55 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,31 @@ # Highcharts Grid React -Monorepo containing React packages for [Highcharts Grid Lite](https://www.highcharts.com/docs/grid/getting-started/grid-lite) and [Highcharts Grid Pro](https://www.highcharts.com/docs/grid/getting-started/grid-pro). +
-## Packages +Official Highcharts Grid for React + + + +

Official React packages for Highcharts Grid Lite and Highcharts Grid Pro. Built for React patterns, with a JSX-native API, TypeScript types, and the Grid CSS included.

+ +Grid Lite React NPM Version +Grid Pro React NPM Version +Discord + +
+ +This is the working repository for the Grid React packages. If you want to use Grid in a React app, install a distribution package from npm rather than this repo. -This monorepo contains the following packages: +## Why Highcharts Grid React? + +- **JSX-Native API** - Compose grids with React components such as `Data`, `Column`, `Caption`, and `Pagination` +- **Lite and Pro** - Start with free Grid Lite, or use Grid Pro for editing, validation, sparklines, and events +- **Self-Contained Packages** - Grid setup, cleanup, and CSS are handled for you +- **Built for Large Tables** - Row virtualization keeps scrolling smooth with thousands of records +- **Accessibility First** - Semantic HTML tables with keyboard navigation and screen reader support +- **TypeScript Ready** - First-class types for options, refs, events, and component props + +## Packages ### Published Packages @@ -15,9 +36,7 @@ This monorepo contains the following packages: - **[@highcharts/grid-shared-react](./packages/grid-shared-react/)** - Shared core functionality used by both Grid Lite and Grid Pro React packages -## Quick Start - -### Installation +## Installation ```bash # For Grid Lite @@ -27,48 +46,59 @@ npm install @highcharts/grid-lite-react npm install @highcharts/grid-pro-react ``` -### Usage +> **Note:** The matching Grid Core package is included as a dependency. `react` and `react-dom` are peer dependencies and are installed automatically with npm v7+. Requires React 18 or higher. -#### Grid Lite - -```tsx -import React, { useState } from 'react'; -import { Grid, type GridOptions } from '@highcharts/grid-lite-react'; - -function App() { - const [options] = useState({ - dataTable: { - columns: { - name: ['Alice', 'Bob', 'Charlie'], - age: [23, 34, 45] - } - } - }); +## Quick Start - return ; +### Grid Lite + +```jsx +import { Grid, Caption, Data, Column } from '@highcharts/grid-lite-react'; + +export function App() { + return ( + +
+ + + + + ); } ``` -#### Grid Pro - -```tsx -import React, { useState } from 'react'; -import { Grid, type GridOptions } from '@highcharts/grid-pro-react'; - -function App() { - const [options] = useState({ - dataTable: { - columns: { - name: ['Alice', 'Bob', 'Charlie'], - age: [23, 34, 45] - } - } - }); - - return ; +### Grid Pro + +```jsx +import { Grid, Caption, Data, Column } from '@highcharts/grid-pro-react'; + +export function App() { + return ( + + + + + + + ); } ``` +See the package READMEs for TypeScript, refs, Next.js, and more: + +- [Grid Lite React](./packages/grid-lite-react/README.md) +- [Grid Pro React](./packages/grid-pro-react/README.md) + ## Repository Structure ``` @@ -76,30 +106,19 @@ highcharts-grid-react/ ├── packages/ # Source packages │ ├── grid-lite-react/ # Grid Lite React package │ ├── grid-pro-react/ # Grid Pro React package -│ └── grid-shared-react/ # Shared core functionality +│ └── grid-shared-react/ # Shared core functionality ├── examples/ # Example applications │ ├── grid-lite/ # Grid Lite examples │ │ ├── minimal-react/ # Minimal React example (Vite) +│ │ ├── components-react/ # JSX component API example (Vite) │ │ └── minimal-nextjs/ # Minimal Next.js example │ └── grid-pro/ # Grid Pro examples │ ├── minimal-react/ # Minimal React example (Vite) -│ └── minimal-nextjs/ # Minimal Next.js example +│ ├── components-react/ # JSX component API example (Vite) +│ └── minimal-nextjs/ # Minimal Next.js example └── README.md # This file ``` -### Packages - -- **`packages/grid-lite-react/`** - React component package for Highcharts Grid Lite. See [README](./packages/grid-lite-react/README.md) for details. -- **`packages/grid-pro-react/`** - React component package for Highcharts Grid Pro. See [README](./packages/grid-pro-react/README.md) for details. -- **`packages/grid-shared-react/`** - Internal package containing shared React components and hooks used by both packages. - -### Examples - -- **`examples/grid-lite/minimal-react/`** - Minimal React application (Vite) demonstrating how to use `@highcharts/grid-lite-react` -- **`examples/grid-lite/minimal-nextjs/`** - Minimal Next.js application demonstrating how to use `@highcharts/grid-lite-react` -- **`examples/grid-pro/minimal-react/`** - Minimal React application (Vite) demonstrating how to use `@highcharts/grid-pro-react` -- **`examples/grid-pro/minimal-nextjs/`** - Minimal Next.js application demonstrating how to use `@highcharts/grid-pro-react` - ## Development This is a monorepo managed with [pnpm workspaces](https://pnpm.io/workspaces). @@ -131,6 +150,10 @@ To run the example applications: cd examples/grid-lite/minimal-react pnpm dev +# Run Grid Lite JSX components example +cd examples/grid-lite/components-react +pnpm dev + # Run Grid Lite Next.js example cd examples/grid-lite/minimal-nextjs pnpm dev @@ -139,6 +162,10 @@ pnpm dev cd examples/grid-pro/minimal-react pnpm dev +# Run Grid Pro JSX components example +cd examples/grid-pro/components-react +pnpm dev + # Run Grid Pro Next.js example cd examples/grid-pro/minimal-nextjs pnpm dev @@ -148,64 +175,25 @@ Note: Since all examples are part of the pnpm workspace, dependencies are instal ## Next.js Integration -Highcharts Grid React components can be used in Next.js applications. Since the Grid components require browser APIs, they need to be rendered on the client side only (without Server-Side Rendering). - -### Setup - -1. Install the required packages: - -```bash -npm install @highcharts/grid-lite-react @highcharts/grid-lite -# or -npm install @highcharts/grid-pro-react @highcharts/grid-pro -``` +Highcharts Grid React components can be used in Next.js applications. Grid uses browser APIs, so it must render on the client. See the [Next.js guide](https://www.highcharts.com/docs/grid/frameworks/nextjs) and the package READMEs for a complete example. -2. Import the Grid component dynamically with SSR disabled: - -```tsx -'use client'; - -import { useState } from 'react'; -import dynamic from 'next/dynamic'; -import { type GridOptions } from '@highcharts/grid-lite-react'; -import '@highcharts/grid-lite/css/grid-lite.css'; - -// Disable SSR for the Grid component -const Grid = dynamic( - () => import('@highcharts/grid-lite-react').then((mod) => mod.Grid), - { ssr: false } -); - -export default function Page() { - const [options] = useState({ - dataTable: { - columns: { - name: ['Alice', 'Bob', 'Charlie'], - age: [23, 34, 45] - } - } - }); - - return ; -} -``` +## Documentation -### Important Notes +- [Grid Lite React](./packages/grid-lite-react/README.md) +- [Grid Pro React](./packages/grid-pro-react/README.md) +- [Highcharts Grid with React](https://www.highcharts.com/docs/grid/frameworks/react) +- [Highcharts Grid Lite](https://www.highcharts.com/docs/grid/getting-started/grid-lite) +- [Highcharts Grid Pro](https://www.highcharts.com/docs/grid/getting-started/grid-pro) +- [Changelog](./CHANGELOG.md) +- [Releasing](./RELEASING.md) -- **SSR is disabled**: The Grid components require browser APIs and cannot be rendered on the server. They are dynamically imported with `ssr: false` to ensure client-side only rendering. -- **Client Component**: The page or component using the Grid must be marked with `'use client'` directive. -- **CSS Import**: Don't forget to import the required CSS file for the Grid component. +## Support and feedback -See the [Next.js examples](./examples/) for complete working implementations. +We love to learn how you are using Highcharts, and what you would like to see from us in the future. -## Documentation +Join our vibrant community on [GitHub](https://github.com/highcharts/grid-react), [Stack Overflow](https://stackoverflow.com/tags/highcharts/), [Discord](https://discord.com/invite/xHxxcyyy6K), and the [Highcharts Forums](https://www.highcharts.com/forum/). -- [Grid Lite React Documentation](./packages/grid-lite-react/README.md) -- [Grid Pro React Documentation](./packages/grid-pro-react/README.md) -- [Highcharts Grid Lite Documentation](https://www.highcharts.com/docs/grid/getting-started/grid-lite) -- [Highcharts Grid Pro Documentation](https://www.highcharts.com/docs/grid/getting-started/grid-pro) -- [Changelog](./CHANGELOG.md) -- [Releasing](./RELEASING.md) +Commercial support packages are available, see [Highcharts Advantage](https://www.highcharts.com/highcharts-advantage/). ## License diff --git a/packages/grid-lite-react/README.md b/packages/grid-lite-react/README.md index 74f0632..baf95f7 100644 --- a/packages/grid-lite-react/README.md +++ b/packages/grid-lite-react/README.md @@ -1,136 +1,160 @@ -# @highcharts/grid-lite-react +# Highcharts Grid Lite React -React integration for [Highcharts Grid Lite](https://www.highcharts.com/docs/grid/general). +
-## Links +Official Highcharts Grid Lite for React -* Official website: [www.highcharts.com](https://www.highcharts.com) -* Product page: [www.highcharts.com/products/grid](https://www.highcharts.com/products/grid) -* Download: [www.highcharts.com/download](https://www.highcharts.com/download) -* License: [www.highcharts.com/license](https://www.highcharts.com/license) -* Documentation: [www.highcharts.com/docs](https://www.highcharts.com/docs/grid/frameworks/grid-with-react) -* Support: [www.highcharts.com/support](https://www.highcharts.com/support) -* Issues: [Working repo](https://github.com/highcharts/highcharts/issues) + +

Highcharts Grid Lite for React makes integrating interactive data tables into your React projects intuitive and aligned with your React workflow, built with an API refined for React patterns.

-## Installation - -```bash -npm install @highcharts/grid-lite-react -``` +NPM Version +NPM Downloads +Discord -## Requirements +
-- React 18 or higher +## Why Highcharts Grid Lite React? -## Quick Start +- **JSX-Native API** - Compose grids with React components such as `Data`, `Column`, `Caption`, and `Pagination` +- **Self-Contained Package** - Grid setup, cleanup, and CSS are handled for you +- **Built for Large Tables** - Row virtualization keeps scrolling smooth with thousands of records +- **Interactive by Default** - Sorting, filtering, and pagination without extra libraries +- **Accessibility First** - Renders a semantic HTML table with keyboard navigation and screen reader support +- **CSS Theming** - Customize appearance with CSS variables and class names that fit your app +- **TypeScript Ready** - First-class types for options, refs, and component props -```tsx -import React, { useState } from 'react'; -import { Grid, type GridOptions } from '@highcharts/grid-lite-react'; - -function App() { - const [options] = useState({ - dataTable: { - columns: { - name: ['Alice', 'Bob', 'Charlie'], - age: [23, 34, 45], - city: ['New York', 'Oslo', 'Paris'] - } - }, - caption: { - text: 'My Grid' - } - }); - - return ; -} -``` +## License -## API +Highcharts Grid Lite is free to use. Review the license terms at the links below: -### `Grid` +- [Standard License Terms](https://www.highcharts.com/license) +- [Product page](https://www.highcharts.com/products/grid) -React component that wraps Highcharts Grid Lite. +Need editing, validation, sparklines, or events? See [@highcharts/grid-pro-react](https://www.npmjs.com/package/@highcharts/grid-pro-react). -#### Props +## Installation -- `options` (required): Configuration options for the grid. Type: `GridOptions` -- `gridRef` (optional): React ref to access the underlying grid instance. Type: `RefObject>` -- `callback` (optional): Callback function called when the grid is initialized. Receives the grid instance as parameter. Type: `(grid: GridInstance) => void` +Install Highcharts Grid Lite React from npm: -### `GridOptions` +```bash +npm install @highcharts/grid-lite-react +``` -Type exported from the package for TypeScript support. +Or using yarn: -```tsx -import type { GridOptions } from '@highcharts/grid-lite-react'; +```bash +yarn add @highcharts/grid-lite-react ``` -### `GridRefHandle` +> **Note:** `@highcharts/grid-lite` is included as a dependency. `react` and `react-dom` are peer dependencies and are installed automatically with npm v7+. Requires React 18 or higher. -Type for the gridRef handle that provides access to the underlying grid instance. +## Quick Start -```tsx -import type { GridRefHandle } from '@highcharts/grid-lite-react'; +```jsx +import { Grid, Caption, Data, Column, Pagination } from '@highcharts/grid-lite-react'; -const gridRef = useRef | null>(null); -// Access the grid instance via gridRef.current?.grid +export function App() { + return ( + +
+ + + + + + + ); +} ``` -### `GridInstance` +## Grid props -Type for the grid instance returned by gridRef or callback. +The grid is rendered inside a container. You can pass layout and theme props directly to `Grid`: -```tsx -import type { GridInstance } from '@highcharts/grid-lite-react'; +```jsx + + + + ``` -### Using gridRef and Callback +- `className` applies to the React mount container +- `tableClassName` applies to the rendered table +- `theme` sets the Grid theme (`rendering.theme`) + +You can also pass a Grid options object via the `options` prop when you prefer a configuration object over JSX children. -You can access the grid instance in two ways: +## TypeScript + +Use `GridOptions` for the `Grid` component `options` prop. -**Using gridRef:** ```tsx -import { useRef } from 'react'; -import { Grid, type GridRefHandle, type GridOptions } from '@highcharts/grid-lite-react'; +import { useState } from 'react'; +import { Grid, type GridOptions } from '@highcharts/grid-lite-react'; -function App() { - const gridRef = useRef | null>(null); - - const handleClick = () => { - // Access the grid instance - const gridInstance = gridRef.current?.grid; - if (gridInstance) { - console.log('Grid instance:', gridInstance); +export function App() { + const [options] = useState({ + data: { + columns: { + name: ['Alice', 'Bob', 'Charlie'], + age: [23, 34, 45] + } } - }; + }); - return ( - <> - - - - ); + return ; } ``` -**Using callback:** +Use `GridRefHandle` and `GridInstance` when you need access to the underlying Grid instance. + ```tsx -import { Grid, type GridInstance, type GridOptions } from '@highcharts/grid-lite-react'; +import { useRef } from 'react'; +import { + Grid, + type GridOptions, + type GridRefHandle, + type GridInstance +} from '@highcharts/grid-lite-react'; + +export function App() { + const gridRef = useRef | null>(null); -function App() { - const handleGridReady = (grid: GridInstance) => { - console.log('Grid initialized:', grid); + const onGridReady = (grid: GridInstance) => { + console.log('Grid instance:', grid); }; - return ; + return ( + + ); } ``` -### Next.js Integration +## Next.js -When using this package with Next.js, you need to disable Server-Side Rendering (SSR) for the Grid component: +Grid uses browser APIs, so it must render on the client. Use a dynamic import with SSR disabled: ```tsx 'use client'; @@ -138,9 +162,7 @@ When using this package with Next.js, you need to disable Server-Side Rendering import { useState } from 'react'; import dynamic from 'next/dynamic'; import { type GridOptions } from '@highcharts/grid-lite-react'; -import '@highcharts/grid-lite/css/grid-lite.css'; -// Disable SSR for the Grid component const Grid = dynamic( () => import('@highcharts/grid-lite-react').then((mod) => mod.Grid), { ssr: false } @@ -148,7 +170,7 @@ const Grid = dynamic( export default function Page() { const [options] = useState({ - dataTable: { + data: { columns: { name: ['Alice', 'Bob', 'Charlie'], age: [23, 34, 45] @@ -160,12 +182,19 @@ export default function Page() { } ``` -**Important:** The Grid component must be rendered client-side only. Always use `dynamic` import with `ssr: false` and mark your component with `'use client'` directive. +The React package loads Grid CSS automatically. See the [Next.js guide](https://www.highcharts.com/docs/grid/frameworks/nextjs) for more detail. ## Documentation -For detailed documentation on available options and features, see the [Highcharts Grid Lite documentation](https://www.highcharts.com/docs/grid/general). +For comprehensive guides and API documentation, visit the [Highcharts Grid React documentation](https://www.highcharts.com/docs/grid/frameworks/react). -## License +- [Grid Lite getting started](https://www.highcharts.com/docs/grid/getting-started/grid-lite) +- [Highcharts Grid overview](https://www.highcharts.com/docs/grid/general) + +## Support and feedback + +We love to learn how you are using Highcharts, and what you would like to see from us in the future. + +Join our vibrant community on [GitHub](https://github.com/highcharts/grid-react), [Stack Overflow](https://stackoverflow.com/tags/highcharts/), [Discord](https://discord.com/invite/xHxxcyyy6K), and the [Highcharts Forums](https://www.highcharts.com/forum/). -SEE LICENSE IN [LICENSE](https://github.com/highcharts/grid-react/blob/main/packages/grid-lite-react/LICENSE). +Commercial support packages are available, see [Highcharts Advantage](https://www.highcharts.com/highcharts-advantage/). diff --git a/packages/grid-pro-react/README.md b/packages/grid-pro-react/README.md index c5848f5..3bc94bf 100644 --- a/packages/grid-pro-react/README.md +++ b/packages/grid-pro-react/README.md @@ -1,135 +1,176 @@ -# @highcharts/grid-pro-react +# Highcharts Grid Pro React -React integration for [Highcharts Grid Pro](https://www.highcharts.com/docs/grid/general). +
-## Links +Official Highcharts Grid Pro for React -* Official website: [www.highcharts.com](https://www.highcharts.com) -* Product page: [www.highcharts.com/products/grid](https://www.highcharts.com/products/grid) -* Download: [www.highcharts.com/download](https://www.highcharts.com/download) -* License: [www.highcharts.com/license](https://www.highcharts.com/license) -* Documentation: [www.highcharts.com/docs](https://www.highcharts.com/docs/grid/frameworks/grid-with-react) -* Support: [www.highcharts.com/support](https://www.highcharts.com/support) -* Issues: [Working repo](https://github.com/highcharts/highcharts/issues) + -## Installation +

Highcharts Grid Pro for React makes integrating editable, interactive data tables into your React projects intuitive and aligned with your React workflow, built with an API refined for React patterns.

-```bash -npm install @highcharts/grid-pro-react -``` +NPM Version +NPM Downloads +Discord -## Requirements - -- React 18 or higher - -## Quick Start +
-```tsx -import React, { useState } from 'react'; -import { Grid, type GridOptions } from '@highcharts/grid-pro-react'; +## Why Highcharts Grid Pro React? -function App() { - const [options] = useState({ - dataTable: { - columns: { - name: ['Alice', 'Bob', 'Charlie'], - age: [23, 34, 45], - city: ['New York', 'Oslo', 'Paris'] - } - }, - caption: { - text: 'My Grid' - } - }); +- **JSX-Native API** - Compose grids with React components such as `Data`, `Column`, `Caption`, and `Pagination` +- **Everything in Grid Lite** - Sorting, filtering, pagination, virtualization, theming, and accessibility +- **Interactive Data Editing** - Built-in editors for text, numbers, dates, and more +- **Validation** - Keep data clean with configurable rules and custom business logic +- **Sparklines** - Show trends in-cell, including Highcharts-powered visualizations +- **React Event Props** - Hook into load, update, sort, click, and pagination events with `on*` props +- **TypeScript Ready** - First-class types for options, refs, events, and component props - return ; -} -``` +## License -## API +Grid Pro is a commercial product. Getting licensed for commercial use makes you production-ready: license, updates and support for business-critical grids. To learn more, please contact our sales team at sales@highcharts.com. You can also review our Standard License Terms and our Annual License at the links below: -### `Grid` +- [Standard License Terms](https://www.highcharts.com/license) +- [Terms & Conditions for Annual Subscription](https://shop.highcharts.com/license-annual-3.0) +- [Product page](https://www.highcharts.com/products/grid) -React component that wraps Highcharts Grid Pro. +Looking for the free edition? See [@highcharts/grid-lite-react](https://www.npmjs.com/package/@highcharts/grid-lite-react). -#### Props +## Installation -- `options` (required): Configuration options for the grid. Type: `GridOptions` -- `gridRef` (optional): React ref to access the underlying grid instance. Type: `RefObject>` -- `callback` (optional): Callback function called when the grid is initialized. Receives the grid instance as parameter. Type: `(grid: GridInstance) => void` +Install Highcharts Grid Pro React from npm: -### `GridOptions` +```bash +npm install @highcharts/grid-pro-react +``` -Type exported from the package for TypeScript support. +Or using yarn: -```tsx -import type { GridOptions } from '@highcharts/grid-pro-react'; +```bash +yarn add @highcharts/grid-pro-react ``` -### `GridRefHandle` +> **Note:** `@highcharts/grid-pro` is included as a dependency. `react` and `react-dom` are peer dependencies and are installed automatically with npm v7+. Requires React 18 or higher. -Type for the gridRef handle that provides access to the underlying grid instance. +## Quick Start -```tsx -import type { GridRefHandle } from '@highcharts/grid-pro-react'; +Pass your Grid Pro license key with `gridKey`. + +```jsx +import { + Grid, + Caption, + Data, + Column, + Pagination +} from '@highcharts/grid-pro-react'; -const gridRef = useRef | null>(null); -// Access the grid instance via gridRef.current?.grid +export function App() { + return ( + +
+ + + + + + + ); +} ``` -### `GridInstance` +## Grid props + +The grid is rendered inside a container. You can pass layout, theme, and Pro event props directly to `Grid`: + +```jsx + + + + +``` -Type for the grid instance returned by gridRef or callback. +- `gridKey` is required and sets your Grid Pro license key +- `className` applies to the React mount container +- `tableClassName` applies to the rendered table +- `theme` sets the Grid theme (`rendering.theme`) +- `onAfterLoad` and other `on*` props map to Grid Pro events -```tsx -import type { GridInstance } from '@highcharts/grid-pro-react'; -``` +You can also pass a Grid options object via the `options` prop when you prefer a configuration object over JSX children. -### Using gridRef and Callback +## TypeScript -You can access the grid instance in two ways: +Use `GridOptions` for the `Grid` component `options` prop. -**Using gridRef:** ```tsx -import { useRef } from 'react'; -import { Grid, type GridRefHandle, type GridOptions } from '@highcharts/grid-pro-react'; +import { useState } from 'react'; +import { Grid, type GridOptions } from '@highcharts/grid-pro-react'; -function App() { - const gridRef = useRef | null>(null); - - const handleClick = () => { - // Access the grid instance - const gridInstance = gridRef.current?.grid; - if (gridInstance) { - console.log('Grid instance:', gridInstance); +export function App() { + const [options] = useState({ + data: { + columns: { + name: ['Alice', 'Bob', 'Charlie'], + age: [23, 34, 45] + } } - }; + }); - return ( - <> - - - - ); + return ; } ``` -**Using callback:** +Use `GridRefHandle` and `GridInstance` when you need access to the underlying Grid instance. + ```tsx -import { Grid, type GridInstance, type GridOptions } from '@highcharts/grid-pro-react'; +import { useRef } from 'react'; +import { + Grid, + type GridOptions, + type GridRefHandle, + type GridInstance +} from '@highcharts/grid-pro-react'; + +export function App() { + const gridRef = useRef | null>(null); -function App() { - const handleGridReady = (grid: GridInstance) => { - console.log('Grid initialized:', grid); + const onGridReady = (grid: GridInstance) => { + console.log('Grid instance:', grid); }; - return ; + return ( + + ); } ``` -### Next.js Integration +## Next.js -When using this package with Next.js, you need to disable Server-Side Rendering (SSR) for the Grid component: +Grid uses browser APIs, so it must render on the client. Use a dynamic import with SSR disabled: ```tsx 'use client'; @@ -137,9 +178,7 @@ When using this package with Next.js, you need to disable Server-Side Rendering import { useState } from 'react'; import dynamic from 'next/dynamic'; import { type GridOptions } from '@highcharts/grid-pro-react'; -import '@highcharts/grid-pro/css/grid-pro.css'; -// Disable SSR for the Grid component const Grid = dynamic( () => import('@highcharts/grid-pro-react').then((mod) => mod.Grid), { ssr: false } @@ -147,7 +186,7 @@ const Grid = dynamic( export default function Page() { const [options] = useState({ - dataTable: { + data: { columns: { name: ['Alice', 'Bob', 'Charlie'], age: [23, 34, 45] @@ -155,16 +194,23 @@ export default function Page() { } }); - return ; + return ; } ``` -**Important:** The Grid component must be rendered client-side only. Always use `dynamic` import with `ssr: false` and mark your component with `'use client'` directive. +The React package loads Grid CSS automatically. See the [Next.js guide](https://www.highcharts.com/docs/grid/frameworks/nextjs) for more detail. ## Documentation -For detailed documentation on available options and features, see the [Highcharts Grid Pro documentation](https://www.highcharts.com/docs/grid/general). +For comprehensive guides and API documentation, visit the [Highcharts Grid React documentation](https://www.highcharts.com/docs/grid/frameworks/react). -## License +- [Grid Pro getting started](https://www.highcharts.com/docs/grid/getting-started/grid-pro) +- [Highcharts Grid overview](https://www.highcharts.com/docs/grid/general) + +## Support and feedback + +We love to learn how you are using Highcharts, and what you would like to see from us in the future. + +Join our vibrant community on [GitHub](https://github.com/highcharts/grid-react), [Stack Overflow](https://stackoverflow.com/tags/highcharts/), [Discord](https://discord.com/invite/xHxxcyyy6K), and the [Highcharts Forums](https://www.highcharts.com/forum/). -SEE LICENSE IN [LICENSE](https://github.com/highcharts/grid-react/blob/main/packages/grid-pro-react/LICENSE). +Commercial support packages are available, see [Highcharts Advantage](https://www.highcharts.com/highcharts-advantage/). From eb7036e3486c48edc9f59a0fa36f179044ed3434 Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Thu, 13 Aug 2026 14:07:05 +0200 Subject: [PATCH 51/51] Added backward compatibility path to use Grid. --- README.md | 50 +++++++++++++++++++++++++++++- packages/grid-lite-react/README.md | 30 ++++++++++++++++-- packages/grid-pro-react/README.md | 30 +++++++++++++++--- 3 files changed, 102 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index e335b55..a30e5ba 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ This is the working repository for the Grid React packages. If you want to use G ## Why Highcharts Grid React? -- **JSX-Native API** - Compose grids with React components such as `Data`, `Column`, `Caption`, and `Pagination` +- **Options or JSX** - Pass a Grid `options` object, compose with React components such as `Data`, `Column`, `Caption`, and `Pagination`, or mix both - **Lite and Pro** - Start with free Grid Lite, or use Grid Pro for editing, validation, sparklines, and events - **Self-Contained Packages** - Grid setup, cleanup, and CSS are handled for you - **Built for Large Tables** - Row virtualization keeps scrolling smooth with thousands of records @@ -50,8 +50,33 @@ npm install @highcharts/grid-pro-react ## Quick Start +Components are optional. You can pass a Grid `options` object to `` the same way as before, use JSX components, or mix both. + ### Grid Lite +Using options: + +```jsx +import { useState } from 'react'; +import { Grid, type GridOptions } from '@highcharts/grid-lite-react'; + +export function App() { + const [options] = useState({ + caption: { text: 'Team directory' }, + data: { + columns: { + name: ['Alice', 'Bob', 'Charlie'], + age: [23, 34, 45] + } + } + }); + + return ; +} +``` + +Using components: + ```jsx import { Grid, Caption, Data, Column } from '@highcharts/grid-lite-react'; @@ -74,6 +99,29 @@ export function App() { ### Grid Pro +Using options: + +```jsx +import { useState } from 'react'; +import { Grid, type GridOptions } from '@highcharts/grid-pro-react'; + +export function App() { + const [options] = useState({ + caption: { text: 'Team directory' }, + data: { + columns: { + name: ['Alice', 'Bob', 'Charlie'], + age: [23, 34, 45] + } + } + }); + + return ; +} +``` + +Using components: + ```jsx import { Grid, Caption, Data, Column } from '@highcharts/grid-pro-react'; diff --git a/packages/grid-lite-react/README.md b/packages/grid-lite-react/README.md index baf95f7..0131af7 100644 --- a/packages/grid-lite-react/README.md +++ b/packages/grid-lite-react/README.md @@ -16,7 +16,7 @@ ## Why Highcharts Grid Lite React? -- **JSX-Native API** - Compose grids with React components such as `Data`, `Column`, `Caption`, and `Pagination` +- **Options or JSX** - Pass a Grid `options` object, compose with React components such as `Data`, `Column`, `Caption`, and `Pagination`, or mix both - **Self-Contained Package** - Grid setup, cleanup, and CSS are handled for you - **Built for Large Tables** - Row virtualization keeps scrolling smooth with thousands of records - **Interactive by Default** - Sorting, filtering, and pagination without extra libraries @@ -51,6 +51,32 @@ yarn add @highcharts/grid-lite-react ## Quick Start +Components are optional. You can pass a Grid `options` object to `` the same way as before, use JSX components, or mix both. + +### Using options + +```jsx +import { useState } from 'react'; +import { Grid, type GridOptions } from '@highcharts/grid-lite-react'; + +export function App() { + const [options] = useState({ + caption: { text: 'Team directory' }, + data: { + columns: { + name: ['Alice', 'Bob', 'Charlie'], + age: [23, 34, 45], + city: ['New York', 'Oslo', 'Paris'] + } + } + }); + + return ; +} +``` + +### Using components + ```jsx import { Grid, Caption, Data, Column, Pagination } from '@highcharts/grid-lite-react'; @@ -93,8 +119,6 @@ The grid is rendered inside a container. You can pass layout and theme props dir - `tableClassName` applies to the rendered table - `theme` sets the Grid theme (`rendering.theme`) -You can also pass a Grid options object via the `options` prop when you prefer a configuration object over JSX children. - ## TypeScript Use `GridOptions` for the `Grid` component `options` prop. diff --git a/packages/grid-pro-react/README.md b/packages/grid-pro-react/README.md index 3bc94bf..900396c 100644 --- a/packages/grid-pro-react/README.md +++ b/packages/grid-pro-react/README.md @@ -16,7 +16,7 @@ ## Why Highcharts Grid Pro React? -- **JSX-Native API** - Compose grids with React components such as `Data`, `Column`, `Caption`, and `Pagination` +- **Options or JSX** - Pass a Grid `options` object, compose with React components such as `Data`, `Column`, `Caption`, and `Pagination`, or mix both - **Everything in Grid Lite** - Sorting, filtering, pagination, virtualization, theming, and accessibility - **Interactive Data Editing** - Built-in editors for text, numbers, dates, and more - **Validation** - Keep data clean with configurable rules and custom business logic @@ -52,7 +52,31 @@ yarn add @highcharts/grid-pro-react ## Quick Start -Pass your Grid Pro license key with `gridKey`. +Pass your Grid Pro license key with `gridKey`. Components are optional. You can pass a Grid `options` object to `` the same way as before, use JSX components, or mix both. + +### Using options + +```jsx +import { useState } from 'react'; +import { Grid, type GridOptions } from '@highcharts/grid-pro-react'; + +export function App() { + const [options] = useState({ + caption: { text: 'Team directory' }, + data: { + columns: { + name: ['Alice', 'Bob', 'Charlie'], + age: [23, 34, 45], + city: ['New York', 'Oslo', 'Paris'] + } + } + }); + + return ; +} +``` + +### Using components ```jsx import { @@ -108,8 +132,6 @@ The grid is rendered inside a container. You can pass layout, theme, and Pro eve - `theme` sets the Grid theme (`rendering.theme`) - `onAfterLoad` and other `on*` props map to Grid Pro events -You can also pass a Grid options object via the `options` prop when you prefer a configuration object over JSX children. - ## TypeScript Use `GridOptions` for the `Grid` component `options` prop.
Team directoryTeam directoryTeam directoryFull-width gridTeam directoryFull-width grid