From ad721bbef4e2c09a7e8bd4e959ee5e98ceff4f27 Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Tue, 30 Jun 2026 15:32:53 +0200 Subject: [PATCH 1/5] 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 2/5] 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 3/5] 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 4/5] 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 5/5] 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';