From 2d65ed092c30e240c093e9f456d6df2bbe09ff10 Mon Sep 17 00:00:00 2001 From: SB-venkatyadavilli Date: Fri, 7 Nov 2025 15:24:52 +0530 Subject: [PATCH 01/13] fix: Handled Active Eelement of Table of Content --- .../TableOfContents/TableOfContents.tsx | 133 +++++++++++++++--- .../src/components/TableOfContents/types.ts | 10 ++ 2 files changed, 122 insertions(+), 21 deletions(-) diff --git a/packages/elements-core/src/components/TableOfContents/TableOfContents.tsx b/packages/elements-core/src/components/TableOfContents/TableOfContents.tsx index e0cfd7485..c72d6a8e8 100644 --- a/packages/elements-core/src/components/TableOfContents/TableOfContents.tsx +++ b/packages/elements-core/src/components/TableOfContents/TableOfContents.tsx @@ -1,6 +1,7 @@ import { Box, Flex, Icon, ITextColorProps } from '@stoplight/mosaic'; import { HttpMethod, NodeType } from '@stoplight/types'; import * as React from 'react'; +import { createContext, ReactNode, useState } from 'react'; import { useFirstRender } from '../../hooks/useFirstRender'; import { resolveRelativeLink } from '../../utils/string'; @@ -15,6 +16,7 @@ import { } from './constants'; import { CustomLinkComponent, + GroupContextType, TableOfContentsDivider, TableOfContentsGroup, TableOfContentsGroupItem, @@ -37,6 +39,28 @@ const ActiveIdContext = React.createContext(undefined); const LinkContext = React.createContext(undefined); LinkContext.displayName = 'LinkContext'; +// Create the context with a default undefined value +export const GroupContext = createContext(undefined); + +// Provider component +const GroupProvider: React.FC<{ children: ReactNode }> = ({ children }) => { + const [lastActiveGroupIndex, setLastActiveGroupIndex] = useState(null); // default value 0 + const [lastActiveGroupId, setLastActiveGroupId] = useState(null); + + return ( + + {children} + + ); +}; export const TableOfContents = React.memo( ({ tree, @@ -47,6 +71,50 @@ export const TableOfContents = React.memo( isInResponsiveMode = false, onLinkClick, }) => { + const addGroupIndex = React.useCallback( + (arr: any[], groupId: number | null, isHttpService: boolean, groupIndex: number | null = null): any[] => { + return arr.map((item, key) => { + // Early return for title-only items + if (Object.keys(item).length === 1 && item.title) { + return item; + } + + const isHttpServiceItem = item?.type === 'http_service'; + const isGroupItem = !item?.type && item?.items; + let GroupId = groupId; + if (!GroupId) { + GroupId = key; + } + // if (Object.keys(item).length === 2 && item?.items) { + // GroupId = key; + // } + const shouldUseHttpService = !isGroupItem && (isHttpService || isHttpServiceItem); + const currentGroupIndex = isHttpServiceItem ? key : groupIndex ?? key; + + let newItem = { + ...item, + groupIndex: shouldUseHttpService ? currentGroupIndex : key, + }; + if (GroupId !== null) { + newItem = { + ...newItem, + groupId: GroupId, + }; + } + + // Process items array if it exists + if (Array.isArray(item.items)) { + newItem.items = addGroupIndex(item.items, GroupId, shouldUseHttpService, currentGroupIndex); + } + + return newItem; + }); + }, + [], + ); + + // Example usage: + const updatedTree = addGroupIndex(tree, null, false); const container = React.useRef(null); const child = React.useRef(null); const firstRender = useFirstRender(); @@ -76,24 +144,26 @@ export const TableOfContents = React.memo( - - {tree.map((item, key) => { - if (isDivider(item)) { - return ; - } - - return ( - - ); - })} - + + + {updatedTree.map((item, key: number) => { + if (isDivider(item)) { + return ; + } + + return ( + + ); + })} + + @@ -198,8 +268,14 @@ const Group = React.memo<{ onLinkClick?(): void; }>(({ depth, item, maxDepthOpenByDefault, isInResponsiveMode, onLinkClick = () => {} }) => { const activeId = React.useContext(ActiveIdContext); + const groupContext = React.useContext(GroupContext); const [isOpen, setIsOpen] = React.useState(() => isGroupOpenByDefault(depth, item, activeId, maxDepthOpenByDefault)); - const hasActive = !!activeId && hasActiveItem(item.items, activeId); + if (groupContext?.lastActiveGroupIndex === null && item?.groupIndex !== undefined) { + groupContext.setLastActiveGroupIndex(item.groupId); + } + + const hasActive = + groupContext?.lastActiveGroupId === item?.groupId && !!activeId && hasActiveItem(item.items, activeId); // If maxDepthOpenByDefault changes, we want to update all the isOpen states (used in live preview mode) React.useEffect(() => { @@ -352,7 +428,15 @@ const Node = React.memo<{ onLinkClick?(): void; }>(({ item, depth, meta, showAsActive, isInResponsiveMode, onClick, onLinkClick = () => {} }) => { const activeId = React.useContext(ActiveIdContext); - const isActive = activeId === item.slug || activeId === item.id; + const groupContext = React.useContext(GroupContext); + + const groupIndex = item.groupIndex; + + const check1 = groupIndex === groupContext?.lastActiveGroupIndex; + const check2 = activeId === item.slug || activeId === item.id; + + // const isActive = nodeTitle === lastVisitedNodeTitle && (activeId === item.slug || activeId === item.id); + const isActive = check1 && check2; const LinkComponent = React.useContext(LinkContext); const handleClick = (e: React.MouseEvent) => { @@ -361,6 +445,13 @@ const Node = React.memo<{ e.stopPropagation(); e.preventDefault(); } else { + groupContext?.setLastActiveGroupIndex(item.groupIndex || 0); + if (item?.groupId !== undefined) { + groupContext?.setLastActiveGroupId(item.groupId); + } else { + groupContext?.setLastActiveGroupId(null); + } + onLinkClick(); } @@ -390,7 +481,7 @@ const Node = React.memo<{ } meta={meta} isInResponsiveMode={isInResponsiveMode} - onClick={handleClick} + onClick={e => handleClick(e, item)} /> ); diff --git a/packages/elements-core/src/components/TableOfContents/types.ts b/packages/elements-core/src/components/TableOfContents/types.ts index e3c69e15d..b70e4ef3b 100644 --- a/packages/elements-core/src/components/TableOfContents/types.ts +++ b/packages/elements-core/src/components/TableOfContents/types.ts @@ -28,8 +28,10 @@ export type TableOfContentsGroupItem = export type TableOfContentsGroup = { title: string; + groupId: number; items: TableOfContentsGroupItem[]; itemsType?: 'article' | 'http_operation' | 'http_webhook' | 'model'; + groupIndex?: number; }; export type TableOfContentsExternalLink = { @@ -46,6 +48,14 @@ export type TableOfContentsNode< type: T; meta: string; version?: string; + groupIndex?: number; + groupId?: number; }; export type TableOfContentsNodeGroup = TableOfContentsNode<'http_service'> & TableOfContentsGroup; +export type GroupContextType = { + lastActiveGroupIndex: number | null; + lastActiveGroupId: number | null; + setLastActiveGroupIndex: React.Dispatch>; + setLastActiveGroupId: React.Dispatch>; +}; From 28e54927a4f742bd6b10208e358adacbc1ebd3b2 Mon Sep 17 00:00:00 2001 From: SB-venkatyadavilli Date: Mon, 10 Nov 2025 14:41:22 +0530 Subject: [PATCH 02/13] fix: handled initial values of Context API. --- .../TableOfContents/TableOfContents.tsx | 127 +++++++++++------- .../TableOfContents/TableOfContents.tsx | 48 +++++-- 2 files changed, 120 insertions(+), 55 deletions(-) diff --git a/packages/elements-core/src/components/TableOfContents/TableOfContents.tsx b/packages/elements-core/src/components/TableOfContents/TableOfContents.tsx index c72d6a8e8..d79cb7b1a 100644 --- a/packages/elements-core/src/components/TableOfContents/TableOfContents.tsx +++ b/packages/elements-core/src/components/TableOfContents/TableOfContents.tsx @@ -1,8 +1,8 @@ import { Box, Flex, Icon, ITextColorProps } from '@stoplight/mosaic'; import { HttpMethod, NodeType } from '@stoplight/types'; import * as React from 'react'; -import { createContext, ReactNode, useState } from 'react'; +import { GroupContext } from '../../../../elements-dev-portal/src/components/TableOfContents/TableOfContents'; import { useFirstRender } from '../../hooks/useFirstRender'; import { resolveRelativeLink } from '../../utils/string'; import { VersionBadge } from '../Docs/HttpOperation/Badges'; @@ -16,7 +16,6 @@ import { } from './constants'; import { CustomLinkComponent, - GroupContextType, TableOfContentsDivider, TableOfContentsGroup, TableOfContentsGroupItem, @@ -40,27 +39,27 @@ const LinkContext = React.createContext(undefin LinkContext.displayName = 'LinkContext'; // Create the context with a default undefined value -export const GroupContext = createContext(undefined); +// export const GroupContext = createContext(undefined); // Provider component -const GroupProvider: React.FC<{ children: ReactNode }> = ({ children }) => { - const [lastActiveGroupIndex, setLastActiveGroupIndex] = useState(null); // default value 0 - const [lastActiveGroupId, setLastActiveGroupId] = useState(null); - - return ( - - {children} - - ); -}; +// const GroupProvider: React.FC<{ children: ReactNode }> = ({ children }) => { +// const [lastActiveGroupIndex, setLastActiveGroupIndex] = useState(null); // default value 0 +// const [lastActiveGroupId, setLastActiveGroupId] = useState(null); + +// return ( +// +// {children} +// +// ); +// }; export const TableOfContents = React.memo( ({ tree, @@ -71,6 +70,7 @@ export const TableOfContents = React.memo( isInResponsiveMode = false, onLinkClick, }) => { + const groupContext = React.useContext(GroupContext); const addGroupIndex = React.useCallback( (arr: any[], groupId: number | null, isHttpService: boolean, groupIndex: number | null = null): any[] => { return arr.map((item, key) => { @@ -112,9 +112,44 @@ export const TableOfContents = React.memo( }, [], ); + const getInitialValues = React.useCallback( + (tree: any[]): boolean => { + for (const item of tree) { + if (item?.items && item.type === 'http_service') { + if (item?.groupId != null) { + groupContext?.setLastActiveGroupId(item.groupId); + } + + if (item?.groupIndex != null) { + groupContext?.setLastActiveGroupIndex(item.groupIndex); + } + + return true; + } else if (item?.items) { + const found = getInitialValues(item.items); + if (found) return true; + } else if (Object.keys(item).length !== 1 && !groupContext?.lastActiveGroupIndex) { + if (item?.groupId != null) { + groupContext?.setLastActiveGroupId(item.groupId); + } + + if (item?.groupIndex != null) { + groupContext?.setLastActiveGroupIndex(item.groupIndex); + } + + return true; + } + } - // Example usage: + return false; + }, + [groupContext], + ); const updatedTree = addGroupIndex(tree, null, false); + React.useEffect(() => { + getInitialValues(updatedTree); + }, [getInitialValues, updatedTree]); + const container = React.useRef(null); const child = React.useRef(null); const firstRender = useFirstRender(); @@ -144,26 +179,24 @@ export const TableOfContents = React.memo( - - - {updatedTree.map((item, key: number) => { - if (isDivider(item)) { - return ; - } - - return ( - - ); - })} - - + + {updatedTree.map((item, key: number) => { + if (isDivider(item)) { + return ; + } + + return ( + + ); + })} + @@ -200,6 +233,8 @@ const GroupItem = React.memo<{ maxDepthOpenByDefault?: number; onLinkClick?(): void; }>(({ item, depth, maxDepthOpenByDefault, isInResponsiveMode, onLinkClick }) => { + const groupContext = React.useContext(GroupContext); + if (isExternalLink(item)) { return ( @@ -270,9 +305,9 @@ const Group = React.memo<{ const activeId = React.useContext(ActiveIdContext); const groupContext = React.useContext(GroupContext); const [isOpen, setIsOpen] = React.useState(() => isGroupOpenByDefault(depth, item, activeId, maxDepthOpenByDefault)); - if (groupContext?.lastActiveGroupIndex === null && item?.groupIndex !== undefined) { - groupContext.setLastActiveGroupIndex(item.groupId); - } + // if (groupContext?.lastActiveGroupIndex === null && item?.groupIndex !== undefined) { + // groupContext.setLastActiveGroupIndex(item.groupId); + // } const hasActive = groupContext?.lastActiveGroupId === item?.groupId && !!activeId && hasActiveItem(item.items, activeId); @@ -439,7 +474,7 @@ const Node = React.memo<{ const isActive = check1 && check2; const LinkComponent = React.useContext(LinkContext); - const handleClick = (e: React.MouseEvent) => { + const handleClick = (e: React.MouseEvent, item: any) => { if (isActive) { // Don't trigger link click when we're active e.stopPropagation(); diff --git a/packages/elements-dev-portal/src/components/TableOfContents/TableOfContents.tsx b/packages/elements-dev-portal/src/components/TableOfContents/TableOfContents.tsx index db371fa56..0782166df 100644 --- a/packages/elements-dev-portal/src/components/TableOfContents/TableOfContents.tsx +++ b/packages/elements-dev-portal/src/components/TableOfContents/TableOfContents.tsx @@ -17,6 +17,33 @@ export type TableOfContentsProps = BoxProps<'div'> & { isInResponsiveMode?: boolean; onLinkClick?(): void; }; +export type GroupContextType = { + lastActiveGroupIndex: number | null; + lastActiveGroupId: number | null; + setLastActiveGroupIndex: React.Dispatch>; + setLastActiveGroupId: React.Dispatch>; +}; +export const GroupContext = React.createContext(undefined); + +// Provider component +const GroupProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const [lastActiveGroupIndex, setLastActiveGroupIndex] = React.useState(null); // default value 0 + const [lastActiveGroupId, setLastActiveGroupId] = React.useState(null); + + return ( + + {children} + + ); +}; export const TableOfContents = ({ tableOfContents, @@ -28,18 +55,21 @@ export const TableOfContents = ({ onLinkClick, ...boxProps }: TableOfContentsProps) => { + console.log('from packages/elements-dev-portal/src/components/TableOfContents/TableOfContents.tsx'); return ( - + + + {tableOfContents.hide_powered_by ? null : ( From 603f1fe3beb76540e5974fb960f7f02daf7d6873 Mon Sep 17 00:00:00 2001 From: SB-venkatyadavilli Date: Tue, 11 Nov 2025 14:17:38 +0530 Subject: [PATCH 03/13] fix:moved ContextAPI provider from dev-portal to elements-core --- .../TableOfContents/TableOfContents.tsx | 114 +++++++++--------- .../TableOfContents/TableOfContents.tsx | 47 ++------ 2 files changed, 69 insertions(+), 92 deletions(-) diff --git a/packages/elements-core/src/components/TableOfContents/TableOfContents.tsx b/packages/elements-core/src/components/TableOfContents/TableOfContents.tsx index d79cb7b1a..1cfa22650 100644 --- a/packages/elements-core/src/components/TableOfContents/TableOfContents.tsx +++ b/packages/elements-core/src/components/TableOfContents/TableOfContents.tsx @@ -1,8 +1,8 @@ import { Box, Flex, Icon, ITextColorProps } from '@stoplight/mosaic'; import { HttpMethod, NodeType } from '@stoplight/types'; import * as React from 'react'; +import { createContext, useState } from 'react'; -import { GroupContext } from '../../../../elements-dev-portal/src/components/TableOfContents/TableOfContents'; import { useFirstRender } from '../../hooks/useFirstRender'; import { resolveRelativeLink } from '../../utils/string'; import { VersionBadge } from '../Docs/HttpOperation/Badges'; @@ -16,6 +16,7 @@ import { } from './constants'; import { CustomLinkComponent, + GroupContextType, TableOfContentsDivider, TableOfContentsGroup, TableOfContentsGroupItem, @@ -39,27 +40,27 @@ const LinkContext = React.createContext(undefin LinkContext.displayName = 'LinkContext'; // Create the context with a default undefined value -// export const GroupContext = createContext(undefined); +export const GroupContext = createContext(undefined); // Provider component -// const GroupProvider: React.FC<{ children: ReactNode }> = ({ children }) => { -// const [lastActiveGroupIndex, setLastActiveGroupIndex] = useState(null); // default value 0 -// const [lastActiveGroupId, setLastActiveGroupId] = useState(null); - -// return ( -// -// {children} -// -// ); -// }; +const GroupProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const [lastActiveGroupIndex, setLastActiveGroupIndex] = useState(null); // default value 0 + const [lastActiveGroupId, setLastActiveGroupId] = useState(null); + + return ( + + {children} + + ); +}; export const TableOfContents = React.memo( ({ tree, @@ -75,32 +76,32 @@ export const TableOfContents = React.memo( (arr: any[], groupId: number | null, isHttpService: boolean, groupIndex: number | null = null): any[] => { return arr.map((item, key) => { // Early return for title-only items - if (Object.keys(item).length === 1 && item.title) { + + if (isDivider(item) || isExternalLink(item)) { return item; } const isHttpServiceItem = item?.type === 'http_service'; - const isGroupItem = !item?.type && item?.items; + const isGroupItem = !item?.type && item?.items && !('itemsType' in item); let GroupId = groupId; - if (!GroupId) { + if (GroupId === null) { GroupId = key; } + let GroupIndex = groupIndex; + if (GroupIndex === null) { + GroupIndex = key; + } // if (Object.keys(item).length === 2 && item?.items) { // GroupId = key; // } const shouldUseHttpService = !isGroupItem && (isHttpService || isHttpServiceItem); - const currentGroupIndex = isHttpServiceItem ? key : groupIndex ?? key; + const currentGroupIndex = isHttpServiceItem ? key : GroupIndex; let newItem = { ...item, groupIndex: shouldUseHttpService ? currentGroupIndex : key, + groupId: GroupId, }; - if (GroupId !== null) { - newItem = { - ...newItem, - groupId: GroupId, - }; - } // Process items array if it exists if (Array.isArray(item.items)) { @@ -112,6 +113,7 @@ export const TableOfContents = React.memo( }, [], ); + const getInitialValues = React.useCallback( (tree: any[]): boolean => { for (const item of tree) { @@ -180,22 +182,24 @@ export const TableOfContents = React.memo( - {updatedTree.map((item, key: number) => { - if (isDivider(item)) { - return ; - } - - return ( - - ); - })} + + {updatedTree.map((item, key: number) => { + if (isDivider(item)) { + return ; + } + + return ( + + ); + })} + @@ -233,7 +237,7 @@ const GroupItem = React.memo<{ maxDepthOpenByDefault?: number; onLinkClick?(): void; }>(({ item, depth, maxDepthOpenByDefault, isInResponsiveMode, onLinkClick }) => { - const groupContext = React.useContext(GroupContext); + // const groupContext = React.useContext(GroupContext); if (isExternalLink(item)) { return ( @@ -466,8 +470,9 @@ const Node = React.memo<{ const groupContext = React.useContext(GroupContext); const groupIndex = item.groupIndex; + const groupId = item.groupId; - const check1 = groupIndex === groupContext?.lastActiveGroupIndex; + const check1 = groupIndex === groupContext?.lastActiveGroupIndex && groupId === groupContext?.lastActiveGroupId; const check2 = activeId === item.slug || activeId === item.id; // const isActive = nodeTitle === lastVisitedNodeTitle && (activeId === item.slug || activeId === item.id); @@ -480,12 +485,13 @@ const Node = React.memo<{ e.stopPropagation(); e.preventDefault(); } else { - groupContext?.setLastActiveGroupIndex(item.groupIndex || 0); - if (item?.groupId !== undefined) { - groupContext?.setLastActiveGroupId(item.groupId); - } else { - groupContext?.setLastActiveGroupId(null); - } + groupContext?.setLastActiveGroupIndex(item.groupIndex); + groupContext?.setLastActiveGroupId(item.groupId); + // if (item?.groupId !== undefined) { + // groupContext?.setLastActiveGroupId(item.groupId); + // } else { + // groupContext?.setLastActiveGroupId(null); + // } onLinkClick(); } diff --git a/packages/elements-dev-portal/src/components/TableOfContents/TableOfContents.tsx b/packages/elements-dev-portal/src/components/TableOfContents/TableOfContents.tsx index 0782166df..ce7e4d581 100644 --- a/packages/elements-dev-portal/src/components/TableOfContents/TableOfContents.tsx +++ b/packages/elements-dev-portal/src/components/TableOfContents/TableOfContents.tsx @@ -17,33 +17,6 @@ export type TableOfContentsProps = BoxProps<'div'> & { isInResponsiveMode?: boolean; onLinkClick?(): void; }; -export type GroupContextType = { - lastActiveGroupIndex: number | null; - lastActiveGroupId: number | null; - setLastActiveGroupIndex: React.Dispatch>; - setLastActiveGroupId: React.Dispatch>; -}; -export const GroupContext = React.createContext(undefined); - -// Provider component -const GroupProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { - const [lastActiveGroupIndex, setLastActiveGroupIndex] = React.useState(null); // default value 0 - const [lastActiveGroupId, setLastActiveGroupId] = React.useState(null); - - return ( - - {children} - - ); -}; export const TableOfContents = ({ tableOfContents, @@ -59,17 +32,15 @@ export const TableOfContents = ({ return ( - - - + {tableOfContents.hide_powered_by ? null : ( From 80666884a45f6fc3f92ef47ff8ea2646ea70c78f Mon Sep 17 00:00:00 2001 From: SB-venkatyadavilli Date: Tue, 11 Nov 2025 23:50:15 +0530 Subject: [PATCH 04/13] fix: handled initial values of contextAPI & implemented isActiveGroup Functionality --- .../TableOfContents/TableOfContents.tsx | 134 +++++++++++++----- .../src/components/TableOfContents/types.ts | 6 +- 2 files changed, 100 insertions(+), 40 deletions(-) diff --git a/packages/elements-core/src/components/TableOfContents/TableOfContents.tsx b/packages/elements-core/src/components/TableOfContents/TableOfContents.tsx index 1cfa22650..49a928776 100644 --- a/packages/elements-core/src/components/TableOfContents/TableOfContents.tsx +++ b/packages/elements-core/src/components/TableOfContents/TableOfContents.tsx @@ -26,7 +26,6 @@ import { } from './types'; import { getHtmlIdFromItemId, - hasActiveItem, isDivider, isExternalLink, isGroup, @@ -91,9 +90,6 @@ export const TableOfContents = React.memo( if (GroupIndex === null) { GroupIndex = key; } - // if (Object.keys(item).length === 2 && item?.items) { - // GroupId = key; - // } const shouldUseHttpService = !isGroupItem && (isHttpService || isHttpServiceItem); const currentGroupIndex = isHttpServiceItem ? key : GroupIndex; @@ -148,9 +144,6 @@ export const TableOfContents = React.memo( [groupContext], ); const updatedTree = addGroupIndex(tree, null, false); - React.useEffect(() => { - getInitialValues(updatedTree); - }, [getInitialValues, updatedTree]); const container = React.useRef(null); const child = React.useRef(null); @@ -183,22 +176,24 @@ export const TableOfContents = React.memo( - {updatedTree.map((item, key: number) => { - if (isDivider(item)) { - return ; - } - - return ( - - ); - })} + + {updatedTree.map((item, key: number) => { + if (isDivider(item)) { + return ; + } + + return ( + + ); + })} + @@ -230,6 +225,51 @@ const Divider = React.memo<{ }); Divider.displayName = 'Divider'; +const TOCContainer = React.memo<{ + updatedTree: TableOfContentsGroupItem[]; + children: React.ReactNode; +}>(({ children, updatedTree }) => { + const groupContext = React.useContext(GroupContext); + const getInitialValues = React.useCallback( + (tree: any[]): boolean => { + for (const item of tree) { + if (item?.items && item.type === 'http_service') { + if (item?.groupId != null) { + groupContext?.setLastActiveGroupId(item.groupId); + } + + if (item?.groupIndex != null) { + groupContext?.setLastActiveGroupIndex(item.groupIndex); + } + + return true; + } else if (item?.items) { + const found = getInitialValues(item.items); + if (found) return true; + } else if (Object.keys(item).length !== 1 && !groupContext?.lastActiveGroupIndex) { + if (item?.groupId != null) { + groupContext?.setLastActiveGroupId(item.groupId); + } + + if (item?.groupIndex != null) { + groupContext?.setLastActiveGroupIndex(item.groupIndex); + } + + return true; + } + } + + return false; + }, + [groupContext], + ); + React.useEffect(() => { + getInitialValues(updatedTree); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + return {children}; +}); +TOCContainer.displayName = 'TOCContainer'; const GroupItem = React.memo<{ depth: number; item: TableOfContentsGroupItem; @@ -237,8 +277,6 @@ const GroupItem = React.memo<{ maxDepthOpenByDefault?: number; onLinkClick?(): void; }>(({ item, depth, maxDepthOpenByDefault, isInResponsiveMode, onLinkClick }) => { - // const groupContext = React.useContext(GroupContext); - if (isExternalLink(item)) { return ( @@ -309,12 +347,40 @@ const Group = React.memo<{ const activeId = React.useContext(ActiveIdContext); const groupContext = React.useContext(GroupContext); const [isOpen, setIsOpen] = React.useState(() => isGroupOpenByDefault(depth, item, activeId, maxDepthOpenByDefault)); - // if (groupContext?.lastActiveGroupIndex === null && item?.groupIndex !== undefined) { - // groupContext.setLastActiveGroupIndex(item.groupId); - // } + function isActiveGroup( + items: any[], + activeId: string | undefined, + contextId: number | undefined | null, + contextIndex: number | undefined | null, + ): boolean { + for (const element of items) { + if (!('items' in element)) { + if ('slug' in element || 'id' in element) { + if ( + !!activeId && + (activeId === element.slug || activeId === element.id) && + contextId === element.groupId && + contextIndex === element.groupIndex + ) { + return true; + } + } + } else if (Array.isArray(element.items)) { + const found = isActiveGroup(element.items, activeId, contextId, contextIndex); + if (found) { + return true; + } + } + } + return false; + } - const hasActive = - groupContext?.lastActiveGroupId === item?.groupId && !!activeId && hasActiveItem(item.items, activeId); + const hasActive = isActiveGroup( + item.items, + activeId, + groupContext?.lastActiveGroupId, + groupContext?.lastActiveGroupIndex, + ); // If maxDepthOpenByDefault changes, we want to update all the isOpen states (used in live preview mode) React.useEffect(() => { @@ -333,6 +399,7 @@ const Group = React.memo<{ }, [hasActive]); const handleClick = (e: React.MouseEvent, forceOpen?: boolean) => { + onLinkClick(); setIsOpen(forceOpen ? true : !isOpen); }; @@ -474,8 +541,6 @@ const Node = React.memo<{ const check1 = groupIndex === groupContext?.lastActiveGroupIndex && groupId === groupContext?.lastActiveGroupId; const check2 = activeId === item.slug || activeId === item.id; - - // const isActive = nodeTitle === lastVisitedNodeTitle && (activeId === item.slug || activeId === item.id); const isActive = check1 && check2; const LinkComponent = React.useContext(LinkContext); @@ -487,11 +552,6 @@ const Node = React.memo<{ } else { groupContext?.setLastActiveGroupIndex(item.groupIndex); groupContext?.setLastActiveGroupId(item.groupId); - // if (item?.groupId !== undefined) { - // groupContext?.setLastActiveGroupId(item.groupId); - // } else { - // groupContext?.setLastActiveGroupId(null); - // } onLinkClick(); } diff --git a/packages/elements-core/src/components/TableOfContents/types.ts b/packages/elements-core/src/components/TableOfContents/types.ts index b70e4ef3b..7e2574f37 100644 --- a/packages/elements-core/src/components/TableOfContents/types.ts +++ b/packages/elements-core/src/components/TableOfContents/types.ts @@ -31,7 +31,7 @@ export type TableOfContentsGroup = { groupId: number; items: TableOfContentsGroupItem[]; itemsType?: 'article' | 'http_operation' | 'http_webhook' | 'model'; - groupIndex?: number; + groupIndex: number; }; export type TableOfContentsExternalLink = { @@ -48,8 +48,8 @@ export type TableOfContentsNode< type: T; meta: string; version?: string; - groupIndex?: number; - groupId?: number; + groupIndex: number; + groupId: number; }; export type TableOfContentsNodeGroup = TableOfContentsNode<'http_service'> & TableOfContentsGroup; From d083d55e91bd567b323938b303e7b847e32fddd8 Mon Sep 17 00:00:00 2001 From: SB-venkatyadavilli Date: Wed, 12 Nov 2025 14:00:52 +0530 Subject: [PATCH 05/13] fix: updated the Unit test cases --- .../TableOfContents/TableOfContents.spec.tsx | 30 +++++++++++++++++ .../components/API/__tests__/utils.test.ts | 6 ++++ packages/elements/src/components/API/utils.ts | 32 ++++++++++--------- 3 files changed, 53 insertions(+), 15 deletions(-) diff --git a/packages/elements-core/src/components/TableOfContents/TableOfContents.spec.tsx b/packages/elements-core/src/components/TableOfContents/TableOfContents.spec.tsx index 284e0de59..12208371f 100644 --- a/packages/elements-core/src/components/TableOfContents/TableOfContents.spec.tsx +++ b/packages/elements-core/src/components/TableOfContents/TableOfContents.spec.tsx @@ -27,6 +27,8 @@ describe('TableOfContents', () => { slug: 'target', type: 'article', meta: '', + groupId: 0, + groupIndex: 0, }, ], }, @@ -55,6 +57,8 @@ describe('TableOfContents', () => { slug: 'target', type: 'article', meta: '', + groupId: 0, + groupIndex: 0, }, ], }, @@ -87,8 +91,12 @@ describe('TableOfContents', () => { slug: 'target', type: 'article', meta: '', + groupId: 0, + groupIndex: 0, }, ], + groupId: 0, + groupIndex: 0, }, ], }, @@ -119,6 +127,8 @@ describe('TableOfContents', () => { slug: 'target', type: 'article', meta: '', + groupId: 0, + groupIndex: 0, }, ], }, @@ -154,6 +164,8 @@ describe('TableOfContents', () => { slug: 'target', type: 'article', meta: '', + groupId: 0, + groupIndex: 0, }, ], }, @@ -191,6 +203,8 @@ describe('TableOfContents', () => { items: [], meta: '', version: '2', + groupId: 0, + groupIndex: 0, }, { id: 'def', @@ -199,6 +213,8 @@ describe('TableOfContents', () => { type: 'model', meta: '', version: '1.0.1', + groupId: 0, + groupIndex: 0, }, { id: 'ghi', @@ -207,6 +223,8 @@ describe('TableOfContents', () => { type: 'http_operation', meta: 'get', version: '1.0.2', + groupId: 0, + groupIndex: 0, }, ], }, @@ -238,6 +256,8 @@ describe('utils', () => { type: 'article', slug: 'abc-doc', meta: '', + groupId: 0, + groupIndex: 0, }, { id: 'targetId', @@ -245,6 +265,8 @@ describe('utils', () => { slug: 'target', type: 'article', meta: '', + groupId: 0, + groupIndex: 0, }, ], }, @@ -258,6 +280,8 @@ describe('utils', () => { type: 'article', slug: 'abc-doc', meta: '', + groupId: 0, + groupIndex: 0, }); }); @@ -276,6 +300,8 @@ describe('utils', () => { slug: 'def-get-todo', type: 'http_operation', meta: 'get', + groupId: 0, + groupIndex: 0, }, { id: 'ghi', @@ -283,6 +309,8 @@ describe('utils', () => { slug: 'ghi-add-todo', type: 'http_operation', meta: 'post', + groupId: 0, + groupIndex: 0, }, ], }, @@ -295,6 +323,8 @@ describe('utils', () => { slug: 'def-get-todo', type: 'http_operation', meta: 'get', + groupId: 0, + groupIndex: 0, }); }); }); diff --git a/packages/elements/src/components/API/__tests__/utils.test.ts b/packages/elements/src/components/API/__tests__/utils.test.ts index d32b67dfd..9366bf26a 100644 --- a/packages/elements/src/components/API/__tests__/utils.test.ts +++ b/packages/elements/src/components/API/__tests__/utils.test.ts @@ -898,6 +898,8 @@ describe.each([ slug: `/${pathProp}/something/get`, title: '/something', type: nodeType, + groupIndex: 0, + groupId: 0, }, ], }, @@ -979,6 +981,8 @@ describe.each([ title: 'a', type: 'model', meta: '', + groupId: 0, + groupIndex: 0, }, ], }, @@ -1042,6 +1046,8 @@ describe.each([ slug: `/${pathProp}/something-else/post`, title: '/something-else', type: nodeType, + groupIndex: 0, + groupId: 0, }, ], }, diff --git a/packages/elements/src/components/API/utils.ts b/packages/elements/src/components/API/utils.ts index 66d8d9e0b..3f94d363c 100644 --- a/packages/elements/src/components/API/utils.ts +++ b/packages/elements/src/components/API/utils.ts @@ -161,9 +161,8 @@ const addTagGroupsToTree = ( ) => { // Show ungrouped nodes above tag groups ungrouped.forEach(node => { - if (hideInternal && isInternal(node)) { - return; - } + if (hideInternal && isInternal(node)) return; + tree.push({ id: node.uri, slug: node.uri, @@ -174,18 +173,21 @@ const addTagGroupsToTree = ( }); groups.forEach(group => { - const items = group.items.flatMap(node => { - if (hideInternal && isInternal(node)) { - return []; - } - return { - id: node.uri, - slug: node.uri, - title: node.name, - type: node.type, - meta: isHttpOperation(node.data) || isHttpWebhookOperation(node.data) ? node.data.method : '', - }; - }); + const items = group.items + .flatMap(node => { + if (hideInternal && isInternal(node)) return []; + return { + id: node.uri, + slug: node.uri, + title: node.name, + type: node.type, + meta: isHttpOperation(node.data) || isHttpWebhookOperation(node.data) ? node.data.method : '', + groupIndex: 0, + groupId: 0, + }; + }) + .filter(Boolean); + if (items.length > 0) { tree.push({ title: group.title, From e06ed81763a9ee20b35f8508f2b67309b7d661e9 Mon Sep 17 00:00:00 2001 From: SB-venkatyadavilli Date: Wed, 12 Nov 2025 14:28:43 +0530 Subject: [PATCH 06/13] fix: removed log --- .../src/components/TableOfContents/TableOfContents.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/elements-dev-portal/src/components/TableOfContents/TableOfContents.tsx b/packages/elements-dev-portal/src/components/TableOfContents/TableOfContents.tsx index ce7e4d581..db371fa56 100644 --- a/packages/elements-dev-portal/src/components/TableOfContents/TableOfContents.tsx +++ b/packages/elements-dev-portal/src/components/TableOfContents/TableOfContents.tsx @@ -28,7 +28,6 @@ export const TableOfContents = ({ onLinkClick, ...boxProps }: TableOfContentsProps) => { - console.log('from packages/elements-dev-portal/src/components/TableOfContents/TableOfContents.tsx'); return ( From acef357dd78310fe2ac9fca86dd2f09c894cfd6a Mon Sep 17 00:00:00 2001 From: SB-venkatyadavilli Date: Wed, 12 Nov 2025 16:05:32 +0530 Subject: [PATCH 07/13] fix:added groupId & groupIndex in TableOfContents.stories.tsx --- .../TableOfContents.stories.tsx | 38 ++++++++++++------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/packages/elements-core/src/components/TableOfContents/TableOfContents.stories.tsx b/packages/elements-core/src/components/TableOfContents/TableOfContents.stories.tsx index 2cce2179b..09c4de98c 100644 --- a/packages/elements-core/src/components/TableOfContents/TableOfContents.stories.tsx +++ b/packages/elements-core/src/components/TableOfContents/TableOfContents.stories.tsx @@ -21,17 +21,13 @@ export const Playground: Story = args => ( Playground.storyName = 'Todo API'; Playground.args = { tree: [ - { - id: '/', - slug: '/', - title: 'Overview', - type: 'overview', - meta: '', - }, + { groupId: 0, groupIndex: 0, id: '/', slug: '/', title: 'Overview', type: 'overview', meta: '' }, { title: 'Endpoints', }, { + groupId: 2, + groupIndex: 2, id: '/operations/get-todos', slug: '/operations/get-todos', title: 'List Todos', @@ -39,6 +35,8 @@ Playground.args = { meta: 'get', }, { + groupId: 3, + groupIndex: 3, id: '/operations/post-todos', slug: '/operations/post-todos', title: 'Create Todo', @@ -46,6 +44,8 @@ Playground.args = { meta: 'post', }, { + groupId: 4, + groupIndex: 4, id: '/operations/get-todos-id', slug: '/operations/get-todos-id', title: 'Get Todo', @@ -53,6 +53,8 @@ Playground.args = { meta: 'get', }, { + groupId: 5, + groupIndex: 5, id: '/operations/put-todos-id', slug: '/operations/put-todos-id', title: 'Replace Todo', @@ -60,6 +62,8 @@ Playground.args = { meta: 'put', }, { + groupId: 6, + groupIndex: 6, id: '/operations/delete-todos-id', slug: '/operations/delete-todos-id', title: 'Delete Todo', @@ -67,6 +71,8 @@ Playground.args = { meta: 'delete', }, { + groupId: 7, + groupIndex: 7, id: '/operations/patch-todos-id', slug: '/operations/patch-todos-id', title: 'Update Todo', @@ -74,9 +80,13 @@ Playground.args = { meta: 'patch', }, { + groupId: 8, + groupIndex: 8, title: 'Users', items: [ { + groupId: 8, + groupIndex: 0, id: '/operations/get-users', slug: '/operations/get-users', title: 'Get User', @@ -84,6 +94,8 @@ Playground.args = { meta: 'get', }, { + groupId: 8, + groupIndex: 1, id: '/operations/delete-users-userID', slug: '/operations/delete-users-userID', title: 'Delete User', @@ -91,6 +103,8 @@ Playground.args = { meta: 'delete', }, { + groupId: 8, + groupIndex: 2, id: '/operations/post-users-userID', slug: '/operations/post-users-userID', title: 'Create User', @@ -103,6 +117,8 @@ Playground.args = { title: 'Schemas', }, { + groupId: 10, + groupIndex: 10, id: '/schemas/Todos', slug: '/schemas/Todos', title: 'Todo', @@ -110,12 +126,6 @@ Playground.args = { meta: '', version: '1.0.2', }, - { - id: '/schemas/User', - slug: '/schemas/User', - title: 'User', - type: 'model', - meta: '', - }, + { groupId: 11, groupIndex: 11, id: '/schemas/User', slug: '/schemas/User', title: 'User', type: 'model', meta: '' }, ], }; From 0ad349960f5b77e4bade3c2b26efe0418f9e0fad Mon Sep 17 00:00:00 2001 From: SB-venkatyadavilli Date: Thu, 13 Nov 2025 11:04:23 +0530 Subject: [PATCH 08/13] fix:removed onLickClick from handleClick --- .../src/components/TableOfContents/TableOfContents.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/elements-core/src/components/TableOfContents/TableOfContents.tsx b/packages/elements-core/src/components/TableOfContents/TableOfContents.tsx index 49a928776..93f541cb4 100644 --- a/packages/elements-core/src/components/TableOfContents/TableOfContents.tsx +++ b/packages/elements-core/src/components/TableOfContents/TableOfContents.tsx @@ -399,7 +399,6 @@ const Group = React.memo<{ }, [hasActive]); const handleClick = (e: React.MouseEvent, forceOpen?: boolean) => { - onLinkClick(); setIsOpen(forceOpen ? true : !isOpen); }; From 56efdabf284d670e26201af58d7723888f34b0e1 Mon Sep 17 00:00:00 2001 From: SB-venkatyadavilli Date: Fri, 14 Nov 2025 12:43:26 +0530 Subject: [PATCH 09/13] fix: optimise the updateTree functionality --- .../TableOfContents/TableOfContents.tsx | 56 +++++++------------ 1 file changed, 20 insertions(+), 36 deletions(-) diff --git a/packages/elements-core/src/components/TableOfContents/TableOfContents.tsx b/packages/elements-core/src/components/TableOfContents/TableOfContents.tsx index 93f541cb4..f101fcdcc 100644 --- a/packages/elements-core/src/components/TableOfContents/TableOfContents.tsx +++ b/packages/elements-core/src/components/TableOfContents/TableOfContents.tsx @@ -71,44 +71,28 @@ export const TableOfContents = React.memo( onLinkClick, }) => { const groupContext = React.useContext(GroupContext); - const addGroupIndex = React.useCallback( - (arr: any[], groupId: number | null, isHttpService: boolean, groupIndex: number | null = null): any[] => { - return arr.map((item, key) => { - // Early return for title-only items + const addGroupIndex = React.useCallback((arr: any[], groupId: number | null): any[] => { + return arr.map((item, key) => { + // Early return for title-only items - if (isDivider(item) || isExternalLink(item)) { - return item; - } + if (isDivider(item) || isExternalLink(item)) { + return item; + } - const isHttpServiceItem = item?.type === 'http_service'; - const isGroupItem = !item?.type && item?.items && !('itemsType' in item); - let GroupId = groupId; - if (GroupId === null) { - GroupId = key; - } - let GroupIndex = groupIndex; - if (GroupIndex === null) { - GroupIndex = key; - } - const shouldUseHttpService = !isGroupItem && (isHttpService || isHttpServiceItem); - const currentGroupIndex = isHttpServiceItem ? key : GroupIndex; - - let newItem = { - ...item, - groupIndex: shouldUseHttpService ? currentGroupIndex : key, - groupId: GroupId, - }; - - // Process items array if it exists - if (Array.isArray(item.items)) { - newItem.items = addGroupIndex(item.items, GroupId, shouldUseHttpService, currentGroupIndex); - } + let newItem = { + ...item, + groupIndex: key, + groupId: groupId || key, + }; - return newItem; - }); - }, - [], - ); + // Process items array if it exists + if (Array.isArray(item.items)) { + newItem.items = addGroupIndex(item.items, key); + } + + return newItem; + }); + }, []); const getInitialValues = React.useCallback( (tree: any[]): boolean => { @@ -143,7 +127,7 @@ export const TableOfContents = React.memo( }, [groupContext], ); - const updatedTree = addGroupIndex(tree, null, false); + const updatedTree = addGroupIndex(tree, null); const container = React.useRef(null); const child = React.useRef(null); From 454151174fdd2c8e8fb410147da01b43b99be747 Mon Sep 17 00:00:00 2001 From: SB-venkatyadavilli Date: Fri, 14 Nov 2025 21:14:08 +0530 Subject: [PATCH 10/13] fix: added parentId as 3rd property to identify the activeElement --- .../TableOfContents/TableOfContents.spec.tsx | 45 ++++-- .../TableOfContents.stories.tsx | 77 ++++++---- .../TableOfContents/TableOfContents.tsx | 135 +++++++++--------- .../src/components/TableOfContents/types.ts | 12 +- .../components/API/__tests__/utils.test.ts | 9 +- packages/elements/src/components/API/utils.ts | 3 +- 6 files changed, 167 insertions(+), 114 deletions(-) diff --git a/packages/elements-core/src/components/TableOfContents/TableOfContents.spec.tsx b/packages/elements-core/src/components/TableOfContents/TableOfContents.spec.tsx index 12208371f..97bc292a4 100644 --- a/packages/elements-core/src/components/TableOfContents/TableOfContents.spec.tsx +++ b/packages/elements-core/src/components/TableOfContents/TableOfContents.spec.tsx @@ -28,7 +28,8 @@ describe('TableOfContents', () => { type: 'article', meta: '', groupId: 0, - groupIndex: 0, + index: 0, + parentId: 0, }, ], }, @@ -58,7 +59,8 @@ describe('TableOfContents', () => { type: 'article', meta: '', groupId: 0, - groupIndex: 0, + index: 0, + parentId: 0, }, ], }, @@ -92,11 +94,13 @@ describe('TableOfContents', () => { type: 'article', meta: '', groupId: 0, - groupIndex: 0, + index: 0, + parentId: 0, }, ], groupId: 0, - groupIndex: 0, + index: 0, + parentId: 0, }, ], }, @@ -128,7 +132,8 @@ describe('TableOfContents', () => { type: 'article', meta: '', groupId: 0, - groupIndex: 0, + index: 0, + parentId: 0, }, ], }, @@ -165,7 +170,8 @@ describe('TableOfContents', () => { type: 'article', meta: '', groupId: 0, - groupIndex: 0, + index: 0, + parentId: 0, }, ], }, @@ -204,7 +210,8 @@ describe('TableOfContents', () => { meta: '', version: '2', groupId: 0, - groupIndex: 0, + index: 0, + parentId: 0, }, { id: 'def', @@ -214,7 +221,8 @@ describe('TableOfContents', () => { meta: '', version: '1.0.1', groupId: 0, - groupIndex: 0, + index: 0, + parentId: 0, }, { id: 'ghi', @@ -224,7 +232,8 @@ describe('TableOfContents', () => { meta: 'get', version: '1.0.2', groupId: 0, - groupIndex: 0, + index: 0, + parentId: 0, }, ], }, @@ -257,7 +266,8 @@ describe('utils', () => { slug: 'abc-doc', meta: '', groupId: 0, - groupIndex: 0, + index: 0, + parentId: 0, }, { id: 'targetId', @@ -266,7 +276,8 @@ describe('utils', () => { type: 'article', meta: '', groupId: 0, - groupIndex: 0, + index: 0, + parentId: 0, }, ], }, @@ -281,7 +292,8 @@ describe('utils', () => { slug: 'abc-doc', meta: '', groupId: 0, - groupIndex: 0, + index: 0, + parentId: 0, }); }); @@ -301,7 +313,8 @@ describe('utils', () => { type: 'http_operation', meta: 'get', groupId: 0, - groupIndex: 0, + index: 0, + parentId: 0, }, { id: 'ghi', @@ -310,7 +323,8 @@ describe('utils', () => { type: 'http_operation', meta: 'post', groupId: 0, - groupIndex: 0, + index: 0, + parentId: 0, }, ], }, @@ -324,7 +338,8 @@ describe('utils', () => { type: 'http_operation', meta: 'get', groupId: 0, - groupIndex: 0, + index: 0, + parentId: 0, }); }); }); diff --git a/packages/elements-core/src/components/TableOfContents/TableOfContents.stories.tsx b/packages/elements-core/src/components/TableOfContents/TableOfContents.stories.tsx index 09c4de98c..6da4eb8a6 100644 --- a/packages/elements-core/src/components/TableOfContents/TableOfContents.stories.tsx +++ b/packages/elements-core/src/components/TableOfContents/TableOfContents.stories.tsx @@ -21,111 +21,140 @@ export const Playground: Story = args => ( Playground.storyName = 'Todo API'; Playground.args = { tree: [ - { groupId: 0, groupIndex: 0, id: '/', slug: '/', title: 'Overview', type: 'overview', meta: '' }, + { + id: '/', + slug: '/', + title: 'Overview', + type: 'overview', + meta: '', + index: 0, + parentId: 0, + groupId: 0, + }, { title: 'Endpoints', }, { - groupId: 2, - groupIndex: 2, id: '/operations/get-todos', slug: '/operations/get-todos', title: 'List Todos', type: 'http_operation', meta: 'get', + index: 2, + parentId: 2, + groupId: 2, }, { - groupId: 3, - groupIndex: 3, id: '/operations/post-todos', slug: '/operations/post-todos', title: 'Create Todo', type: 'http_operation', meta: 'post', + index: 3, + parentId: 3, + groupId: 3, }, { - groupId: 4, - groupIndex: 4, id: '/operations/get-todos-id', slug: '/operations/get-todos-id', title: 'Get Todo', type: 'http_operation', meta: 'get', + index: 4, + parentId: 4, + groupId: 4, }, { - groupId: 5, - groupIndex: 5, id: '/operations/put-todos-id', slug: '/operations/put-todos-id', title: 'Replace Todo', type: 'http_operation', meta: 'put', + index: 5, + parentId: 5, + groupId: 5, }, { - groupId: 6, - groupIndex: 6, id: '/operations/delete-todos-id', slug: '/operations/delete-todos-id', title: 'Delete Todo', type: 'http_operation', meta: 'delete', + index: 6, + parentId: 6, + groupId: 6, }, { - groupId: 7, - groupIndex: 7, id: '/operations/patch-todos-id', slug: '/operations/patch-todos-id', title: 'Update Todo', type: 'http_operation', meta: 'patch', + index: 7, + parentId: 7, + groupId: 7, }, { - groupId: 8, - groupIndex: 8, title: 'Users', items: [ { - groupId: 8, - groupIndex: 0, id: '/operations/get-users', slug: '/operations/get-users', title: 'Get User', type: 'http_operation', meta: 'get', + index: 0, + parentId: 8, + groupId: 8, }, { - groupId: 8, - groupIndex: 1, id: '/operations/delete-users-userID', slug: '/operations/delete-users-userID', title: 'Delete User', type: 'http_operation', meta: 'delete', + index: 1, + parentId: 8, + groupId: 8, }, { - groupId: 8, - groupIndex: 2, id: '/operations/post-users-userID', slug: '/operations/post-users-userID', title: 'Create User', type: 'http_operation', meta: 'post', + index: 2, + parentId: 8, + groupId: 8, }, ], + index: 8, + parentId: 8, + groupId: 8, }, { title: 'Schemas', }, { - groupId: 10, - groupIndex: 10, id: '/schemas/Todos', slug: '/schemas/Todos', title: 'Todo', type: 'model', meta: '', version: '1.0.2', + index: 10, + parentId: 10, + groupId: 10, + }, + { + id: '/schemas/User', + slug: '/schemas/User', + title: 'User', + type: 'model', + meta: '', + index: 11, + parentId: 11, + groupId: 11, }, - { groupId: 11, groupIndex: 11, id: '/schemas/User', slug: '/schemas/User', title: 'User', type: 'model', meta: '' }, ], }; diff --git a/packages/elements-core/src/components/TableOfContents/TableOfContents.tsx b/packages/elements-core/src/components/TableOfContents/TableOfContents.tsx index f101fcdcc..235dd8125 100644 --- a/packages/elements-core/src/components/TableOfContents/TableOfContents.tsx +++ b/packages/elements-core/src/components/TableOfContents/TableOfContents.tsx @@ -39,20 +39,30 @@ const LinkContext = React.createContext(undefin LinkContext.displayName = 'LinkContext'; // Create the context with a default undefined value -export const GroupContext = createContext(undefined); +export const GroupContext = createContext({ + lastActiveIndex: null, + lastActiveParentId: null, + lastActiveGroupId: null, + setLastActiveIndex: () => {}, + setLastActiveParentId: () => {}, + setLastActiveGroupId: () => {}, +}); // Provider component const GroupProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { - const [lastActiveGroupIndex, setLastActiveGroupIndex] = useState(null); // default value 0 + const [lastActiveIndex, setLastActiveIndex] = useState(null); // default value 0 + const [lastActiveParentId, setLastActiveParentId] = useState(null); const [lastActiveGroupId, setLastActiveGroupId] = useState(null); return ( @@ -71,7 +81,7 @@ export const TableOfContents = React.memo( onLinkClick, }) => { const groupContext = React.useContext(GroupContext); - const addGroupIndex = React.useCallback((arr: any[], groupId: number | null): any[] => { + const addGroupIndex = React.useCallback((arr: any[], groupId: number | null, parentId: number | null): any[] => { return arr.map((item, key) => { // Early return for title-only items @@ -81,13 +91,19 @@ export const TableOfContents = React.memo( let newItem = { ...item, - groupIndex: key, - groupId: groupId || key, + index: key, + parentId: parentId !== null ? parentId : key, + groupId: groupId !== null ? groupId : key, }; // Process items array if it exists if (Array.isArray(item.items)) { - newItem.items = addGroupIndex(item.items, key); + console.log('itemsType:::', 'itemsType' in item, ' key:::', key, ' parentId:::', parentId); + newItem.items = addGroupIndex( + item.items, + groupId !== null ? groupId : key, + 'itemsType' in item ? parentId : key, + ); } return newItem; @@ -97,29 +113,19 @@ export const TableOfContents = React.memo( const getInitialValues = React.useCallback( (tree: any[]): boolean => { for (const item of tree) { - if (item?.items && item.type === 'http_service') { - if (item?.groupId != null) { - groupContext?.setLastActiveGroupId(item.groupId); - } - - if (item?.groupIndex != null) { - groupContext?.setLastActiveGroupIndex(item.groupIndex); - } + const shouldSetValues = + (Array.isArray(item?.items) && item.type === 'http_service') || Object.keys(item).length !== 1; + if (shouldSetValues) { + groupContext?.setLastActiveGroupId(item.groupId); + groupContext?.setLastActiveParentId(item.parentId); + groupContext?.setLastActiveIndex(item.index); return true; - } else if (item?.items) { + } + + if (Array.isArray(item?.items)) { const found = getInitialValues(item.items); if (found) return true; - } else if (Object.keys(item).length !== 1 && !groupContext?.lastActiveGroupIndex) { - if (item?.groupId != null) { - groupContext?.setLastActiveGroupId(item.groupId); - } - - if (item?.groupIndex != null) { - groupContext?.setLastActiveGroupIndex(item.groupIndex); - } - - return true; } } @@ -127,7 +133,8 @@ export const TableOfContents = React.memo( }, [groupContext], ); - const updatedTree = addGroupIndex(tree, null); + const updatedTree = addGroupIndex(tree, null, null); + console.log(updatedTree); const container = React.useRef(null); const child = React.useRef(null); @@ -217,29 +224,19 @@ const TOCContainer = React.memo<{ const getInitialValues = React.useCallback( (tree: any[]): boolean => { for (const item of tree) { - if (item?.items && item.type === 'http_service') { - if (item?.groupId != null) { - groupContext?.setLastActiveGroupId(item.groupId); - } - - if (item?.groupIndex != null) { - groupContext?.setLastActiveGroupIndex(item.groupIndex); - } + const shouldSetValues = + (item?.items && item.type === 'http_service') || (!item?.items && Object.keys(item).length !== 1); + if (shouldSetValues) { + groupContext?.setLastActiveGroupId(item.groupId); + groupContext?.setLastActiveParentId(item.parentId); + groupContext?.setLastActiveIndex(item.index); return true; - } else if (item?.items) { + } + + if (item?.items) { const found = getInitialValues(item.items); if (found) return true; - } else if (Object.keys(item).length !== 1 && !groupContext?.lastActiveGroupIndex) { - if (item?.groupId != null) { - groupContext?.setLastActiveGroupId(item.groupId); - } - - if (item?.groupIndex != null) { - groupContext?.setLastActiveGroupIndex(item.groupIndex); - } - - return true; } } @@ -329,13 +326,14 @@ const Group = React.memo<{ onLinkClick?(): void; }>(({ depth, item, maxDepthOpenByDefault, isInResponsiveMode, onLinkClick = () => {} }) => { const activeId = React.useContext(ActiveIdContext); - const groupContext = React.useContext(GroupContext); + const { lastActiveGroupId, lastActiveParentId, lastActiveIndex } = React.useContext(GroupContext); const [isOpen, setIsOpen] = React.useState(() => isGroupOpenByDefault(depth, item, activeId, maxDepthOpenByDefault)); function isActiveGroup( items: any[], activeId: string | undefined, - contextId: number | undefined | null, - contextIndex: number | undefined | null, + contextGroupId: number | null, + contextParentId: number | null, + contextIndex: number | null, ): boolean { for (const element of items) { if (!('items' in element)) { @@ -343,14 +341,15 @@ const Group = React.memo<{ if ( !!activeId && (activeId === element.slug || activeId === element.id) && - contextId === element.groupId && - contextIndex === element.groupIndex + contextGroupId === element.groupId && + contextParentId === element.parentId && + contextIndex === element.index ) { return true; } } } else if (Array.isArray(element.items)) { - const found = isActiveGroup(element.items, activeId, contextId, contextIndex); + const found = isActiveGroup(element.items, activeId, contextGroupId, contextParentId, contextIndex); if (found) { return true; } @@ -359,12 +358,7 @@ const Group = React.memo<{ return false; } - const hasActive = isActiveGroup( - item.items, - activeId, - groupContext?.lastActiveGroupId, - groupContext?.lastActiveGroupIndex, - ); + const hasActive = isActiveGroup(item.items, activeId, lastActiveGroupId, lastActiveParentId, lastActiveIndex); // If maxDepthOpenByDefault changes, we want to update all the isOpen states (used in live preview mode) React.useEffect(() => { @@ -517,12 +511,18 @@ const Node = React.memo<{ onLinkClick?(): void; }>(({ item, depth, meta, showAsActive, isInResponsiveMode, onClick, onLinkClick = () => {} }) => { const activeId = React.useContext(ActiveIdContext); - const groupContext = React.useContext(GroupContext); - - const groupIndex = item.groupIndex; - const groupId = item.groupId; - - const check1 = groupIndex === groupContext?.lastActiveGroupIndex && groupId === groupContext?.lastActiveGroupId; + const { + lastActiveGroupId, + lastActiveIndex, + lastActiveParentId, + setLastActiveGroupId, + setLastActiveIndex, + setLastActiveParentId, + } = React.useContext(GroupContext); + const { groupId, parentId, index } = item; + // const groupId = item.groupId; + + const check1 = index === lastActiveIndex && groupId === lastActiveGroupId && parentId === lastActiveParentId; const check2 = activeId === item.slug || activeId === item.id; const isActive = check1 && check2; const LinkComponent = React.useContext(LinkContext); @@ -533,8 +533,9 @@ const Node = React.memo<{ e.stopPropagation(); e.preventDefault(); } else { - groupContext?.setLastActiveGroupIndex(item.groupIndex); - groupContext?.setLastActiveGroupId(item.groupId); + setLastActiveIndex(index); + setLastActiveGroupId(groupId); + setLastActiveParentId(parentId); onLinkClick(); } diff --git a/packages/elements-core/src/components/TableOfContents/types.ts b/packages/elements-core/src/components/TableOfContents/types.ts index 7e2574f37..dcf8527ee 100644 --- a/packages/elements-core/src/components/TableOfContents/types.ts +++ b/packages/elements-core/src/components/TableOfContents/types.ts @@ -31,7 +31,8 @@ export type TableOfContentsGroup = { groupId: number; items: TableOfContentsGroupItem[]; itemsType?: 'article' | 'http_operation' | 'http_webhook' | 'model'; - groupIndex: number; + index: number; + parentId: number; }; export type TableOfContentsExternalLink = { @@ -48,14 +49,17 @@ export type TableOfContentsNode< type: T; meta: string; version?: string; - groupIndex: number; + index: number; + parentId: number; groupId: number; }; export type TableOfContentsNodeGroup = TableOfContentsNode<'http_service'> & TableOfContentsGroup; export type GroupContextType = { - lastActiveGroupIndex: number | null; + lastActiveIndex: number | null; + lastActiveParentId: number | null; lastActiveGroupId: number | null; - setLastActiveGroupIndex: React.Dispatch>; + setLastActiveIndex: React.Dispatch>; + setLastActiveParentId: React.Dispatch>; setLastActiveGroupId: React.Dispatch>; }; diff --git a/packages/elements/src/components/API/__tests__/utils.test.ts b/packages/elements/src/components/API/__tests__/utils.test.ts index 9366bf26a..46ece2fa6 100644 --- a/packages/elements/src/components/API/__tests__/utils.test.ts +++ b/packages/elements/src/components/API/__tests__/utils.test.ts @@ -898,8 +898,9 @@ describe.each([ slug: `/${pathProp}/something/get`, title: '/something', type: nodeType, - groupIndex: 0, + index: 0, groupId: 0, + parentId: 0, }, ], }, @@ -982,7 +983,8 @@ describe.each([ type: 'model', meta: '', groupId: 0, - groupIndex: 0, + index: 0, + parentId: 0, }, ], }, @@ -1046,8 +1048,9 @@ describe.each([ slug: `/${pathProp}/something-else/post`, title: '/something-else', type: nodeType, - groupIndex: 0, + index: 0, groupId: 0, + parentId: 0, }, ], }, diff --git a/packages/elements/src/components/API/utils.ts b/packages/elements/src/components/API/utils.ts index 3f94d363c..21bc6816d 100644 --- a/packages/elements/src/components/API/utils.ts +++ b/packages/elements/src/components/API/utils.ts @@ -182,8 +182,9 @@ const addTagGroupsToTree = ( title: node.name, type: node.type, meta: isHttpOperation(node.data) || isHttpWebhookOperation(node.data) ? node.data.method : '', - groupIndex: 0, + index: 0, groupId: 0, + parentId: 0, }; }) .filter(Boolean); From 27ca65f6a61cfb2bc6355abd8a4ee10ff355231e Mon Sep 17 00:00:00 2001 From: SB-venkatyadavilli Date: Mon, 17 Nov 2025 15:48:03 +0530 Subject: [PATCH 11/13] fix: renamed the functionaliy to updateTocTree and removed logs. --- .../components/TableOfContents/TableOfContents.tsx | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/packages/elements-core/src/components/TableOfContents/TableOfContents.tsx b/packages/elements-core/src/components/TableOfContents/TableOfContents.tsx index 235dd8125..9fd0d803a 100644 --- a/packages/elements-core/src/components/TableOfContents/TableOfContents.tsx +++ b/packages/elements-core/src/components/TableOfContents/TableOfContents.tsx @@ -81,7 +81,7 @@ export const TableOfContents = React.memo( onLinkClick, }) => { const groupContext = React.useContext(GroupContext); - const addGroupIndex = React.useCallback((arr: any[], groupId: number | null, parentId: number | null): any[] => { + const updateTocTree = React.useCallback((arr: any[], groupId: number | null, parentId: number | null): any[] => { return arr.map((item, key) => { // Early return for title-only items @@ -98,8 +98,7 @@ export const TableOfContents = React.memo( // Process items array if it exists if (Array.isArray(item.items)) { - console.log('itemsType:::', 'itemsType' in item, ' key:::', key, ' parentId:::', parentId); - newItem.items = addGroupIndex( + newItem.items = updateTocTree( item.items, groupId !== null ? groupId : key, 'itemsType' in item ? parentId : key, @@ -133,8 +132,7 @@ export const TableOfContents = React.memo( }, [groupContext], ); - const updatedTree = addGroupIndex(tree, null, null); - console.log(updatedTree); + const updatedTree = updateTocTree(tree, null, null); const container = React.useRef(null); const child = React.useRef(null); @@ -520,14 +518,13 @@ const Node = React.memo<{ setLastActiveParentId, } = React.useContext(GroupContext); const { groupId, parentId, index } = item; - // const groupId = item.groupId; const check1 = index === lastActiveIndex && groupId === lastActiveGroupId && parentId === lastActiveParentId; const check2 = activeId === item.slug || activeId === item.id; const isActive = check1 && check2; const LinkComponent = React.useContext(LinkContext); - const handleClick = (e: React.MouseEvent, item: any) => { + const handleClick = (e: React.MouseEvent) => { if (isActive) { // Don't trigger link click when we're active e.stopPropagation(); @@ -566,7 +563,7 @@ const Node = React.memo<{ } meta={meta} isInResponsiveMode={isInResponsiveMode} - onClick={e => handleClick(e, item)} + onClick={e => handleClick(e)} /> ); From 8b732bd74079e4122193acaa2d15668e62047b6f Mon Sep 17 00:00:00 2001 From: SB-venkatyadavilli Date: Wed, 19 Nov 2025 17:57:47 +0530 Subject: [PATCH 12/13] fix: added useMemo to contextAPI. --- .../TableOfContents/TableOfContents.tsx | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/packages/elements-core/src/components/TableOfContents/TableOfContents.tsx b/packages/elements-core/src/components/TableOfContents/TableOfContents.tsx index 9fd0d803a..4c7eafb6b 100644 --- a/packages/elements-core/src/components/TableOfContents/TableOfContents.tsx +++ b/packages/elements-core/src/components/TableOfContents/TableOfContents.tsx @@ -53,22 +53,19 @@ const GroupProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => const [lastActiveIndex, setLastActiveIndex] = useState(null); // default value 0 const [lastActiveParentId, setLastActiveParentId] = useState(null); const [lastActiveGroupId, setLastActiveGroupId] = useState(null); - - return ( - - {children} - + const value = React.useMemo( + () => ({ + lastActiveIndex, + lastActiveParentId, + lastActiveGroupId, + setLastActiveIndex, + setLastActiveParentId, + setLastActiveGroupId, + }), + [lastActiveIndex, lastActiveParentId, lastActiveGroupId], ); + + return {children}; }; export const TableOfContents = React.memo( ({ @@ -84,6 +81,7 @@ export const TableOfContents = React.memo( const updateTocTree = React.useCallback((arr: any[], groupId: number | null, parentId: number | null): any[] => { return arr.map((item, key) => { // Early return for title-only items + console.log({ item, key }); if (isDivider(item) || isExternalLink(item)) { return item; @@ -519,9 +517,10 @@ const Node = React.memo<{ } = React.useContext(GroupContext); const { groupId, parentId, index } = item; - const check1 = index === lastActiveIndex && groupId === lastActiveGroupId && parentId === lastActiveParentId; - const check2 = activeId === item.slug || activeId === item.id; - const isActive = check1 && check2; + const isIndexesMatched = + index === lastActiveIndex && groupId === lastActiveGroupId && parentId === lastActiveParentId; + const isSlugMatched = activeId === item.slug || activeId === item.id; + const isActive = isIndexesMatched && isSlugMatched; const LinkComponent = React.useContext(LinkContext); const handleClick = (e: React.MouseEvent) => { From d0a3bab0d94e5512bcf80ec9ae4e09264283d34c Mon Sep 17 00:00:00 2001 From: SB-venkatyadavilli Date: Tue, 25 Nov 2025 12:57:20 +0530 Subject: [PATCH 13/13] fix: removed log --- .../src/components/TableOfContents/TableOfContents.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/elements-core/src/components/TableOfContents/TableOfContents.tsx b/packages/elements-core/src/components/TableOfContents/TableOfContents.tsx index 4c7eafb6b..c2b7b8609 100644 --- a/packages/elements-core/src/components/TableOfContents/TableOfContents.tsx +++ b/packages/elements-core/src/components/TableOfContents/TableOfContents.tsx @@ -80,9 +80,6 @@ export const TableOfContents = React.memo( const groupContext = React.useContext(GroupContext); const updateTocTree = React.useCallback((arr: any[], groupId: number | null, parentId: number | null): any[] => { return arr.map((item, key) => { - // Early return for title-only items - console.log({ item, key }); - if (isDivider(item) || isExternalLink(item)) { return item; }