From a63fa17e20de744812bcef6ee27a09952d6c78c7 Mon Sep 17 00:00:00 2001 From: Anthony LC Date: Thu, 11 Jun 2026 22:21:43 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B(frontend)=20fix=20removed=20item?= =?UTF-8?q?=20in=20the=20tree?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit react-arborist's scrollTo calls react-window's scrollToItem, which mutates the internal scrollOffset state. When navigating to a deep item in a large tree, this causes all items above the target to be removed from the DOM (virtualized away), making the tree appear empty above the selected node. We no-op it to prevent that — the panel's own overflow-y handles scrolling. --- CHANGELOG.md | 5 ++ .../docs/doc-tree/components/DocTree.tsx | 54 ++++++++++++++++++- 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b5fd146f..efbd16d59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,11 @@ and this project adheres to ## [Unreleased] +### Fixed + +- 🐛(frontend) fix removed item in the tree #2420 + + ## [v5.3.0] - 2026-06-19 ### Added diff --git a/src/frontend/apps/impress/src/features/docs/doc-tree/components/DocTree.tsx b/src/frontend/apps/impress/src/features/docs/doc-tree/components/DocTree.tsx index 5180d757d..3ce915244 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-tree/components/DocTree.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-tree/components/DocTree.tsx @@ -7,7 +7,13 @@ import { useTreeContext, } from '@gouvfr-lasuite/ui-kit'; import { useRouter } from 'next/navigation'; -import { useCallback, useEffect, useRef, useState } from 'react'; +import { + useCallback, + useEffect, + useLayoutEffect, + useRef, + useState, +} from 'react'; import { useTranslation } from 'react-i18next'; import { css } from 'styled-components'; @@ -258,6 +264,52 @@ export const DocTree = ({ currentDoc }: DocTreeProps) => { } }, [currentDoc, treeContext]); + /** + * react-arborist's scrollTo calls react-window's scrollToItem, which mutates + * the internal scrollOffset state. When navigating to a deep item in a large + * tree, this causes all items above the target to be removed from the DOM + * (virtualized away), making the tree appear empty above the selected node. + * We no-op it to prevent that — the panel's own overflow-y handles scrolling. + */ + const treeApiRef = treeContext?.treeApiRef; + useLayoutEffect(() => { + if (!treeRoot || !treeApiRef?.current) { + return; + } + const api = treeApiRef.current as unknown as Record; + const origScrollTo = api['scrollTo']; + if (typeof origScrollTo !== 'function') { + return; + } + api['scrollTo'] = () => {}; + return () => { + api['scrollTo'] = origScrollTo; + }; + }, [treeRoot, treeApiRef]); + + /** + * On initial tree load, scroll the panel to show the current document. + * This fires once when initialOpenState is first set (tree data just loaded). + * It does not re-fire on user navigation — clicked items are already in view. + */ + useEffect(() => { + if (!treeRoot || !initialOpenState) { + return; + } + + const timeoutId = setTimeout(() => { + treeRoot + .querySelector( + `[data-testid="doc-sub-page-item-${currentDoc.id}"]`, + ) + ?.scrollIntoView({ block: 'nearest', behavior: 'smooth' }); + }, 100); + + return () => { + clearTimeout(timeoutId); + }; + }, [treeRoot, initialOpenState, currentDoc.id]); + if (!treeContext || !treeContext.root) { return ; }