From 96698cc801a65c1d00a20817afec907a6278e6d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nicole=20miko=C5=82ajczyk?= Date: Wed, 17 Sep 2025 22:03:01 +0200 Subject: [PATCH] pl-fe: try to improve hotkeys scroll, reuse code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: nicole mikołajczyk --- packages/pl-fe/src/columns/notifications.tsx | 20 ++---------- packages/pl-fe/src/columns/search.tsx | 20 ++---------- packages/pl-fe/src/components/status-list.tsx | 20 ++---------- .../components/conversations-list.tsx | 20 ++---------- .../src/features/status/components/thread.tsx | 32 ++++++------------- .../src/pages/statuses/event-discussion.tsx | 20 ++---------- packages/pl-fe/src/utils/scroll-utils.ts | 21 ++++++++++++ 7 files changed, 45 insertions(+), 108 deletions(-) create mode 100644 packages/pl-fe/src/utils/scroll-utils.ts diff --git a/packages/pl-fe/src/columns/notifications.tsx b/packages/pl-fe/src/columns/notifications.tsx index 0c2f449f7..d3353fe3b 100644 --- a/packages/pl-fe/src/columns/notifications.tsx +++ b/packages/pl-fe/src/columns/notifications.tsx @@ -23,6 +23,7 @@ import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch'; import { useAppSelector } from 'pl-fe/hooks/use-app-selector'; import { useFeatures } from 'pl-fe/hooks/use-features'; import { useSettings } from 'pl-fe/hooks/use-settings'; +import { selectChild } from 'pl-fe/utils/scroll-utils'; import type { Item } from 'pl-fe/components/ui/tabs'; import type { RootState } from 'pl-fe/store'; @@ -182,27 +183,12 @@ const NotificationsColumn = () => { const handleMoveUp = (id: string) => { const elementIndex = displayedNotifications.findIndex(item => item !== null && item.group_key === id) - 1; - _selectChild(elementIndex); + selectChild(elementIndex, node); }; const handleMoveDown = (id: string) => { const elementIndex = displayedNotifications.findIndex(item => item !== null && item.group_key === id) + 1; - _selectChild(elementIndex); - }; - - const _selectChild = (index: number) => { - const selector = `[data-index="${index}"] .focusable`; - const element = document.querySelector(selector); - - if (element) element.focus(); - - node.current?.scrollIntoView({ - index, - behavior: 'smooth', - done: () => { - if (!element) document.querySelector(selector)?.focus(); - }, - }); + selectChild(elementIndex, node); }; const handleDequeueNotifications = useCallback(() => { diff --git a/packages/pl-fe/src/columns/search.tsx b/packages/pl-fe/src/columns/search.tsx index 5266bf125..863bd1f51 100644 --- a/packages/pl-fe/src/columns/search.tsx +++ b/packages/pl-fe/src/columns/search.tsx @@ -10,6 +10,7 @@ import PlaceholderAccount from 'pl-fe/features/placeholder/components/placeholde import PlaceholderHashtag from 'pl-fe/features/placeholder/components/placeholder-hashtag'; import PlaceholderStatus from 'pl-fe/features/placeholder/components/placeholder-status'; import { useSearchAccounts, useSearchHashtags, useSearchStatuses } from 'pl-fe/queries/search/use-search'; +import { selectChild } from 'pl-fe/utils/scroll-utils'; import TrendsColumn from './trends'; @@ -46,29 +47,14 @@ const SearchColumn: React.FC = ({ type, query, accountId, multiCo if (!resultsIds) return; const elementIndex = getCurrentIndex(id) - 1; - selectChild(elementIndex); + selectChild(elementIndex, node, document.getElementById('search-results') || undefined); }; const handleMoveDown = (id: string) => { if (!resultsIds) return; const elementIndex = getCurrentIndex(id) + 1; - selectChild(elementIndex); - }; - - const selectChild = (index: number) => { - const selector = `#search-results [data-index="${index}"] .focusable`; - const element = document.querySelector(selector); - - if (element) element.focus(); - - node.current?.scrollIntoView({ - index, - behavior: 'smooth', - done: () => { - if (!element) document.querySelector(selector)?.focus(); - }, - }); + selectChild(elementIndex, node, document.getElementById('search-results') || undefined); }; const handleLoadMore = () => activeQuery.fetchNextPage({ cancelRefetch: false }); diff --git a/packages/pl-fe/src/components/status-list.tsx b/packages/pl-fe/src/components/status-list.tsx index be38d13d5..ebf66415e 100644 --- a/packages/pl-fe/src/components/status-list.tsx +++ b/packages/pl-fe/src/components/status-list.tsx @@ -10,6 +10,7 @@ import Text from 'pl-fe/components/ui/text'; import StatusContainer from 'pl-fe/containers/status-container'; import PlaceholderStatus from 'pl-fe/features/placeholder/components/placeholder-status'; import PendingStatus from 'pl-fe/features/ui/components/pending-status'; +import { selectChild } from 'pl-fe/utils/scroll-utils'; import type { VirtuosoHandle } from 'react-virtuoso'; @@ -65,12 +66,12 @@ const StatusList: React.FC = ({ const handleMoveUp = (id: string, featured: boolean = false) => { const elementIndex = getCurrentStatusIndex(id, featured) - 1; - selectChild(elementIndex); + selectChild(elementIndex, node, document.getElementById('status-list') || undefined); }; const handleMoveDown = (id: string, featured: boolean = false) => { const elementIndex = getCurrentStatusIndex(id, featured) + 1; - selectChild(elementIndex); + selectChild(elementIndex, node, document.getElementById('status-list') || undefined); }; const handleLoadOlder = useCallback(debounce(() => { @@ -80,21 +81,6 @@ const StatusList: React.FC = ({ } }, 300, { leading: true }), [onLoadMore, lastStatusId, statusIds.at(-1)]); - const selectChild = (index: number) => { - const selector = `#status-list [data-index="${index}"] .focusable`; - const element = document.querySelector(selector); - - if (element) element.focus(); - - node.current?.scrollIntoView({ - index, - behavior: 'smooth', - done: () => { - if (!element) document.querySelector(selector)?.focus(); - }, - }); - }; - const renderLoadGap = (index: number) => { const ids = statusIds; const nextId = ids[index + 1]; diff --git a/packages/pl-fe/src/features/conversations/components/conversations-list.tsx b/packages/pl-fe/src/features/conversations/components/conversations-list.tsx index 9a2dca048..6a767c586 100644 --- a/packages/pl-fe/src/features/conversations/components/conversations-list.tsx +++ b/packages/pl-fe/src/features/conversations/components/conversations-list.tsx @@ -6,6 +6,7 @@ import { expandConversations } from 'pl-fe/actions/conversations'; import ScrollableList from 'pl-fe/components/scrollable-list'; import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch'; import { useAppSelector } from 'pl-fe/hooks/use-app-selector'; +import { selectChild } from 'pl-fe/utils/scroll-utils'; import Conversation from './conversation'; @@ -23,27 +24,12 @@ const ConversationsList: React.FC = () => { const handleMoveUp = (id: string) => { const elementIndex = getCurrentIndex(id) - 1; - selectChild(elementIndex); + selectChild(elementIndex, ref, document.getElementById('direct-list') || undefined); }; const handleMoveDown = (id: string) => { const elementIndex = getCurrentIndex(id) + 1; - selectChild(elementIndex); - }; - - const selectChild = (index: number) => { - const selector = `#direct-list [data-index="${index}"] .focusable`; - const element = document.querySelector(selector); - - if (element) element.focus(); - - ref.current?.scrollIntoView({ - index, - behavior: 'smooth', - done: () => { - if (!element) document.querySelector(selector)?.focus(); - }, - }); + selectChild(elementIndex, ref, document.getElementById('direct-list') || undefined); }; const handleLoadOlder = debounce(() => { diff --git a/packages/pl-fe/src/features/status/components/thread.tsx b/packages/pl-fe/src/features/status/components/thread.tsx index 5f5a28bdd..4f81f2c09 100644 --- a/packages/pl-fe/src/features/status/components/thread.tsx +++ b/packages/pl-fe/src/features/status/components/thread.tsx @@ -20,6 +20,7 @@ import { RootState } from 'pl-fe/store'; import { useModalsStore } from 'pl-fe/stores/modals'; import { useSettingsStore } from 'pl-fe/stores/settings'; import { useStatusMetaStore } from 'pl-fe/stores/status-meta'; +import { selectChild } from 'pl-fe/utils/scroll-utils'; import { textForScreenReader } from 'pl-fe/utils/status'; import DetailedStatus from './detailed-status'; @@ -241,52 +242,37 @@ const Thread = ({ }; const handleMoveUp = (id: string) => { + const modalOffset = isModal ? 1 : 0; if (id === status.id) { - _selectChild(statusIndex - 1); + selectChild(statusIndex - 1 + modalOffset, scroller, node.current || undefined); } else { let index = thread.indexOf(id); if (index === -1) { index = thread.indexOf(id); - _selectChild(index); + selectChild(index + modalOffset, scroller, node.current || undefined); } else { - _selectChild(index - 1); + selectChild(index - 1 + modalOffset, scroller, node.current || undefined); } } }; const handleMoveDown = (id: string) => { + const modalOffset = isModal ? 1 : 0; if (id === status.id) { - _selectChild(statusIndex + 1); + selectChild(statusIndex + 1 + modalOffset, scroller, node.current || undefined); } else { let index = thread.indexOf(id); if (index === -1) { index = thread.indexOf(id); - _selectChild(index); + selectChild(index + modalOffset, scroller, node.current || undefined); } else { - _selectChild(index + 1); + selectChild(index + 1 + modalOffset, scroller, node.current || undefined); } } }; - const _selectChild = (index: number) => { - if (isModal) index = index + 1; - - const selector = `[data-index="${index}"] .focusable`; - const element = node.current?.querySelector(selector); - - if (element) element.focus(); - - scroller.current?.scrollIntoView({ - index, - behavior: 'smooth', - done: () => { - if (!element) node.current?.querySelector(selector)?.focus(); - }, - }); - }; - const renderTombstone = (id: string) => (
= ({ params: { statusId: s const handleMoveUp = (id: string) => { const index = descendantsIds.indexOf(id); - _selectChild(index - 1); + selectChild(index - 1, scroller, node.current || undefined); }; const handleMoveDown = (id: string) => { const index = descendantsIds.indexOf(id); - _selectChild(index + 1); - }; - - const _selectChild = (index: number) => { - const selector = `#thread [data-index="${index}"] .focusable`; - const element = document.querySelector(selector); - - if (element) element.focus(); - - scroller.current?.scrollIntoView({ - index, - behavior: 'smooth', - done: () => { - if (!element) document.querySelector(selector)?.focus(); - }, - }); + selectChild(index + 1, scroller, node.current || undefined); }; const renderTombstone = (id: string) => ( diff --git a/packages/pl-fe/src/utils/scroll-utils.ts b/packages/pl-fe/src/utils/scroll-utils.ts new file mode 100644 index 000000000..9a302d0a4 --- /dev/null +++ b/packages/pl-fe/src/utils/scroll-utils.ts @@ -0,0 +1,21 @@ +import type React from 'react'; +import type { VirtuosoHandle } from 'react-virtuoso'; + +const selectChild = (index: number, handle: React.RefObject, node: ParentNode = document) => { + const selector = `[data-index="${index}"] .focusable`; + const element = node.querySelector(selector); + + if (element) { + element.focus({ preventScroll: false }); + } else { + handle.current?.scrollIntoView({ + index, + behavior: 'smooth', + done: () => { + if (!element) document.querySelector(selector)?.focus(); + }, + }); + } +}; + +export { selectChild };