From 2b7931732afa67eac8903c4ccad4f3389bbd98d0 Mon Sep 17 00:00:00 2001 From: Sergey Kozyrenko Date: Tue, 7 Jul 2026 21:24:33 +0700 Subject: [PATCH] refactor(markdown-editor): drop the toolbar roving-focus childList observation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The roving-tabindex MutationObserver watched childList:true/subtree:true, justified by a comment claiming "the table control swaps button↔menu". Verified against the source: the toolbar's item set is static (every control renders unconditionally; state only drives pressed/disabled props), and each menu keeps its `data-toolbar-item` trigger in the bar while swapping its content in a Radix body portal — outside the observed subtree. So childList observed nothing. Keep only the load-bearing attributeFilter:['disabled'] + subtree (a disabled toggle changes the roving set), and correct the comment. Co-Authored-By: Claude Opus 4.8 --- .../markdown-editor/markdown-editor-toolbar.tsx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/shared/markdown-editor/markdown-editor-toolbar.tsx b/frontend/src/components/shared/markdown-editor/markdown-editor-toolbar.tsx index 76a72aeb..c3bb5308 100644 --- a/frontend/src/components/shared/markdown-editor/markdown-editor-toolbar.tsx +++ b/frontend/src/components/shared/markdown-editor/markdown-editor-toolbar.tsx @@ -75,10 +75,11 @@ function useHorizontalWheelScroll(ref: React.RefObject) { } // WAI-ARIA toolbar pattern: one Tab stop for the whole bar, Arrow/Home/End move between controls. Managed -// imperatively on `[data-toolbar-item]` so each control stays a dumb button; the set changes (the table -// control swaps button↔menu, controls disable) so a MutationObserver re-seeds the single tab stop. When a -// popover/dropdown is open its focus lives in a body portal outside the bar, so activeElement isn't an item -// and Arrow keys fall through to that menu instead of being hijacked here. +// imperatively on `[data-toolbar-item]` so each control stays a dumb button. The item set is static, but a +// control's `disabled` toggling changes which items are in the roving set, so a MutationObserver on the +// `disabled` attribute re-seeds the single tab stop (menus swap their content in a body portal, not here, so +// no childList watch is needed). When a popover/dropdown is open its focus lives in that portal outside the +// bar, so activeElement isn't an item and Arrow keys fall through to the menu instead of being hijacked here. function useToolbarRovingFocus(ref: React.RefObject) { useEffect(() => { const toolbar = ref.current; @@ -104,7 +105,7 @@ function useToolbarRovingFocus(ref: React.RefObject) { seedTabStop(); const observer = new MutationObserver(seedTabStop); - observer.observe(toolbar, { attributeFilter: ['disabled'], childList: true, subtree: true }); + observer.observe(toolbar, { attributeFilter: ['disabled'], subtree: true }); const handleKeyDown = (event: KeyboardEvent) => { if (!['ArrowLeft', 'ArrowRight', 'End', 'Home'].includes(event.key)) {