diff --git a/packages/graph-explorer/src/core/StateProvider/graphViewLayout.test.ts b/packages/graph-explorer/src/core/StateProvider/graphViewLayout.test.ts index 71c7a1c470..f95affb22b 100644 --- a/packages/graph-explorer/src/core/StateProvider/graphViewLayout.test.ts +++ b/packages/graph-explorer/src/core/StateProvider/graphViewLayout.test.ts @@ -15,6 +15,7 @@ import { import { DEFAULT_SIDEBAR_WIDTH, DEFAULT_TABLE_VIEW_HEIGHT, + MIN_SIDEBAR_WIDTH, } from "./graphViewLayoutDefaults"; const baseLayout: GraphViewLayout = { @@ -167,6 +168,17 @@ describe("useGraphViewSidebar", () => { expect(result.current.sidebarWidth).toBe(DEFAULT_SIDEBAR_WIDTH - 100); }); + it("should clamp sidebar width to the minimum on a large negative delta", () => { + const { result } = renderHookWithState( + () => useGraphViewSidebar(), + stateWithLayout(), + ); + + act(() => result.current.setSidebarWidth(-10000)); + + expect(result.current.sidebarWidth).toBe(MIN_SIDEBAR_WIDTH); + }); + it("should auto-open details when detailsAutoOpenOnSelection is true", () => { const { result } = renderHookWithState( () => useGraphViewSidebar(), diff --git a/packages/graph-explorer/src/core/StateProvider/graphViewLayout.ts b/packages/graph-explorer/src/core/StateProvider/graphViewLayout.ts index 3b95fc0bc4..b7d3981234 100644 --- a/packages/graph-explorer/src/core/StateProvider/graphViewLayout.ts +++ b/packages/graph-explorer/src/core/StateProvider/graphViewLayout.ts @@ -5,6 +5,7 @@ import { useQueryEngine } from "@/connections"; import { DEFAULT_SIDEBAR_WIDTH, DEFAULT_TABLE_VIEW_HEIGHT, + MIN_SIDEBAR_WIDTH, type GraphViewSidebarItem, type ToggleableView, } from "./graphViewLayoutDefaults"; @@ -97,7 +98,10 @@ export function useGraphViewSidebar() { ...prev, sidebar: { ...prev.sidebar, - width: (prev.sidebar?.width ?? DEFAULT_SIDEBAR_WIDTH) + deltaWidth, + width: Math.max( + MIN_SIDEBAR_WIDTH, + (prev.sidebar?.width ?? DEFAULT_SIDEBAR_WIDTH) + deltaWidth, + ), }, })); } diff --git a/packages/graph-explorer/src/core/StateProvider/graphViewLayoutDefaults.ts b/packages/graph-explorer/src/core/StateProvider/graphViewLayoutDefaults.ts index e34dd6804c..a04573fe90 100644 --- a/packages/graph-explorer/src/core/StateProvider/graphViewLayoutDefaults.ts +++ b/packages/graph-explorer/src/core/StateProvider/graphViewLayoutDefaults.ts @@ -36,6 +36,9 @@ export const legacyStylingSidebarItemSchema = z /** Default width for the graph view sidebar in pixels. */ export const DEFAULT_SIDEBAR_WIDTH = 400; +/** Floor for the sidebar width in pixels, so resizing cannot persist a non-positive width. */ +export const MIN_SIDEBAR_WIDTH = 200; + /** * The Graph View Layout as the shared breadcrumb stores it, declared once so the * runtime type and the parser cannot drift apart. It accepts the shapes older diff --git a/packages/graph-explorer/src/modules/SchemaGraph/Sidebar/schemaViewLayout.test.ts b/packages/graph-explorer/src/modules/SchemaGraph/Sidebar/schemaViewLayout.test.ts index 94aab5b9d3..4d1d2e9228 100644 --- a/packages/graph-explorer/src/modules/SchemaGraph/Sidebar/schemaViewLayout.test.ts +++ b/packages/graph-explorer/src/modules/SchemaGraph/Sidebar/schemaViewLayout.test.ts @@ -5,7 +5,10 @@ import { describe, expect, it } from "vitest"; import type { SchemaViewLayout } from "@/core/StateProvider/schemaViewLayoutDefaults"; -import { DEFAULT_SIDEBAR_WIDTH } from "@/core/StateProvider/graphViewLayoutDefaults"; +import { + DEFAULT_SIDEBAR_WIDTH, + MIN_SIDEBAR_WIDTH, +} from "@/core/StateProvider/graphViewLayoutDefaults"; import { DbState, renderHookWithState } from "@/utils/testing"; import { useSchemaViewSidebar } from "./schemaViewLayout"; @@ -94,6 +97,17 @@ describe("useSchemaViewSidebar", () => { expect(result.current.sidebarWidth).toBe(DEFAULT_SIDEBAR_WIDTH - 50); }); + it("should clamp sidebar width to the minimum on a large negative delta", () => { + const { result } = renderHookWithState( + () => useSchemaViewSidebar(), + stateWithLayout(), + ); + + act(() => result.current.setSidebarWidth(-10000)); + + expect(result.current.sidebarWidth).toBe(MIN_SIDEBAR_WIDTH); + }); + it("should auto-open details tab when enabled and selection changes", () => { const { result } = renderHookWithState( () => useSchemaViewSidebar(), diff --git a/packages/graph-explorer/src/modules/SchemaGraph/Sidebar/schemaViewLayout.ts b/packages/graph-explorer/src/modules/SchemaGraph/Sidebar/schemaViewLayout.ts index 77d62a08c1..ee137623ee 100644 --- a/packages/graph-explorer/src/modules/SchemaGraph/Sidebar/schemaViewLayout.ts +++ b/packages/graph-explorer/src/modules/SchemaGraph/Sidebar/schemaViewLayout.ts @@ -2,7 +2,10 @@ import { useAtom } from "jotai"; import type { SchemaViewSidebarItem } from "@/core/StateProvider/schemaViewLayoutDefaults"; -import { DEFAULT_SIDEBAR_WIDTH } from "@/core/StateProvider/graphViewLayoutDefaults"; +import { + DEFAULT_SIDEBAR_WIDTH, + MIN_SIDEBAR_WIDTH, +} from "@/core/StateProvider/graphViewLayoutDefaults"; import { schemaViewLayoutAtom } from "@/core/StateProvider/storageAtoms"; export type { SchemaViewSidebarItem } from "@/core/StateProvider/schemaViewLayoutDefaults"; @@ -30,7 +33,10 @@ export function useSchemaViewSidebar() { ...prev, sidebar: { ...prev.sidebar, - width: (prev.sidebar?.width ?? DEFAULT_SIDEBAR_WIDTH) + deltaWidth, + width: Math.max( + MIN_SIDEBAR_WIDTH, + (prev.sidebar?.width ?? DEFAULT_SIDEBAR_WIDTH) + deltaWidth, + ), }, })); }