From 0546a7ec277fd5de24164307483eac658dbee109 Mon Sep 17 00:00:00 2001 From: Alex Dunae Date: Tue, 16 Jun 2026 11:43:18 -0700 Subject: [PATCH 1/3] Prevent Stratakit grid-level props from being passed to cards --- .../main_2026-06-16-18-43.json | 10 +++++ .../mui/containers/ITwinGrid/ITwinGridMUI.tsx | 9 ++-- .../containers/iModelGrid/IModelGridMUI.tsx | 9 ++-- .../src/mui/utils/stripNonTileProps.test.ts | 44 +++++++++++++++++++ .../src/mui/utils/stripNonTileProps.ts | 24 ++++++++++ 5 files changed, 89 insertions(+), 7 deletions(-) create mode 100644 common/changes/@itwin/imodel-browser-react/main_2026-06-16-18-43.json create mode 100644 packages/modules/imodel-browser/src/mui/utils/stripNonTileProps.test.ts create mode 100644 packages/modules/imodel-browser/src/mui/utils/stripNonTileProps.ts diff --git a/common/changes/@itwin/imodel-browser-react/main_2026-06-16-18-43.json b/common/changes/@itwin/imodel-browser-react/main_2026-06-16-18-43.json new file mode 100644 index 00000000..f814be76 --- /dev/null +++ b/common/changes/@itwin/imodel-browser-react/main_2026-06-16-18-43.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@itwin/imodel-browser-react", + "comment": "Prevent Stratakit grid-level props from being passed to cards", + "type": "patch" + } + ], + "packageName": "@itwin/imodel-browser-react" +} \ No newline at end of file diff --git a/packages/modules/imodel-browser/src/mui/containers/ITwinGrid/ITwinGridMUI.tsx b/packages/modules/imodel-browser/src/mui/containers/ITwinGrid/ITwinGridMUI.tsx index 9f6bea7b..ba2d8a0a 100644 --- a/packages/modules/imodel-browser/src/mui/containers/ITwinGrid/ITwinGridMUI.tsx +++ b/packages/modules/imodel-browser/src/mui/containers/ITwinGrid/ITwinGridMUI.tsx @@ -22,6 +22,7 @@ import { import { BaseCardLoading } from "../../components/baseCard/BaseCardLoading"; import { NoResultsMUI } from "../../components/noResults/NoResultsMUI"; import { type ITwinTableOverridesMUI } from "../../types"; +import { stripNonTileProps } from "../../utils/stripNonTileProps"; import { type ITwinTableMUIStrings, ITwinTableMUI } from "./ITwinTableMUI"; import { type ITwinTilePropsMUI, ITwinTileMUI } from "./ITwinTileMUI"; @@ -296,7 +297,7 @@ type ITwinHookedTileProps = ITwinTilePropsMUI & { }; const noOp = () => ({} as Partial); const ITwinHookedTile = (props: ITwinHookedTileProps) => { - const { useTileState = noOp, ...iTwinTileProps } = props; + const { useTileState = noOp, ...rest } = props; const hookIdentity = React.useRef(useTileState); @@ -306,8 +307,8 @@ const ITwinHookedTile = (props: ITwinHookedTileProps) => { ); } - const tileState = useTileState(props.iTwin, iTwinTileProps); - // gridProps aren't used by ITwinTileMUI but are passed to useIndividualState - const { gridProps, ...tileProps } = props; + const useIndividualStateResult = useTileState(props.iTwin, rest); + const tileProps = stripNonTileProps(rest); + const tileState = stripNonTileProps(useIndividualStateResult); return ; }; diff --git a/packages/modules/imodel-browser/src/mui/containers/iModelGrid/IModelGridMUI.tsx b/packages/modules/imodel-browser/src/mui/containers/iModelGrid/IModelGridMUI.tsx index 12d45c0c..9251c12a 100644 --- a/packages/modules/imodel-browser/src/mui/containers/iModelGrid/IModelGridMUI.tsx +++ b/packages/modules/imodel-browser/src/mui/containers/iModelGrid/IModelGridMUI.tsx @@ -32,6 +32,7 @@ import { import { BaseCardLoading } from "../../components/baseCard/BaseCardLoading"; import { NoResultsMUI as NoResults } from "../../components/noResults/NoResultsMUI"; import { type IModelTableOverridesMUI } from "../../types"; +import { stripNonTileProps } from "../../utils/stripNonTileProps"; import { type IModelTileMUIProps, IModelTileMUI, @@ -453,7 +454,7 @@ type IModelHookedTileProps = IModelTileMUIProps & { const noOp = () => ({} as Partial); const IModelHookedTile = (props: IModelHookedTileProps) => { - const { useTileState = noOp, ...iModelTileProps } = props; + const { useTileState = noOp, ...rest } = props; const hookIdentity = React.useRef(useTileState); @@ -463,9 +464,11 @@ const IModelHookedTile = (props: IModelHookedTileProps) => { ); } - const tileState = useTileState(props.iModel, iModelTileProps); + const useIndividualStateResult = useTileState(props.iModel, rest); + const tileProps = stripNonTileProps(rest); + const tileState = stripNonTileProps(useIndividualStateResult); - return ; + return ; }; function removeFromRecentsAction( diff --git a/packages/modules/imodel-browser/src/mui/utils/stripNonTileProps.test.ts b/packages/modules/imodel-browser/src/mui/utils/stripNonTileProps.test.ts new file mode 100644 index 00000000..1e78833a --- /dev/null +++ b/packages/modules/imodel-browser/src/mui/utils/stripNonTileProps.test.ts @@ -0,0 +1,44 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Bentley Systems, Incorporated. All rights reserved. + * See LICENSE.md in the project root for license terms and full copyright notice. + *--------------------------------------------------------------------------------------------*/ +import { stripNonTileProps } from "./stripNonTileProps"; + +describe("stripNonTileProps", () => { + it("removes gridProps, useTileState, and tileProps while preserving everything else", () => { + const input = { + id: "1", + title: "Tile", + onClick: () => undefined, + gridProps: { foo: "bar" }, + useTileState: () => ({}), + tileProps: { className: "x" }, + }; + + const result = stripNonTileProps(input); + + expect(result).toEqual({ + id: "1", + title: "Tile", + onClick: input.onClick, + }); + expect(result).not.toHaveProperty("gridProps"); + expect(result).not.toHaveProperty("useTileState"); + expect(result).not.toHaveProperty("tileProps"); + }); + + it("returns a new object without mutating the source", () => { + const input = { keep: 1, gridProps: { foo: "bar" } }; + + const result = stripNonTileProps(input); + + expect(result).not.toBe(input); + expect(input).toHaveProperty("gridProps"); + }); + + it("is a no-op when none of the stripped keys are present", () => { + const input = { a: 1, b: "two" }; + + expect(stripNonTileProps(input)).toEqual(input); + }); +}); diff --git a/packages/modules/imodel-browser/src/mui/utils/stripNonTileProps.ts b/packages/modules/imodel-browser/src/mui/utils/stripNonTileProps.ts new file mode 100644 index 00000000..623fcf85 --- /dev/null +++ b/packages/modules/imodel-browser/src/mui/utils/stripNonTileProps.ts @@ -0,0 +1,24 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Bentley Systems, Incorporated. All rights reserved. + * See LICENSE.md in the project root for license terms and full copyright notice. + *--------------------------------------------------------------------------------------------*/ + +/** + * Strip props that may be passed to useIndividualState hooks but should not be + * forwarded to a tile/card component. + */ +export const stripNonTileProps = ( + source: T +): Omit => { + const { + gridProps: _gridProps, + useTileState: _useTileState, + tileProps: _tileProps, + ...stripped + } = source as T & { + gridProps?: unknown; + useTileState?: unknown; + tileProps?: unknown; + }; + return stripped; +}; From 36d2a02ac68b95e3e4b051aa75eb21009897d8b5 Mon Sep 17 00:00:00 2001 From: Alex Dunae Date: Wed, 17 Jun 2026 06:45:48 -0700 Subject: [PATCH 2/3] Apply suggestions from code review Co-authored-by: Todd Southen <48103957+toddsouthenbentley@users.noreply.github.com> --- .../src/mui/containers/ITwinGrid/ITwinGridMUI.tsx | 3 +-- .../src/mui/containers/iModelGrid/IModelGridMUI.tsx | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/modules/imodel-browser/src/mui/containers/ITwinGrid/ITwinGridMUI.tsx b/packages/modules/imodel-browser/src/mui/containers/ITwinGrid/ITwinGridMUI.tsx index 6d30983a..bce76405 100644 --- a/packages/modules/imodel-browser/src/mui/containers/ITwinGrid/ITwinGridMUI.tsx +++ b/packages/modules/imodel-browser/src/mui/containers/ITwinGrid/ITwinGridMUI.tsx @@ -307,8 +307,7 @@ const ITwinHookedTile = (props: ITwinHookedTileProps) => { ); } - const useIndividualStateResult = useTileState(props.iTwin, rest); const tileProps = stripNonTileProps(rest); - const tileState = stripNonTileProps(useIndividualStateResult); + const tileState = stripNonTileProps(useTileState(props.iTwin, rest)); return ; }; diff --git a/packages/modules/imodel-browser/src/mui/containers/iModelGrid/IModelGridMUI.tsx b/packages/modules/imodel-browser/src/mui/containers/iModelGrid/IModelGridMUI.tsx index e7c7365d..d82871cd 100644 --- a/packages/modules/imodel-browser/src/mui/containers/iModelGrid/IModelGridMUI.tsx +++ b/packages/modules/imodel-browser/src/mui/containers/iModelGrid/IModelGridMUI.tsx @@ -464,9 +464,8 @@ const IModelHookedTile = (props: IModelHookedTileProps) => { ); } - const useIndividualStateResult = useTileState(props.iModel, rest); const tileProps = stripNonTileProps(rest); - const tileState = stripNonTileProps(useIndividualStateResult); + const tileState = stripNonTileProps(useTileState(props.iModel, rest);); return ; }; From 393f22842bfb9b0af29eff542cf907c67e575c42 Mon Sep 17 00:00:00 2001 From: Alex Dunae Date: Wed, 17 Jun 2026 10:47:22 -0700 Subject: [PATCH 3/3] merge fix --- .../src/mui/containers/iModelGrid/IModelGridMUI.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/modules/imodel-browser/src/mui/containers/iModelGrid/IModelGridMUI.tsx b/packages/modules/imodel-browser/src/mui/containers/iModelGrid/IModelGridMUI.tsx index d82871cd..b813b43c 100644 --- a/packages/modules/imodel-browser/src/mui/containers/iModelGrid/IModelGridMUI.tsx +++ b/packages/modules/imodel-browser/src/mui/containers/iModelGrid/IModelGridMUI.tsx @@ -465,8 +465,7 @@ const IModelHookedTile = (props: IModelHookedTileProps) => { } const tileProps = stripNonTileProps(rest); - const tileState = stripNonTileProps(useTileState(props.iModel, rest);); - + const tileState = stripNonTileProps(useTileState(props.iModel, rest)); return ; };