From bf322c405ae401bef1946aead8ab93988120670a Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Mon, 21 Sep 2026 11:55:00 +0200 Subject: [PATCH 1/3] Applied suggestions. --- packages/grid-lite-react/src/Grid.tsx | 16 +++-- packages/grid-pro-react/src/Grid.tsx | 72 +++++++++++++++---- .../src/hooks/useDeclarativeGridOptions.ts | 8 +-- .../src/hooks/useGrid.test.tsx | 15 +++- .../grid-shared-react/src/hooks/useGrid.ts | 23 +++--- 5 files changed, 97 insertions(+), 37 deletions(-) diff --git a/packages/grid-lite-react/src/Grid.tsx b/packages/grid-lite-react/src/Grid.tsx index d14dec8..bc45c6a 100644 --- a/packages/grid-lite-react/src/Grid.tsx +++ b/packages/grid-lite-react/src/Grid.tsx @@ -7,6 +7,7 @@ * */ +import { useCallback } from 'react'; import { BaseGrid, useDeclarativeGridOptions @@ -32,16 +33,17 @@ export default function GridLite(props: GridProps) { className, tableClassName } = props; + const build = useCallback( + ( + childOptions: Record, + opts?: Options + ) => buildGridOptions(childOptions, opts, theme, tableClassName), + [theme, tableClassName] + ); const { gridOptions, columnKey } = useDeclarativeGridOptions( children, options, - (childOptions, opts) => buildGridOptions( - childOptions, - opts, - theme, - tableClassName - ), - [theme, tableClassName] + build ); return ( diff --git a/packages/grid-pro-react/src/Grid.tsx b/packages/grid-pro-react/src/Grid.tsx index 4c5d134..6f813a1 100644 --- a/packages/grid-pro-react/src/Grid.tsx +++ b/packages/grid-pro-react/src/Grid.tsx @@ -7,16 +7,14 @@ * */ +import { useCallback } from 'react'; import { BaseGrid, useDeclarativeGridOptions } from '@highcharts/grid-shared-react'; import Grid from '@highcharts/grid-pro/es-modules/masters/grid-pro.src'; import '@highcharts/grid-pro/css/grid-pro.css'; -import type { GridProProps } from './utils/mappers/grid'; -import { - getGridEventPropDeps -} from './utils/mappers/grid'; +import type { GridProOptions, GridProProps } from './utils/mappers/grid'; import { buildGridOptions } from './utils/buildGridOptions'; /** @@ -25,17 +23,67 @@ import { buildGridOptions } from './utils/buildGridOptions'; * Links to Grid.Options */ export default function GridPro(props: GridProProps) { - const { gridRef, children, options, callback, className } = props; + const { + gridRef, + children, + options, + callback, + className, + gridKey, + theme, + tableClassName, + onBeforeLoad, + onAfterLoad, + onBeforeUpdate, + onAfterUpdate, + onBeforeRedraw, + onAfterRedraw, + onBeforeTreeRowToggle, + onAfterTreeRowToggle, + onBeforeRowPin, + onAfterRowPin + } = props; + const build = useCallback( + ( + childOptions: Record, + opts?: GridProOptions + ) => buildGridOptions(gridKey, childOptions, opts, { + gridKey, + theme, + className, + tableClassName, + onBeforeLoad, + onAfterLoad, + onBeforeUpdate, + onAfterUpdate, + onBeforeRedraw, + onAfterRedraw, + onBeforeTreeRowToggle, + onAfterTreeRowToggle, + onBeforeRowPin, + onAfterRowPin + } as GridProProps), + [ + gridKey, + theme, + className, + tableClassName, + onBeforeLoad, + onAfterLoad, + onBeforeUpdate, + onAfterUpdate, + onBeforeRedraw, + onAfterRedraw, + onBeforeTreeRowToggle, + onAfterTreeRowToggle, + onBeforeRowPin, + onAfterRowPin + ] + ); const { gridOptions, columnKey } = useDeclarativeGridOptions( children, options, - (childOptions, opts) => buildGridOptions( - props.gridKey, - childOptions, - opts, - props - ), - getGridEventPropDeps(props) + build ); return ( diff --git a/packages/grid-shared-react/src/hooks/useDeclarativeGridOptions.ts b/packages/grid-shared-react/src/hooks/useDeclarativeGridOptions.ts index be657fe..1c4503d 100644 --- a/packages/grid-shared-react/src/hooks/useDeclarativeGridOptions.ts +++ b/packages/grid-shared-react/src/hooks/useDeclarativeGridOptions.ts @@ -32,16 +32,14 @@ export interface UseDeclarativeGridOptionsFn { ( children: ReactNode | undefined, options: T | undefined, - build: OptionsBuildFn, - buildDeps?: unknown[] + build: OptionsBuildFn ): DeclarativeGridOptionsState; } export const useDeclarativeGridOptions: UseDeclarativeGridOptionsFn = ( children, options, - build, - buildDeps = [] + build ) => { const childOptions = useMemo( () => (children != null ? getChildProps(children) : {}), @@ -53,7 +51,7 @@ export const useDeclarativeGridOptions: UseDeclarativeGridOptionsFn = ( ); const gridOptions = useMemo( () => build(childOptions, options), - [childOptions, options, ...buildDeps] + [childOptions, options, build] ); return { gridOptions, columnKey }; diff --git a/packages/grid-shared-react/src/hooks/useGrid.test.tsx b/packages/grid-shared-react/src/hooks/useGrid.test.tsx index 53e86d1..1e65bd2 100644 --- a/packages/grid-shared-react/src/hooks/useGrid.test.tsx +++ b/packages/grid-shared-react/src/hooks/useGrid.test.tsx @@ -11,7 +11,10 @@ interface DeferredInit { resolve: () => Promise; } -function createDeferredGrid(initQueue: DeferredInit[]): GridType { +function createDeferredGrid( + initQueue: DeferredInit[], + destroyedIds: number[] = [] +): GridType { let nextId = 0; return { @@ -19,6 +22,7 @@ function createDeferredGrid(initQueue: DeferredInit[]): GridType { const id = ++nextId; const grid: GridInstance = { destroy: () => { + destroyedIds.push(id); container.innerHTML = ''; }, update: () => {} @@ -48,9 +52,10 @@ function createDeferredGrid(initQueue: DeferredInit[]): GridType { describe('useGrid', () => { it('keeps the active grid when StrictMode double-inits', async () => { const initQueue: DeferredInit[] = []; - const Grid = createDeferredGrid(initQueue); + const destroyedIds: number[] = []; + const Grid = createDeferredGrid(initQueue, destroyedIds); - const { container } = render( + const { container, unmount } = render( @@ -68,5 +73,9 @@ describe('useGrid', () => { await waitFor(() => { expect(container.querySelector('[data-grid-id="1"]')).not.toBeNull(); }); + + unmount(); + + expect(destroyedIds).toEqual([1]); }); }); diff --git a/packages/grid-shared-react/src/hooks/useGrid.ts b/packages/grid-shared-react/src/hooks/useGrid.ts index f5e1756..d0ab479 100644 --- a/packages/grid-shared-react/src/hooks/useGrid.ts +++ b/packages/grid-shared-react/src/hooks/useGrid.ts @@ -70,9 +70,18 @@ export function useGrid({ // allow init to complete if re-mounted. destroyOnInitRef.current = false; - // Prevent double initialization + const destroyGrid = () => { + destroyOnInitRef.current = true; + if (currGridRef.current) { + currGridRef.current.destroy(); + currGridRef.current = null; + } + }; + + // Prevent double initialization. Still return destroyGrid so the + // StrictMode remount keeps a destroy handler. if (initStartedRef.current || currGridRef.current) { - return; + return destroyGrid; } initStartedRef.current = true; @@ -114,14 +123,8 @@ export function useGrid({ initGrid(); - return () => { - destroyOnInitRef.current = true; - if (currGridRef.current) { - currGridRef.current.destroy(); - currGridRef.current = null; - } - }; - }, [containerRef, Grid]); + return destroyGrid; + }, [containerRef, Grid, options]); // Effect for options updates - separate from init useEffect(() => { From e0e1585d4acd2e147f045437e732f30b9833a836 Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Mon, 21 Sep 2026 12:05:35 +0200 Subject: [PATCH 2/3] Sync tailwind styles with grid-pro. --- .../grid-pro/components-react/package.json | 2 + .../grid-pro/components-react/src/App.tsx | 203 ++++++++++++------ .../grid-pro/components-react/src/index.css | 93 +++++++- .../grid-pro/components-react/vite.config.ts | 4 +- pnpm-lock.yaml | 6 + 5 files changed, 235 insertions(+), 73 deletions(-) diff --git a/examples/grid-pro/components-react/package.json b/examples/grid-pro/components-react/package.json index 851c39f..d216da2 100644 --- a/examples/grid-pro/components-react/package.json +++ b/examples/grid-pro/components-react/package.json @@ -16,9 +16,11 @@ "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-pro/components-react/src/App.tsx b/examples/grid-pro/components-react/src/App.tsx index 5859090..1d5a073 100644 --- a/examples/grid-pro/components-react/src/App.tsx +++ b/examples/grid-pro/components-react/src/App.tsx @@ -8,22 +8,41 @@ import { ColumnDefaults, Column, Description, - Pagination + Pagination, + Header } 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] + 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 + ] }); const onButtonClick = () => { setDataSource({ - name: ['John', 'Jane', 'Jim', 'Jill', 'Jack'], + name: ['John Hale', 'Jane Ortiz', 'Jim Novak', 'Jill Meyer', 'Jack Quinn'], age: [30, 25, 35, 40, 45], city: ['New York', 'Los Angeles', 'Chicago', 'Houston', 'Miami'], salary: [40000, 35000, 45000, 50000, 55000] @@ -35,63 +54,125 @@ function App() { }; return ( - <> - - - - Grid Pro Components - Declarative API with gridKey and event props - +
+ - - - - - -
- + > + + + Team directory +
+ + + + + + + Filter, sort, and page through sample employee rows styled with + utility classes. + + + +
+ +
- +
); } diff --git a/examples/grid-pro/components-react/src/index.css b/examples/grid-pro/components-react/src/index.css index 16edde1..93cfec9 100644 --- a/examples/grid-pro/components-react/src/index.css +++ b/examples/grid-pro/components-react/src/index.css @@ -1,3 +1,18 @@ +@import "tailwindcss"; + +/* 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; + } + } +} + body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', @@ -6,21 +21,77 @@ body { -moz-osx-font-smoothing: grayscale; } -#root { +.hcg-container { width: 100%; - min-height: 100vh; - padding: 20px; + 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; } -#controls { - margin-top: 20px; - display: flex; - gap: 10px; +.demo-grid .hcg-container .hcg-clear-filter-button:hover:not(:disabled) { + @apply text-slate-900 underline dark:text-slate-50; } -@media (prefers-color-scheme: dark) { - body { - background-color: #121212; - color: #ffffff; +@container hcg (max-width: 800px) { + .hcg-pagination-controls { + @apply justify-center border-r-0 border-l-0 mt-2 mb-2; } } diff --git a/examples/grid-pro/components-react/vite.config.ts b/examples/grid-pro/components-react/vite.config.ts index a28be86..39805a3 100644 --- a/examples/grid-pro/components-react/vite.config.ts +++ b/examples/grid-pro/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: [ { @@ -25,6 +26,7 @@ export default defineConfig({ ] }, server: { + host: true, port: 3002 } }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 196d204..eba9be5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -168,6 +168,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 @@ -177,6 +180,9 @@ importers: '@vitejs/plugin-react': specifier: ^4.2.0 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 From c96cbf5a0b2f73c0d479908adb742755527e0fb6 Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Mon, 21 Sep 2026 12:51:41 +0200 Subject: [PATCH 3/3] Reverted callback. --- packages/grid-lite-react/src/Grid.tsx | 16 ++--- packages/grid-pro-react/src/Grid.tsx | 72 ++++--------------- .../src/hooks/useDeclarativeGridOptions.ts | 9 ++- 3 files changed, 25 insertions(+), 72 deletions(-) diff --git a/packages/grid-lite-react/src/Grid.tsx b/packages/grid-lite-react/src/Grid.tsx index bc45c6a..d14dec8 100644 --- a/packages/grid-lite-react/src/Grid.tsx +++ b/packages/grid-lite-react/src/Grid.tsx @@ -7,7 +7,6 @@ * */ -import { useCallback } from 'react'; import { BaseGrid, useDeclarativeGridOptions @@ -33,17 +32,16 @@ export default function GridLite(props: GridProps) { className, tableClassName } = props; - const build = useCallback( - ( - childOptions: Record, - opts?: Options - ) => buildGridOptions(childOptions, opts, theme, tableClassName), - [theme, tableClassName] - ); const { gridOptions, columnKey } = useDeclarativeGridOptions( children, options, - build + (childOptions, opts) => buildGridOptions( + childOptions, + opts, + theme, + tableClassName + ), + [theme, tableClassName] ); return ( diff --git a/packages/grid-pro-react/src/Grid.tsx b/packages/grid-pro-react/src/Grid.tsx index 6f813a1..4c5d134 100644 --- a/packages/grid-pro-react/src/Grid.tsx +++ b/packages/grid-pro-react/src/Grid.tsx @@ -7,14 +7,16 @@ * */ -import { useCallback } from 'react'; import { BaseGrid, useDeclarativeGridOptions } from '@highcharts/grid-shared-react'; import Grid from '@highcharts/grid-pro/es-modules/masters/grid-pro.src'; import '@highcharts/grid-pro/css/grid-pro.css'; -import type { GridProOptions, GridProProps } from './utils/mappers/grid'; +import type { GridProProps } from './utils/mappers/grid'; +import { + getGridEventPropDeps +} from './utils/mappers/grid'; import { buildGridOptions } from './utils/buildGridOptions'; /** @@ -23,67 +25,17 @@ import { buildGridOptions } from './utils/buildGridOptions'; * Links to Grid.Options */ export default function GridPro(props: GridProProps) { - const { - gridRef, - children, - options, - callback, - className, - gridKey, - theme, - tableClassName, - onBeforeLoad, - onAfterLoad, - onBeforeUpdate, - onAfterUpdate, - onBeforeRedraw, - onAfterRedraw, - onBeforeTreeRowToggle, - onAfterTreeRowToggle, - onBeforeRowPin, - onAfterRowPin - } = props; - const build = useCallback( - ( - childOptions: Record, - opts?: GridProOptions - ) => buildGridOptions(gridKey, childOptions, opts, { - gridKey, - theme, - className, - tableClassName, - onBeforeLoad, - onAfterLoad, - onBeforeUpdate, - onAfterUpdate, - onBeforeRedraw, - onAfterRedraw, - onBeforeTreeRowToggle, - onAfterTreeRowToggle, - onBeforeRowPin, - onAfterRowPin - } as GridProProps), - [ - gridKey, - theme, - className, - tableClassName, - onBeforeLoad, - onAfterLoad, - onBeforeUpdate, - onAfterUpdate, - onBeforeRedraw, - onAfterRedraw, - onBeforeTreeRowToggle, - onAfterTreeRowToggle, - onBeforeRowPin, - onAfterRowPin - ] - ); + const { gridRef, children, options, callback, className } = props; const { gridOptions, columnKey } = useDeclarativeGridOptions( children, options, - build + (childOptions, opts) => buildGridOptions( + props.gridKey, + childOptions, + opts, + props + ), + getGridEventPropDeps(props) ); return ( diff --git a/packages/grid-shared-react/src/hooks/useDeclarativeGridOptions.ts b/packages/grid-shared-react/src/hooks/useDeclarativeGridOptions.ts index 1c4503d..3a3c24b 100644 --- a/packages/grid-shared-react/src/hooks/useDeclarativeGridOptions.ts +++ b/packages/grid-shared-react/src/hooks/useDeclarativeGridOptions.ts @@ -32,14 +32,16 @@ export interface UseDeclarativeGridOptionsFn { ( children: ReactNode | undefined, options: T | undefined, - build: OptionsBuildFn + build: OptionsBuildFn, + buildDeps?: unknown[] ): DeclarativeGridOptionsState; } export const useDeclarativeGridOptions: UseDeclarativeGridOptionsFn = ( children, options, - build + build, + buildDeps = [] ) => { const childOptions = useMemo( () => (children != null ? getChildProps(children) : {}), @@ -51,7 +53,8 @@ export const useDeclarativeGridOptions: UseDeclarativeGridOptionsFn = ( ); const gridOptions = useMemo( () => build(childOptions, options), - [childOptions, options, build] + // eslint-disable-next-line react-hooks/exhaustive-deps + [childOptions, options, ...buildDeps] ); return { gridOptions, columnKey };