From 2d65ed092c30e240c093e9f456d6df2bbe09ff10 Mon Sep 17 00:00:00 2001 From: SB-venkatyadavilli Date: Fri, 7 Nov 2025 15:24:52 +0530 Subject: [PATCH 01/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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; } From b9a8209daec9b1827c6a7461532bab1203f099aa Mon Sep 17 00:00:00 2001 From: SB-venkatyadavilli Date: Wed, 26 Nov 2025 15:56:14 +0530 Subject: [PATCH 14/22] fix:implemented unique key approach for each Tree elements --- .../TableOfContents/TableOfContents.spec.tsx | 62 +++-------- .../TableOfContents.stories.tsx | 54 +++------ .../TableOfContents/TableOfContents.tsx | 103 +++++------------- .../src/components/TableOfContents/types.ts | 16 +-- .../components/API/__tests__/utils.test.ts | 12 +- packages/elements/src/components/API/utils.ts | 4 +- 6 files changed, 66 insertions(+), 185 deletions(-) diff --git a/packages/elements-core/src/components/TableOfContents/TableOfContents.spec.tsx b/packages/elements-core/src/components/TableOfContents/TableOfContents.spec.tsx index 97bc292a4..6e5b0500b 100644 --- a/packages/elements-core/src/components/TableOfContents/TableOfContents.spec.tsx +++ b/packages/elements-core/src/components/TableOfContents/TableOfContents.spec.tsx @@ -27,9 +27,7 @@ describe('TableOfContents', () => { slug: 'target', type: 'article', meta: '', - groupId: 0, - index: 0, - parentId: 0, + index: '', }, ], }, @@ -39,7 +37,7 @@ describe('TableOfContents', () => { ); expect(screen.queryByTitle('Root')).toBeInTheDocument(); - expect(screen.queryByTitle('Target')).not.toBeInTheDocument(); + // expect(screen.queryByTitle('Target')).not.toBeInTheDocument(); unmount(); }); @@ -58,9 +56,7 @@ describe('TableOfContents', () => { slug: 'target', type: 'article', meta: '', - groupId: 0, - index: 0, - parentId: 0, + index: '', }, ], }, @@ -93,14 +89,10 @@ describe('TableOfContents', () => { slug: 'target', type: 'article', meta: '', - groupId: 0, - index: 0, - parentId: 0, + index: '', }, ], - groupId: 0, - index: 0, - parentId: 0, + index: '', }, ], }, @@ -131,9 +123,7 @@ describe('TableOfContents', () => { slug: 'target', type: 'article', meta: '', - groupId: 0, - index: 0, - parentId: 0, + index: '', }, ], }, @@ -169,9 +159,7 @@ describe('TableOfContents', () => { slug: 'target', type: 'article', meta: '', - groupId: 0, - index: 0, - parentId: 0, + index: '', }, ], }, @@ -209,9 +197,7 @@ describe('TableOfContents', () => { items: [], meta: '', version: '2', - groupId: 0, - index: 0, - parentId: 0, + index: '0#', }, { id: 'def', @@ -220,9 +206,7 @@ describe('TableOfContents', () => { type: 'model', meta: '', version: '1.0.1', - groupId: 0, - index: 0, - parentId: 0, + index: '1#', }, { id: 'ghi', @@ -231,9 +215,7 @@ describe('TableOfContents', () => { type: 'http_operation', meta: 'get', version: '1.0.2', - groupId: 0, - index: 0, - parentId: 0, + index: '2#', }, ], }, @@ -265,9 +247,7 @@ describe('utils', () => { type: 'article', slug: 'abc-doc', meta: '', - groupId: 0, - index: 0, - parentId: 0, + index: '', }, { id: 'targetId', @@ -275,9 +255,7 @@ describe('utils', () => { slug: 'target', type: 'article', meta: '', - groupId: 0, - index: 0, - parentId: 0, + index: '', }, ], }, @@ -291,9 +269,7 @@ describe('utils', () => { type: 'article', slug: 'abc-doc', meta: '', - groupId: 0, - index: 0, - parentId: 0, + index: '', }); }); @@ -312,9 +288,7 @@ describe('utils', () => { slug: 'def-get-todo', type: 'http_operation', meta: 'get', - groupId: 0, - index: 0, - parentId: 0, + index: '', }, { id: 'ghi', @@ -322,9 +296,7 @@ describe('utils', () => { slug: 'ghi-add-todo', type: 'http_operation', meta: 'post', - groupId: 0, - index: 0, - parentId: 0, + index: '', }, ], }, @@ -337,9 +309,7 @@ describe('utils', () => { slug: 'def-get-todo', type: 'http_operation', meta: 'get', - groupId: 0, - index: 0, - parentId: 0, + index: '', }); }); }); diff --git a/packages/elements-core/src/components/TableOfContents/TableOfContents.stories.tsx b/packages/elements-core/src/components/TableOfContents/TableOfContents.stories.tsx index 6da4eb8a6..daae1a252 100644 --- a/packages/elements-core/src/components/TableOfContents/TableOfContents.stories.tsx +++ b/packages/elements-core/src/components/TableOfContents/TableOfContents.stories.tsx @@ -27,12 +27,11 @@ Playground.args = { title: 'Overview', type: 'overview', meta: '', - index: 0, - parentId: 0, - groupId: 0, + index: '0#', }, { title: 'Endpoints', + index: '1#', }, { id: '/operations/get-todos', @@ -40,9 +39,7 @@ Playground.args = { title: 'List Todos', type: 'http_operation', meta: 'get', - index: 2, - parentId: 2, - groupId: 2, + index: '2#', }, { id: '/operations/post-todos', @@ -50,9 +47,7 @@ Playground.args = { title: 'Create Todo', type: 'http_operation', meta: 'post', - index: 3, - parentId: 3, - groupId: 3, + index: '3#', }, { id: '/operations/get-todos-id', @@ -60,9 +55,7 @@ Playground.args = { title: 'Get Todo', type: 'http_operation', meta: 'get', - index: 4, - parentId: 4, - groupId: 4, + index: '4#', }, { id: '/operations/put-todos-id', @@ -70,9 +63,7 @@ Playground.args = { title: 'Replace Todo', type: 'http_operation', meta: 'put', - index: 5, - parentId: 5, - groupId: 5, + index: '5#', }, { id: '/operations/delete-todos-id', @@ -80,9 +71,7 @@ Playground.args = { title: 'Delete Todo', type: 'http_operation', meta: 'delete', - index: 6, - parentId: 6, - groupId: 6, + index: '6#', }, { id: '/operations/patch-todos-id', @@ -90,9 +79,7 @@ Playground.args = { title: 'Update Todo', type: 'http_operation', meta: 'patch', - index: 7, - parentId: 7, - groupId: 7, + index: '7#', }, { title: 'Users', @@ -103,9 +90,7 @@ Playground.args = { title: 'Get User', type: 'http_operation', meta: 'get', - index: 0, - parentId: 8, - groupId: 8, + index: '8#0#', }, { id: '/operations/delete-users-userID', @@ -113,9 +98,7 @@ Playground.args = { title: 'Delete User', type: 'http_operation', meta: 'delete', - index: 1, - parentId: 8, - groupId: 8, + index: '8#1#', }, { id: '/operations/post-users-userID', @@ -123,17 +106,14 @@ Playground.args = { title: 'Create User', type: 'http_operation', meta: 'post', - index: 2, - parentId: 8, - groupId: 8, + index: '8#2#', }, ], - index: 8, - parentId: 8, - groupId: 8, + index: '8#', }, { title: 'Schemas', + index: '9#', }, { id: '/schemas/Todos', @@ -142,9 +122,7 @@ Playground.args = { type: 'model', meta: '', version: '1.0.2', - index: 10, - parentId: 10, - groupId: 10, + index: '10#', }, { id: '/schemas/User', @@ -152,9 +130,7 @@ Playground.args = { title: 'User', type: 'model', meta: '', - index: 11, - parentId: 11, - groupId: 11, + index: '11#', }, ], }; diff --git a/packages/elements-core/src/components/TableOfContents/TableOfContents.tsx b/packages/elements-core/src/components/TableOfContents/TableOfContents.tsx index c2b7b8609..e1584e5ba 100644 --- a/packages/elements-core/src/components/TableOfContents/TableOfContents.tsx +++ b/packages/elements-core/src/components/TableOfContents/TableOfContents.tsx @@ -40,29 +40,19 @@ LinkContext.displayName = 'LinkContext'; // Create the context with a default undefined value export const GroupContext = createContext({ - lastActiveIndex: null, - lastActiveParentId: null, - lastActiveGroupId: null, + lastActiveIndex: '', setLastActiveIndex: () => {}, - setLastActiveParentId: () => {}, - setLastActiveGroupId: () => {}, }); // Provider component 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); + const [lastActiveIndex, setLastActiveIndex] = useState(''); // default value 0 const value = React.useMemo( () => ({ lastActiveIndex, - lastActiveParentId, - lastActiveGroupId, setLastActiveIndex, - setLastActiveParentId, - setLastActiveGroupId, }), - [lastActiveIndex, lastActiveParentId, lastActiveGroupId], + [lastActiveIndex], ); return {children}; @@ -78,26 +68,16 @@ export const TableOfContents = React.memo( onLinkClick, }) => { const groupContext = React.useContext(GroupContext); - const updateTocTree = React.useCallback((arr: any[], groupId: number | null, parentId: number | null): any[] => { + const updateTocTree = React.useCallback((arr: any[], parentId: string): any[] => { return arr.map((item, key) => { - if (isDivider(item) || isExternalLink(item)) { - return item; - } - let newItem = { ...item, - index: key, - parentId: parentId !== null ? parentId : key, - groupId: groupId !== null ? groupId : key, + index: parentId + key + '#', }; // Process items array if it exists if (Array.isArray(item.items)) { - newItem.items = updateTocTree( - item.items, - groupId !== null ? groupId : key, - 'itemsType' in item ? parentId : key, - ); + newItem.items = updateTocTree(item.items, parentId + key + '#'); } return newItem; @@ -111,8 +91,6 @@ export const TableOfContents = React.memo( (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; } @@ -127,7 +105,7 @@ export const TableOfContents = React.memo( }, [groupContext], ); - const updatedTree = updateTocTree(tree, null, null); + const updatedTree = updateTocTree(tree, ''); const container = React.useRef(null); const child = React.useRef(null); @@ -221,8 +199,6 @@ const TOCContainer = React.memo<{ (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; } @@ -319,39 +295,27 @@ const Group = React.memo<{ onLinkClick?(): void; }>(({ depth, item, maxDepthOpenByDefault, isInResponsiveMode, onLinkClick = () => {} }) => { const activeId = React.useContext(ActiveIdContext); - const { lastActiveGroupId, lastActiveParentId, lastActiveIndex } = React.useContext(GroupContext); + const { lastActiveIndex } = React.useContext(GroupContext); const [isOpen, setIsOpen] = React.useState(() => isGroupOpenByDefault(depth, item, activeId, maxDepthOpenByDefault)); - function isActiveGroup( - items: any[], - activeId: string | undefined, - contextGroupId: number | null, - contextParentId: number | null, - contextIndex: number | 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) && - contextGroupId === element.groupId && - contextParentId === element.parentId && - contextIndex === element.index - ) { - return true; - } - } - } else if (Array.isArray(element.items)) { - const found = isActiveGroup(element.items, activeId, contextGroupId, contextParentId, contextIndex); - if (found) { + const isActiveGroup = React.useCallback( + (items: any[], activeId: string | undefined, contextIndex: string): boolean => { + return items.some(element => { + const hasSlugOrId = 'slug' in element || 'id' in element; + const hasItems = Array.isArray(element.items); + + if (!hasSlugOrId && !hasItems) return false; + + if (activeId && (activeId === element.slug || activeId === element.id) && contextIndex === element.index) { return true; } - } - } - return false; - } - const hasActive = isActiveGroup(item.items, activeId, lastActiveGroupId, lastActiveParentId, lastActiveIndex); + return hasItems ? isActiveGroup(element.items, activeId, contextIndex) : false; + }); + }, + [], + ); + + const hasActive = isActiveGroup(item.items, activeId, lastActiveIndex); // If maxDepthOpenByDefault changes, we want to update all the isOpen states (used in live preview mode) React.useEffect(() => { @@ -504,20 +468,10 @@ const Node = React.memo<{ onLinkClick?(): void; }>(({ item, depth, meta, showAsActive, isInResponsiveMode, onClick, onLinkClick = () => {} }) => { const activeId = React.useContext(ActiveIdContext); - const { - lastActiveGroupId, - lastActiveIndex, - lastActiveParentId, - setLastActiveGroupId, - setLastActiveIndex, - setLastActiveParentId, - } = React.useContext(GroupContext); - const { groupId, parentId, index } = item; - - const isIndexesMatched = - index === lastActiveIndex && groupId === lastActiveGroupId && parentId === lastActiveParentId; + const { lastActiveIndex, setLastActiveIndex } = React.useContext(GroupContext); + const { index } = item; const isSlugMatched = activeId === item.slug || activeId === item.id; - const isActive = isIndexesMatched && isSlugMatched; + const isActive = lastActiveIndex === index && isSlugMatched; const LinkComponent = React.useContext(LinkContext); const handleClick = (e: React.MouseEvent) => { @@ -527,9 +481,6 @@ const Node = React.memo<{ e.preventDefault(); } else { 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 dcf8527ee..71b00004b 100644 --- a/packages/elements-core/src/components/TableOfContents/types.ts +++ b/packages/elements-core/src/components/TableOfContents/types.ts @@ -28,11 +28,9 @@ export type TableOfContentsGroupItem = export type TableOfContentsGroup = { title: string; - groupId: number; items: TableOfContentsGroupItem[]; itemsType?: 'article' | 'http_operation' | 'http_webhook' | 'model'; - index: number; - parentId: number; + index: string; }; export type TableOfContentsExternalLink = { @@ -49,17 +47,11 @@ export type TableOfContentsNode< type: T; meta: string; version?: string; - index: number; - parentId: number; - groupId: number; + index: string; }; export type TableOfContentsNodeGroup = TableOfContentsNode<'http_service'> & TableOfContentsGroup; export type GroupContextType = { - lastActiveIndex: number | null; - lastActiveParentId: number | null; - lastActiveGroupId: number | null; - setLastActiveIndex: React.Dispatch>; - setLastActiveParentId: React.Dispatch>; - setLastActiveGroupId: React.Dispatch>; + lastActiveIndex: string; + setLastActiveIndex: 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 46ece2fa6..f255e1631 100644 --- a/packages/elements/src/components/API/__tests__/utils.test.ts +++ b/packages/elements/src/components/API/__tests__/utils.test.ts @@ -898,9 +898,7 @@ describe.each([ slug: `/${pathProp}/something/get`, title: '/something', type: nodeType, - index: 0, - groupId: 0, - parentId: 0, + index: '', }, ], }, @@ -982,9 +980,7 @@ describe.each([ title: 'a', type: 'model', meta: '', - groupId: 0, - index: 0, - parentId: 0, + index: '', }, ], }, @@ -1048,9 +1044,7 @@ describe.each([ slug: `/${pathProp}/something-else/post`, title: '/something-else', type: nodeType, - index: 0, - groupId: 0, - parentId: 0, + index: '', }, ], }, diff --git a/packages/elements/src/components/API/utils.ts b/packages/elements/src/components/API/utils.ts index 21bc6816d..208b7f77e 100644 --- a/packages/elements/src/components/API/utils.ts +++ b/packages/elements/src/components/API/utils.ts @@ -182,9 +182,7 @@ const addTagGroupsToTree = ( title: node.name, type: node.type, meta: isHttpOperation(node.data) || isHttpWebhookOperation(node.data) ? node.data.method : '', - index: 0, - groupId: 0, - parentId: 0, + index: '', }; }) .filter(Boolean); From 7159de5fe131f726da312c677b39fe3fad1f6c20 Mon Sep 17 00:00:00 2001 From: SB-venkatyadavilli Date: Wed, 26 Nov 2025 17:56:16 +0530 Subject: [PATCH 15/22] fix: uncommented expect statement --- .../src/components/TableOfContents/TableOfContents.spec.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/elements-core/src/components/TableOfContents/TableOfContents.spec.tsx b/packages/elements-core/src/components/TableOfContents/TableOfContents.spec.tsx index 6e5b0500b..00803cbab 100644 --- a/packages/elements-core/src/components/TableOfContents/TableOfContents.spec.tsx +++ b/packages/elements-core/src/components/TableOfContents/TableOfContents.spec.tsx @@ -37,7 +37,7 @@ describe('TableOfContents', () => { ); expect(screen.queryByTitle('Root')).toBeInTheDocument(); - // expect(screen.queryByTitle('Target')).not.toBeInTheDocument(); + expect(screen.queryByTitle('Target')).not.toBeInTheDocument(); unmount(); }); From 1915ac17cbf416c625d13a1edb29251cb976e27e Mon Sep 17 00:00:00 2001 From: SB-venkatyadavilli Date: Thu, 27 Nov 2025 19:26:15 +0530 Subject: [PATCH 16/22] fix: used '-' symbol as seperator in index unique key --- .../TableOfContents/TableOfContents.spec.tsx | 30 +++++++++---------- .../TableOfContents.stories.tsx | 30 +++++++++---------- .../TableOfContents/TableOfContents.tsx | 4 +-- .../components/API/__tests__/utils.test.ts | 6 ++-- packages/elements/src/components/API/utils.ts | 2 +- 5 files changed, 36 insertions(+), 36 deletions(-) diff --git a/packages/elements-core/src/components/TableOfContents/TableOfContents.spec.tsx b/packages/elements-core/src/components/TableOfContents/TableOfContents.spec.tsx index 00803cbab..f886c9f9d 100644 --- a/packages/elements-core/src/components/TableOfContents/TableOfContents.spec.tsx +++ b/packages/elements-core/src/components/TableOfContents/TableOfContents.spec.tsx @@ -27,7 +27,7 @@ describe('TableOfContents', () => { slug: 'target', type: 'article', meta: '', - index: '', + index: '0-', }, ], }, @@ -56,7 +56,7 @@ describe('TableOfContents', () => { slug: 'target', type: 'article', meta: '', - index: '', + index: '0-', }, ], }, @@ -89,10 +89,10 @@ describe('TableOfContents', () => { slug: 'target', type: 'article', meta: '', - index: '', + index: '0-0-', }, ], - index: '', + index: '0-', }, ], }, @@ -123,7 +123,7 @@ describe('TableOfContents', () => { slug: 'target', type: 'article', meta: '', - index: '', + index: '0-', }, ], }, @@ -159,7 +159,7 @@ describe('TableOfContents', () => { slug: 'target', type: 'article', meta: '', - index: '', + index: '0-', }, ], }, @@ -197,7 +197,7 @@ describe('TableOfContents', () => { items: [], meta: '', version: '2', - index: '0#', + index: '0-', }, { id: 'def', @@ -206,7 +206,7 @@ describe('TableOfContents', () => { type: 'model', meta: '', version: '1.0.1', - index: '1#', + index: '1-', }, { id: 'ghi', @@ -215,7 +215,7 @@ describe('TableOfContents', () => { type: 'http_operation', meta: 'get', version: '1.0.2', - index: '2#', + index: '2-', }, ], }, @@ -247,7 +247,7 @@ describe('utils', () => { type: 'article', slug: 'abc-doc', meta: '', - index: '', + index: '0-', }, { id: 'targetId', @@ -255,7 +255,7 @@ describe('utils', () => { slug: 'target', type: 'article', meta: '', - index: '', + index: '1-', }, ], }, @@ -269,7 +269,7 @@ describe('utils', () => { type: 'article', slug: 'abc-doc', meta: '', - index: '', + index: '0-', }); }); @@ -288,7 +288,7 @@ describe('utils', () => { slug: 'def-get-todo', type: 'http_operation', meta: 'get', - index: '', + index: '0-', }, { id: 'ghi', @@ -296,7 +296,7 @@ describe('utils', () => { slug: 'ghi-add-todo', type: 'http_operation', meta: 'post', - index: '', + index: '1-', }, ], }, @@ -309,7 +309,7 @@ describe('utils', () => { slug: 'def-get-todo', type: 'http_operation', meta: 'get', - index: '', + index: '0-', }); }); }); diff --git a/packages/elements-core/src/components/TableOfContents/TableOfContents.stories.tsx b/packages/elements-core/src/components/TableOfContents/TableOfContents.stories.tsx index daae1a252..9d5e1af72 100644 --- a/packages/elements-core/src/components/TableOfContents/TableOfContents.stories.tsx +++ b/packages/elements-core/src/components/TableOfContents/TableOfContents.stories.tsx @@ -27,11 +27,11 @@ Playground.args = { title: 'Overview', type: 'overview', meta: '', - index: '0#', + index: '0-', }, { title: 'Endpoints', - index: '1#', + index: '1-', }, { id: '/operations/get-todos', @@ -39,7 +39,7 @@ Playground.args = { title: 'List Todos', type: 'http_operation', meta: 'get', - index: '2#', + index: '2-', }, { id: '/operations/post-todos', @@ -47,7 +47,7 @@ Playground.args = { title: 'Create Todo', type: 'http_operation', meta: 'post', - index: '3#', + index: '3-', }, { id: '/operations/get-todos-id', @@ -55,7 +55,7 @@ Playground.args = { title: 'Get Todo', type: 'http_operation', meta: 'get', - index: '4#', + index: '4-', }, { id: '/operations/put-todos-id', @@ -63,7 +63,7 @@ Playground.args = { title: 'Replace Todo', type: 'http_operation', meta: 'put', - index: '5#', + index: '5-', }, { id: '/operations/delete-todos-id', @@ -71,7 +71,7 @@ Playground.args = { title: 'Delete Todo', type: 'http_operation', meta: 'delete', - index: '6#', + index: '6-', }, { id: '/operations/patch-todos-id', @@ -79,7 +79,7 @@ Playground.args = { title: 'Update Todo', type: 'http_operation', meta: 'patch', - index: '7#', + index: '7-', }, { title: 'Users', @@ -90,7 +90,7 @@ Playground.args = { title: 'Get User', type: 'http_operation', meta: 'get', - index: '8#0#', + index: '8-0-', }, { id: '/operations/delete-users-userID', @@ -98,7 +98,7 @@ Playground.args = { title: 'Delete User', type: 'http_operation', meta: 'delete', - index: '8#1#', + index: '8-1-', }, { id: '/operations/post-users-userID', @@ -106,14 +106,14 @@ Playground.args = { title: 'Create User', type: 'http_operation', meta: 'post', - index: '8#2#', + index: '8-2-', }, ], - index: '8#', + index: '8-', }, { title: 'Schemas', - index: '9#', + index: '9-', }, { id: '/schemas/Todos', @@ -122,7 +122,7 @@ Playground.args = { type: 'model', meta: '', version: '1.0.2', - index: '10#', + index: '10-', }, { id: '/schemas/User', @@ -130,7 +130,7 @@ Playground.args = { title: 'User', type: 'model', meta: '', - index: '11#', + index: '11-', }, ], }; diff --git a/packages/elements-core/src/components/TableOfContents/TableOfContents.tsx b/packages/elements-core/src/components/TableOfContents/TableOfContents.tsx index e1584e5ba..5ed0bd347 100644 --- a/packages/elements-core/src/components/TableOfContents/TableOfContents.tsx +++ b/packages/elements-core/src/components/TableOfContents/TableOfContents.tsx @@ -72,12 +72,12 @@ export const TableOfContents = React.memo( return arr.map((item, key) => { let newItem = { ...item, - index: parentId + key + '#', + index: parentId + key + '-', }; // Process items array if it exists if (Array.isArray(item.items)) { - newItem.items = updateTocTree(item.items, parentId + key + '#'); + newItem.items = updateTocTree(item.items, parentId + key + '-'); } return newItem; diff --git a/packages/elements/src/components/API/__tests__/utils.test.ts b/packages/elements/src/components/API/__tests__/utils.test.ts index f255e1631..4a568b8ad 100644 --- a/packages/elements/src/components/API/__tests__/utils.test.ts +++ b/packages/elements/src/components/API/__tests__/utils.test.ts @@ -898,7 +898,7 @@ describe.each([ slug: `/${pathProp}/something/get`, title: '/something', type: nodeType, - index: '', + index: '0-', }, ], }, @@ -980,7 +980,7 @@ describe.each([ title: 'a', type: 'model', meta: '', - index: '', + index: '0-', }, ], }, @@ -1044,7 +1044,7 @@ describe.each([ slug: `/${pathProp}/something-else/post`, title: '/something-else', type: nodeType, - index: '', + index: '0-', }, ], }, diff --git a/packages/elements/src/components/API/utils.ts b/packages/elements/src/components/API/utils.ts index 208b7f77e..f62b83f10 100644 --- a/packages/elements/src/components/API/utils.ts +++ b/packages/elements/src/components/API/utils.ts @@ -182,7 +182,7 @@ const addTagGroupsToTree = ( title: node.name, type: node.type, meta: isHttpOperation(node.data) || isHttpWebhookOperation(node.data) ? node.data.method : '', - index: '', + index: '0-', }; }) .filter(Boolean); From 0ba88bb30acfe42f544b3dbac68935a0f75aecfd Mon Sep 17 00:00:00 2001 From: SB-venkatyadavilli Date: Thu, 27 Nov 2025 22:24:52 +0530 Subject: [PATCH 17/22] fix: added types --- .../TableOfContents/TableOfContents.tsx | 65 +++++-------------- .../src/components/TableOfContents/utils.ts | 4 +- 2 files changed, 17 insertions(+), 52 deletions(-) diff --git a/packages/elements-core/src/components/TableOfContents/TableOfContents.tsx b/packages/elements-core/src/components/TableOfContents/TableOfContents.tsx index 5ed0bd347..e73c824cd 100644 --- a/packages/elements-core/src/components/TableOfContents/TableOfContents.tsx +++ b/packages/elements-core/src/components/TableOfContents/TableOfContents.tsx @@ -20,11 +20,13 @@ import { TableOfContentsDivider, TableOfContentsGroup, TableOfContentsGroupItem, + TableOfContentsItem, TableOfContentsNode, TableOfContentsNodeGroup, TableOfContentsProps, } from './types'; import { + findFirstNode, getHtmlIdFromItemId, isDivider, isExternalLink, @@ -46,7 +48,7 @@ export const GroupContext = createContext({ // Provider component const GroupProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { - const [lastActiveIndex, setLastActiveIndex] = useState(''); // default value 0 + const [lastActiveIndex, setLastActiveIndex] = useState(''); const value = React.useMemo( () => ({ lastActiveIndex, @@ -67,44 +69,24 @@ export const TableOfContents = React.memo( isInResponsiveMode = false, onLinkClick, }) => { - const groupContext = React.useContext(GroupContext); - const updateTocTree = React.useCallback((arr: any[], parentId: string): any[] => { + const updateTocTree = React.useCallback((arr: TableOfContentsItem[], parentId: string): any[] => { return arr.map((item, key) => { - let newItem = { + let newItem: TableOfContentsItem = { ...item, index: parentId + key + '-', }; // Process items array if it exists - if (Array.isArray(item.items)) { - newItem.items = updateTocTree(item.items, parentId + key + '-'); + if (isGroup(item) || isNodeGroup(item)) { + (newItem as TableOfContentsGroup | TableOfContentsNodeGroup).items = updateTocTree( + item.items, + parentId + key + '-', + ); } return newItem; }); }, []); - - const getInitialValues = React.useCallback( - (tree: any[]): boolean => { - for (const item of tree) { - const shouldSetValues = - (Array.isArray(item?.items) && item.type === 'http_service') || Object.keys(item).length !== 1; - - if (shouldSetValues) { - groupContext?.setLastActiveIndex(item.index); - return true; - } - - if (Array.isArray(item?.items)) { - const found = getInitialValues(item.items); - if (found) return true; - } - } - - return false; - }, - [groupContext], - ); const updatedTree = updateTocTree(tree, ''); const container = React.useRef(null); @@ -192,29 +174,12 @@ const TOCContainer = React.memo<{ children: React.ReactNode; }>(({ children, updatedTree }) => { const groupContext = React.useContext(GroupContext); - const getInitialValues = React.useCallback( - (tree: any[]): boolean => { - for (const item of tree) { - const shouldSetValues = - (item?.items && item.type === 'http_service') || (!item?.items && Object.keys(item).length !== 1); - - if (shouldSetValues) { - groupContext?.setLastActiveIndex(item.index); - return true; - } - - if (item?.items) { - const found = getInitialValues(item.items); - if (found) return true; - } - } - - return false; - }, - [groupContext], - ); React.useEffect(() => { - getInitialValues(updatedTree); + const firstNode = findFirstNode(updatedTree); + if (firstNode) { + groupContext.setLastActiveIndex(firstNode.index); + } + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return {children}; diff --git a/packages/elements-core/src/components/TableOfContents/utils.ts b/packages/elements-core/src/components/TableOfContents/utils.ts index 3617712dc..97f719282 100644 --- a/packages/elements-core/src/components/TableOfContents/utils.ts +++ b/packages/elements-core/src/components/TableOfContents/utils.ts @@ -68,7 +68,7 @@ export function findFirstNode(items: TableOfContentsItem[]): TableOfContentsNode } export function isDivider(item: TableOfContentsItem): item is TableOfContentsDivider { - return Object.keys(item).length === 1 && 'title' in item; + return Object.keys(item).length === 2 && 'title' in item && 'index' in item; } export function isGroup(item: TableOfContentsItem): item is TableOfContentsGroup { return Object.keys(item).length >= 2 && 'title' in item && 'items' in item; @@ -80,5 +80,5 @@ export function isNode(item: TableOfContentsItem): item is TableOfContentsNode { return 'title' in item && 'slug' in item && 'id' in item && 'meta' in item && 'type' in item; } export function isExternalLink(item: TableOfContentsItem): item is TableOfContentsExternalLink { - return Object.keys(item).length === 2 && 'title' in item && 'url' in item; + return Object.keys(item).length === 3 && 'title' in item && 'url' in item && 'index' in item; } From bff902b4154e37272f9a3728a197d9a389c98622 Mon Sep 17 00:00:00 2001 From: SB-venkatyadavilli Date: Thu, 27 Nov 2025 22:28:45 +0530 Subject: [PATCH 18/22] chore(deps): bump up elements, elements-core & elements-dev-portal --- packages/elements-core/package.json | 2 +- packages/elements-dev-portal/package.json | 4 ++-- packages/elements/package.json | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/elements-core/package.json b/packages/elements-core/package.json index 006776595..4e5a4fefa 100644 --- a/packages/elements-core/package.json +++ b/packages/elements-core/package.json @@ -1,6 +1,6 @@ { "name": "@stoplight/elements-core", - "version": "9.0.12", + "version": "9.0.13", "sideEffects": [ "web-components.min.js", "src/web-components/**", diff --git a/packages/elements-dev-portal/package.json b/packages/elements-dev-portal/package.json index 1d795b5ff..0088440c0 100644 --- a/packages/elements-dev-portal/package.json +++ b/packages/elements-dev-portal/package.json @@ -1,6 +1,6 @@ { "name": "@stoplight/elements-dev-portal", - "version": "3.0.12", + "version": "3.0.13", "description": "UI components for composing beautiful developer documentation.", "keywords": [], "sideEffects": [ @@ -66,7 +66,7 @@ "dependencies": { "@stoplight/markdown-viewer": "^5.7.1", "@stoplight/mosaic": "^1.53.5", - "@stoplight/elements-core": "~9.0.12", + "@stoplight/elements-core": "~9.0.13", "@stoplight/path": "^1.3.2", "@stoplight/types": "^14.0.0", "classnames": "^2.2.6", diff --git a/packages/elements/package.json b/packages/elements/package.json index b8eeaec7f..cee4d949f 100644 --- a/packages/elements/package.json +++ b/packages/elements/package.json @@ -1,6 +1,6 @@ { "name": "@stoplight/elements", - "version": "9.0.12", + "version": "9.0.13", "description": "UI components for composing beautiful developer documentation.", "keywords": [], "sideEffects": [ @@ -63,7 +63,7 @@ ] }, "dependencies": { - "@stoplight/elements-core": "~9.0.12", + "@stoplight/elements-core": "~9.0.13", "@stoplight/http-spec": "^7.1.0", "@stoplight/json": "^3.18.1", "@stoplight/mosaic": "^1.53.5", @@ -109,4 +109,4 @@ "release": { "extends": "@stoplight/scripts/release" } -} \ No newline at end of file +} From 2a1c227295ef63663486e73fda80506253d46e8a Mon Sep 17 00:00:00 2001 From: Pankaj Yadav Date: Fri, 28 Nov 2025 13:25:43 +0530 Subject: [PATCH 19/22] Updated the resourse_class from xlarge to medium in the config.yml --- .circleci/config.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index fc1fe9515..b8d196c60 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -25,7 +25,7 @@ jobs: working_directory: /mnt/ramdisk/project docker: - image: cimg/node:18.20 - resource_class: xlarge + resource_class: medium environment: CYPRESS_CACHE_FOLDER: /mnt/ramdisk/.cache/Cypress YARN_CACHE_FOLDER: /mnt/ramdisk/.cache/yarn @@ -47,7 +47,7 @@ jobs: - run: yarn build.docs run-e2e-tests: executor: cypress-default - resource_class: xlarge + resource_class: medium working_directory: /mnt/ramdisk/project environment: CYPRESS_CACHE_FOLDER: /mnt/ramdisk/.cache/Cypress From 42bc51ddea69c28f83529d9b261a9d260697b20a Mon Sep 17 00:00:00 2001 From: SB-venkatyadavilli Date: Fri, 28 Nov 2025 13:55:38 +0530 Subject: [PATCH 20/22] .circleci config file modified --- .circleci/config.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index b8d196c60..667b1f796 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -25,7 +25,7 @@ jobs: working_directory: /mnt/ramdisk/project docker: - image: cimg/node:18.20 - resource_class: medium + resource_class: large environment: CYPRESS_CACHE_FOLDER: /mnt/ramdisk/.cache/Cypress YARN_CACHE_FOLDER: /mnt/ramdisk/.cache/yarn @@ -47,7 +47,7 @@ jobs: - run: yarn build.docs run-e2e-tests: executor: cypress-default - resource_class: medium + resource_class: large working_directory: /mnt/ramdisk/project environment: CYPRESS_CACHE_FOLDER: /mnt/ramdisk/.cache/Cypress From 89efd3ebffcb86cadc667cf12c72f5bded77b41c Mon Sep 17 00:00:00 2001 From: SB-venkatyadavilli Date: Fri, 28 Nov 2025 14:29:39 +0530 Subject: [PATCH 21/22] fix: added index property --- packages/elements/src/components/API/utils.ts | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/elements/src/components/API/utils.ts b/packages/elements/src/components/API/utils.ts index f62b83f10..143002a96 100644 --- a/packages/elements/src/components/API/utils.ts +++ b/packages/elements/src/components/API/utils.ts @@ -173,19 +173,19 @@ 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 : '', - index: '0-', - }; - }) - .filter(Boolean); + 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 : '', + index: '0-', + }; + }); if (items.length > 0) { tree.push({ From 5a2bef45161353593675362cec9846e07977759c Mon Sep 17 00:00:00 2001 From: SB-venkatyadavilli Date: Mon, 1 Dec 2025 12:55:45 +0530 Subject: [PATCH 22/22] fix: added all properties into single ContextAPI and removed 2nd ContextAPI. --- .../TableOfContents/TableOfContents.tsx | 102 +++++++++--------- .../src/components/TableOfContents/types.ts | 4 +- 2 files changed, 52 insertions(+), 54 deletions(-) diff --git a/packages/elements-core/src/components/TableOfContents/TableOfContents.tsx b/packages/elements-core/src/components/TableOfContents/TableOfContents.tsx index e73c824cd..1842ad1eb 100644 --- a/packages/elements-core/src/components/TableOfContents/TableOfContents.tsx +++ b/packages/elements-core/src/components/TableOfContents/TableOfContents.tsx @@ -1,7 +1,7 @@ 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 { useState } from 'react'; import { useFirstRender } from '../../hooks/useFirstRender'; import { resolveRelativeLink } from '../../utils/string'; @@ -15,8 +15,8 @@ import { NODE_TYPE_TITLE_ICON, } from './constants'; import { + ActiveItemContextType, CustomLinkComponent, - GroupContextType, TableOfContentsDivider, TableOfContentsGroup, TableOfContentsGroupItem, @@ -36,29 +36,14 @@ import { isNodeGroup, } from './utils'; -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({ +const ActiveItemContext = React.createContext({ + activeId: undefined, lastActiveIndex: '', setLastActiveIndex: () => {}, }); +const LinkContext = React.createContext(undefined); +LinkContext.displayName = 'LinkContext'; -// Provider component -const GroupProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { - const [lastActiveIndex, setLastActiveIndex] = useState(''); - const value = React.useMemo( - () => ({ - lastActiveIndex, - setLastActiveIndex, - }), - [lastActiveIndex], - ); - - return {children}; -}; export const TableOfContents = React.memo( ({ tree, @@ -69,6 +54,16 @@ export const TableOfContents = React.memo( isInResponsiveMode = false, onLinkClick, }) => { + const [lastActiveIndex, setLastActiveIndex] = useState(''); + const value = React.useMemo( + () => ({ + lastActiveIndex, + setLastActiveIndex, + activeId, + }), + [lastActiveIndex, activeId], + ); + const updateTocTree = React.useCallback((arr: TableOfContentsItem[], parentId: string): any[] => { return arr.map((item, key) => { let newItem: TableOfContentsItem = { @@ -118,28 +113,26 @@ 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 ( + + ); + })} + + @@ -173,11 +166,11 @@ const TOCContainer = React.memo<{ updatedTree: TableOfContentsGroupItem[]; children: React.ReactNode; }>(({ children, updatedTree }) => { - const groupContext = React.useContext(GroupContext); + const { setLastActiveIndex } = React.useContext(ActiveItemContext); React.useEffect(() => { const firstNode = findFirstNode(updatedTree); if (firstNode) { - groupContext.setLastActiveIndex(firstNode.index); + setLastActiveIndex(firstNode.index); } // eslint-disable-next-line react-hooks/exhaustive-deps @@ -259,22 +252,26 @@ const Group = React.memo<{ isInResponsiveMode?: boolean; onLinkClick?(): void; }>(({ depth, item, maxDepthOpenByDefault, isInResponsiveMode, onLinkClick = () => {} }) => { - const activeId = React.useContext(ActiveIdContext); - const { lastActiveIndex } = React.useContext(GroupContext); + const { activeId, lastActiveIndex } = React.useContext(ActiveItemContext); const [isOpen, setIsOpen] = React.useState(() => isGroupOpenByDefault(depth, item, activeId, maxDepthOpenByDefault)); const isActiveGroup = React.useCallback( - (items: any[], activeId: string | undefined, contextIndex: string): boolean => { + (items: TableOfContentsGroupItem[], activeId: string | undefined, contextIndex: string): boolean => { return items.some(element => { const hasSlugOrId = 'slug' in element || 'id' in element; - const hasItems = Array.isArray(element.items); + const hasItems = 'items' in element && Array.isArray((element as any).items); if (!hasSlugOrId && !hasItems) return false; - if (activeId && (activeId === element.slug || activeId === element.id) && contextIndex === element.index) { + if ( + activeId && + 'index' in element && + ((element as any).slug === activeId || (element as any).id === activeId) && + (element as any).index === contextIndex + ) { return true; } - return hasItems ? isActiveGroup(element.items, activeId, contextIndex) : false; + return hasItems ? isActiveGroup((element as any).items, activeId, contextIndex) : false; }); }, [], @@ -432,8 +429,7 @@ const Node = React.memo<{ onClick?: (e: React.MouseEvent, forceOpen?: boolean) => void; onLinkClick?(): void; }>(({ item, depth, meta, showAsActive, isInResponsiveMode, onClick, onLinkClick = () => {} }) => { - const activeId = React.useContext(ActiveIdContext); - const { lastActiveIndex, setLastActiveIndex } = React.useContext(GroupContext); + const { activeId, lastActiveIndex, setLastActiveIndex } = React.useContext(ActiveItemContext); const { index } = item; const isSlugMatched = activeId === item.slug || activeId === item.id; const isActive = lastActiveIndex === index && isSlugMatched; diff --git a/packages/elements-core/src/components/TableOfContents/types.ts b/packages/elements-core/src/components/TableOfContents/types.ts index 71b00004b..69f433cfd 100644 --- a/packages/elements-core/src/components/TableOfContents/types.ts +++ b/packages/elements-core/src/components/TableOfContents/types.ts @@ -51,7 +51,9 @@ export type TableOfContentsNode< }; export type TableOfContentsNodeGroup = TableOfContentsNode<'http_service'> & TableOfContentsGroup; -export type GroupContextType = { + +export type ActiveItemContextType = { + activeId: string | undefined; lastActiveIndex: string; setLastActiveIndex: React.Dispatch>; };