From db66a967c6082c2bdfeafce397cac79ad66d9f25 Mon Sep 17 00:00:00 2001 From: "mkhutornyi (via MelvinBot)" Date: Sat, 4 Jul 2026 19:00:33 +0000 Subject: [PATCH 1/6] Fix scroll/reorder jump when selecting in Search In filter Co-authored-by: mkhutornyi --- .../Search/FilterComponents/InSelector.tsx | 56 +++++++++++++------ 1 file changed, 39 insertions(+), 17 deletions(-) diff --git a/src/components/Search/FilterComponents/InSelector.tsx b/src/components/Search/FilterComponents/InSelector.tsx index 30242c8e2c64..03256715dd35 100644 --- a/src/components/Search/FilterComponents/InSelector.tsx +++ b/src/components/Search/FilterComponents/InSelector.tsx @@ -7,6 +7,7 @@ import type {TextInputOptions} from '@components/SelectionList/types'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useDebouncedState from '@hooks/useDebouncedState'; import useFilteredOptions from '@hooks/useFilteredOptions'; +import useInitialValue from '@hooks/useInitialValue'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import usePrivateIsArchivedMap from '@hooks/usePrivateIsArchivedMap'; @@ -74,24 +75,27 @@ function InSelector({value = [], selectionListTextInputStyle, selectionListStyle const [conciergeReportID] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID); const [isTrackIntentUser] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED, {selector: isTrackIntentUserSelector}); - const selectedOptions: OptionData[] = value.map((id) => { + const buildReportOption = (id: string, isSelected: boolean): OptionData => { const privateIsArchived = privateIsArchivedMap[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${id}`]; const reportData = reports?.[`${ONYXKEYS.COLLECTION.REPORT}${id}`]; const reportPolicy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${reportData?.policyID}`]; - const report = getSelectedOptionData( - createOptionFromReport( - {...reportData, reportID: id}, - personalDetails, - privateIsArchived, - reportPolicy, - sortedActions, - reportAttributesDerived, - undefined, - undefined, - undefined, - isTrackIntentUser, + const report = { + ...getSelectedOptionData( + createOptionFromReport( + {...reportData, reportID: id}, + personalDetails, + privateIsArchived, + reportPolicy, + sortedActions, + reportAttributesDerived, + undefined, + undefined, + undefined, + isTrackIntentUser, + ), ), - ); + isSelected, + }; const isReportArchived = !!privateIsArchived; const policy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${reportData?.policyID}`]; const reportPolicyTags = policyTags?.[`${ONYXKEYS.COLLECTION.POLICY_TAGS}${getNonEmptyStringOnyxID(report?.policyID)}`]; @@ -101,7 +105,14 @@ function InSelector({value = [], selectionListTextInputStyle, selectionListStyle {isReportArchived, personalDetails, policy, reportAttributesDerived, policyTags: reportPolicyTags, conciergeReportID, isTrackIntentUser}, ); return {...report, alternateText}; - }); + }; + + const selectedOptions: OptionData[] = value.map((id) => buildReportOption(id, true)); + + // Snapshot the reports that were already selected when the filter first opened. On a long list these stay + // pinned to the top so they're easy to find, but items toggled during the current session are NOT re-pinned: + // they keep their position so selecting doesn't scroll/jump the list (https://github.com/Expensify/App/issues/61414). + const initialValue = useInitialValue(() => value); const defaultOptions = isLoading || !ready || !options @@ -130,9 +141,16 @@ function InSelector({value = [], selectionListTextInputStyle, selectionListStyle const sections: SelectionListSections = []; if (!isLoading) { + // Only float the initially-selected reports to the top of a long list. Keying the pinned section on the + // snapshot (instead of the live `value`) means items toggled during this session stay put (see #61414). + const shouldMoveSelectedToTop = chatOptions.recentReports.length >= CONST.STANDARD_LIST_ITEM_LIMIT; + const pinnedReportIDs = shouldMoveSelectedToTop ? initialValue : []; + const pinnedReportIDSet = new Set(pinnedReportIDs); + const pinnedSelectedOptions = pinnedReportIDs.map((id) => buildReportOption(id, value.includes(id))); + const formattedResults = formatSectionsFromSearchTerm( cleanSearchTerm, - selectedOptions, + cleanSearchTerm === '' ? pinnedSelectedOptions : selectedOptions, chatOptions.recentReports, chatOptions.personalDetails, privateIsArchivedMap, @@ -146,8 +164,12 @@ function InSelector({value = [], selectionListTextInputStyle, selectionListStyle sections.push(formattedResults.section); + // Mark reports as selected in place based on the live `value`, so the checkmark toggles without reordering. + // When the search term is empty, drop only the pinned reports (they already appear in the top section above). const visibleReportsWhenSearchTermNonEmpty = chatOptions.recentReports.map((report) => (value.includes(report.reportID) ? getSelectedOptionData(report) : report)); - const visibleReportsWhenSearchTermEmpty = chatOptions.recentReports.filter((report) => !value.includes(report.reportID)); + const visibleReportsWhenSearchTermEmpty = chatOptions.recentReports + .filter((report) => !pinnedReportIDSet.has(report.reportID)) + .map((report) => (value.includes(report.reportID) ? getSelectedOptionData(report) : report)); const reportsFiltered = cleanSearchTerm === '' ? visibleReportsWhenSearchTermEmpty : visibleReportsWhenSearchTermNonEmpty; sections.push({ From 0f9386a570288d3ef6c68361d1072cf2b22144e3 Mon Sep 17 00:00:00 2001 From: "mkhutornyi (via MelvinBot)" Date: Sat, 4 Jul 2026 19:06:07 +0000 Subject: [PATCH 2/6] Add regression test for In filter scroll/reorder jump Co-authored-by: mkhutornyi --- tests/ui/InSelectorTest.tsx | 123 ++++++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 tests/ui/InSelectorTest.tsx diff --git a/tests/ui/InSelectorTest.tsx b/tests/ui/InSelectorTest.tsx new file mode 100644 index 000000000000..6be719d50fc4 --- /dev/null +++ b/tests/ui/InSelectorTest.tsx @@ -0,0 +1,123 @@ +import {render} from '@testing-library/react-native'; + +import InSelector from '@components/Search/FilterComponents/InSelector'; +import SelectionListWithSections from '@components/SelectionList/SelectionListWithSections'; + +import CONST from '@src/CONST'; + +import React from 'react'; + +// The list of reports returned by the (mocked) options pipeline. Tests mutate this to switch between a +// long list (pinning enabled) and a short list (pinning disabled). +let mockRecentReports: Array<{reportID: string; keyForList: string; text: string}> = []; + +jest.mock('@components/SelectionList/SelectionListWithSections', () => jest.fn(() => null)); +jest.mock('@components/SelectionList/ListItem/InviteMemberListItem', () => jest.fn(() => null)); +jest.mock('@components/Search/FilterComponents/ListFilterViewWrapper', () => jest.fn(({children}: {children: React.ReactNode}) => children)); + +jest.mock('@components/OnyxListItemProvider', () => ({usePersonalDetails: jest.fn(() => ({}))})); +jest.mock('@hooks/useOnyx', () => jest.fn(() => [undefined])); +jest.mock('@hooks/useDebouncedState', () => jest.fn(() => ['', '', jest.fn()])); +jest.mock('@hooks/useFilteredOptions', () => jest.fn(() => ({options: {recentReports: [], personalDetails: []}, isLoading: false}))); +jest.mock('@hooks/useCurrentUserPersonalDetails', () => jest.fn(() => ({email: 'me@expensify.com', accountID: 999}))); +jest.mock('@hooks/useSortedActions', () => jest.fn(() => ({}))); +jest.mock('@hooks/useReportAttributes', () => jest.fn(() => ({}))); +jest.mock('@hooks/usePrivateIsArchivedMap', () => jest.fn(() => ({}))); +jest.mock('@hooks/useLocalize', () => + jest.fn(() => ({ + translate: (key: string) => key, + })), +); +jest.mock('@libs/actions/Report', () => ({searchInServer: jest.fn()})); +jest.mock('@libs/DeviceCapabilities', () => ({canUseTouchScreen: () => false})); +jest.mock('@libs/OptionsListUtils', () => ({ + // Build a minimal option object from the report id the component passes in. + createOptionFromReport: (report: {reportID: string}) => ({reportID: report.reportID, keyForList: report.reportID, text: `Report ${report.reportID}`}), + getSearchOptions: () => ({options: {recentReports: [], personalDetails: []}}), + filterAndOrderOptions: () => ({recentReports: mockRecentReports, personalDetails: []}), + // Mirror the real helper for an empty search term: the passed-in selectedOptions become the top section verbatim. + formatSectionsFromSearchTerm: (searchTerm: string, selectedOptions: unknown[]) => ({section: {sectionIndex: 0, data: selectedOptions}}), + getAlternateText: () => '', +})); + +type SectionData = {reportID?: string; keyForList: string; isSelected?: boolean}; + +describe('InSelector', () => { + const mockedSelectionList = jest.mocked(SelectionListWithSections); + + const buildRecentReports = (count: number) => + Array.from({length: count}, (_, index) => ({ + reportID: `r${index}`, + keyForList: `r${index}`, + text: `Report ${index}`, + })); + + const getSections = () => mockedSelectionList.mock.lastCall?.[0].sections as Array<{data: SectionData[]}>; + + beforeEach(() => { + mockedSelectionList.mockClear(); + // A long list so the "move selected to top" behavior is enabled. + mockRecentReports = buildRecentReports(CONST.STANDARD_LIST_ITEM_LIMIT + 2); + }); + + it('floats the initially-selected report to the top of a long list', () => { + render( + , + ); + + const sections = getSections(); + // Top section holds only the pre-selected report. + expect(sections.at(0)?.data.map((item) => item.keyForList)).toEqual(['r10']); + // Main section keeps every other report in its natural order, with the pinned one removed. + expect(sections.at(1)?.data.map((item) => item.keyForList)).not.toContain('r10'); + expect(sections.at(1)?.data).toHaveLength(CONST.STANDARD_LIST_ITEM_LIMIT + 1); + }); + + it('keeps a report in place when it is toggled after first render (does not jump to the top)', () => { + const {rerender} = render( + , + ); + + // Simulate the user toggling another report on: the parent re-renders the filter with the updated value. + rerender( + , + ); + + const sections = getSections(); + // Only the originally pre-selected report stays pinned at the top. + expect(sections.at(0)?.data.map((item) => item.keyForList)).toEqual(['r10']); + + // The newly toggled report is marked selected but stays in its natural position (index 3) instead of jumping up. + const mainSection = sections.at(1)?.data ?? []; + expect(mainSection.findIndex((item) => item.keyForList === 'r3')).toBe(3); + expect(mainSection.find((item) => item.keyForList === 'r3')?.isSelected).toBe(true); + }); + + it('does not pin selected reports to the top of a short list', () => { + mockRecentReports = buildRecentReports(5); + + render( + , + ); + + const sections = getSections(); + // No pinned section for a short list. + expect(sections.at(0)?.data).toHaveLength(0); + // Every report stays in place; the selected one is simply marked in position. + const mainSection = sections.at(1)?.data ?? []; + expect(mainSection.map((item) => item.keyForList)).toEqual(['r0', 'r1', 'r2', 'r3', 'r4']); + expect(mainSection.find((item) => item.keyForList === 'r2')?.isSelected).toBe(true); + }); +}); From 55b5b57cbbfbbb8f3b2c83366038d5ffb1ca7af3 Mon Sep 17 00:00:00 2001 From: "mkhutornyi (via MelvinBot)" Date: Sat, 4 Jul 2026 19:31:45 +0000 Subject: [PATCH 3/6] Fix ESLint no-unsafe-type-assertion in InSelector test Co-authored-by: mkhutornyi --- tests/ui/InSelectorTest.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/ui/InSelectorTest.tsx b/tests/ui/InSelectorTest.tsx index 6be719d50fc4..033aae0ec482 100644 --- a/tests/ui/InSelectorTest.tsx +++ b/tests/ui/InSelectorTest.tsx @@ -40,8 +40,6 @@ jest.mock('@libs/OptionsListUtils', () => ({ getAlternateText: () => '', })); -type SectionData = {reportID?: string; keyForList: string; isSelected?: boolean}; - describe('InSelector', () => { const mockedSelectionList = jest.mocked(SelectionListWithSections); @@ -52,7 +50,7 @@ describe('InSelector', () => { text: `Report ${index}`, })); - const getSections = () => mockedSelectionList.mock.lastCall?.[0].sections as Array<{data: SectionData[]}>; + const getSections = () => mockedSelectionList.mock.lastCall?.[0].sections ?? []; beforeEach(() => { mockedSelectionList.mockClear(); From e8b6bdc291a8ed4df228452a21e02919f8b710a0 Mon Sep 17 00:00:00 2001 From: "mkhutornyi (via MelvinBot)" Date: Sun, 5 Jul 2026 09:35:13 +0000 Subject: [PATCH 4/6] Pass shouldClearInputOnSelect, shouldUpdateFocusedIndex, shouldPreventAutoScrollOnSelect to SelectionListWithSections Co-authored-by: mkhutornyi --- src/components/Search/FilterComponents/InSelector.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/Search/FilterComponents/InSelector.tsx b/src/components/Search/FilterComponents/InSelector.tsx index 03256715dd35..5e58357b7c9b 100644 --- a/src/components/Search/FilterComponents/InSelector.tsx +++ b/src/components/Search/FilterComponents/InSelector.tsx @@ -227,6 +227,9 @@ function InSelector({value = [], selectionListTextInputStyle, selectionListStyle onSelectRow={handleParticipantSelection} ListItem={InviteMemberListItem} canSelectMultiple + shouldClearInputOnSelect={false} + shouldUpdateFocusedIndex + shouldPreventAutoScrollOnSelect shouldPreventDefaultFocusOnSelectRow={!canUseTouchScreen()} textInputOptions={textInputOptions} isLoadingNewOptions={isLoadingNewOptions} From 7fcde2bf34a9c423b5b136d20a2de3d4a7d69ec1 Mon Sep 17 00:00:00 2001 From: "mkhutornyi (via MelvinBot)" Date: Sun, 5 Jul 2026 09:48:17 +0000 Subject: [PATCH 5/6] Keep pinned In-filter items at the top while searching Gate pinning on the unfiltered list size so typing a search value doesn't collapse the pinned section, and keep only the pinned reports that still match the filtered results at the top for consistency. Co-authored-by: mkhutornyi --- .../Search/FilterComponents/InSelector.tsx | 44 ++++++++----------- tests/ui/InSelectorTest.tsx | 41 ++++++++++++++--- 2 files changed, 54 insertions(+), 31 deletions(-) diff --git a/src/components/Search/FilterComponents/InSelector.tsx b/src/components/Search/FilterComponents/InSelector.tsx index 5e58357b7c9b..00158c4d37fa 100644 --- a/src/components/Search/FilterComponents/InSelector.tsx +++ b/src/components/Search/FilterComponents/InSelector.tsx @@ -17,7 +17,7 @@ import useSortedActions from '@hooks/useSortedActions'; import {searchInServer} from '@libs/actions/Report'; import {canUseTouchScreen} from '@libs/DeviceCapabilities'; import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID'; -import {createOptionFromReport, filterAndOrderOptions, formatSectionsFromSearchTerm, getAlternateText, getSearchOptions} from '@libs/OptionsListUtils'; +import {createOptionFromReport, filterAndOrderOptions, getAlternateText, getSearchOptions} from '@libs/OptionsListUtils'; import type {Option, OptionWithKey, SelectionListSections} from '@libs/OptionsListUtils/types'; import type {OptionData} from '@libs/ReportUtils'; import {expensifyLoginsSelector} from '@libs/UserUtils'; @@ -141,36 +141,30 @@ function InSelector({value = [], selectionListTextInputStyle, selectionListStyle const sections: SelectionListSections = []; if (!isLoading) { - // Only float the initially-selected reports to the top of a long list. Keying the pinned section on the - // snapshot (instead of the live `value`) means items toggled during this session stay put (see #61414). - const shouldMoveSelectedToTop = chatOptions.recentReports.length >= CONST.STANDARD_LIST_ITEM_LIMIT; + // Only float the initially-selected reports to the top of a long list. Gate on the *unfiltered* list size so the + // decision doesn't flip as the user types, and key the pinned section on the snapshot (instead of the live + // `value`) so items toggled during this session stay put (https://github.com/Expensify/App/issues/61414). + const shouldMoveSelectedToTop = defaultOptions.recentReports.length >= CONST.STANDARD_LIST_ITEM_LIMIT; const pinnedReportIDs = shouldMoveSelectedToTop ? initialValue : []; const pinnedReportIDSet = new Set(pinnedReportIDs); - const pinnedSelectedOptions = pinnedReportIDs.map((id) => buildReportOption(id, value.includes(id))); - - const formattedResults = formatSectionsFromSearchTerm( - cleanSearchTerm, - cleanSearchTerm === '' ? pinnedSelectedOptions : selectedOptions, - chatOptions.recentReports, - chatOptions.personalDetails, - privateIsArchivedMap, - currentUserAccountID, - allPolicies, - personalDetails, - false, - undefined, - reportAttributesDerived, - ); - sections.push(formattedResults.section); + // Keep the pinned reports at the top whether or not a search value is entered, for consistency. When a term is + // typed, only surface the pinned reports that still match the filtered results. + const matchedReportIDs = new Set(chatOptions.recentReports.map((report) => report.reportID)); + const visiblePinnedReportIDs = cleanSearchTerm === '' ? pinnedReportIDs : pinnedReportIDs.filter((id) => matchedReportIDs.has(id)); + const pinnedSelectedOptions = visiblePinnedReportIDs.map((id) => buildReportOption(id, value.includes(id))); + + sections.push({ + title: undefined, + sectionIndex: 0, + data: pinnedSelectedOptions, + }); - // Mark reports as selected in place based on the live `value`, so the checkmark toggles without reordering. - // When the search term is empty, drop only the pinned reports (they already appear in the top section above). - const visibleReportsWhenSearchTermNonEmpty = chatOptions.recentReports.map((report) => (value.includes(report.reportID) ? getSelectedOptionData(report) : report)); - const visibleReportsWhenSearchTermEmpty = chatOptions.recentReports + // Drop the pinned reports from the main section (they already appear in the top section above) and mark the + // remaining reports selected in place based on the live `value`, so the checkmark toggles without reordering. + const reportsFiltered = chatOptions.recentReports .filter((report) => !pinnedReportIDSet.has(report.reportID)) .map((report) => (value.includes(report.reportID) ? getSelectedOptionData(report) : report)); - const reportsFiltered = cleanSearchTerm === '' ? visibleReportsWhenSearchTermEmpty : visibleReportsWhenSearchTermNonEmpty; sections.push({ data: reportsFiltered, diff --git a/tests/ui/InSelectorTest.tsx b/tests/ui/InSelectorTest.tsx index 033aae0ec482..cad034175c5d 100644 --- a/tests/ui/InSelectorTest.tsx +++ b/tests/ui/InSelectorTest.tsx @@ -7,9 +7,13 @@ import CONST from '@src/CONST'; import React from 'react'; -// The list of reports returned by the (mocked) options pipeline. Tests mutate this to switch between a -// long list (pinning enabled) and a short list (pinning disabled). +// The list of reports returned by the (mocked) options pipeline. Tests mutate these to switch between a +// long list (pinning enabled) and a short list (pinning disabled), and to simulate a search filtering the list. +// `mockDefaultReports` is the unfiltered base list (drives the "is the list long enough to pin" gate); `mockRecentReports` +// is what the filter returns for the current search term. let mockRecentReports: Array<{reportID: string; keyForList: string; text: string}> = []; +let mockDefaultReports: Array<{reportID: string; keyForList: string; text: string}> = []; +let mockSearchTerm = ''; jest.mock('@components/SelectionList/SelectionListWithSections', () => jest.fn(() => null)); jest.mock('@components/SelectionList/ListItem/InviteMemberListItem', () => jest.fn(() => null)); @@ -17,7 +21,7 @@ jest.mock('@components/Search/FilterComponents/ListFilterViewWrapper', () => jes jest.mock('@components/OnyxListItemProvider', () => ({usePersonalDetails: jest.fn(() => ({}))})); jest.mock('@hooks/useOnyx', () => jest.fn(() => [undefined])); -jest.mock('@hooks/useDebouncedState', () => jest.fn(() => ['', '', jest.fn()])); +jest.mock('@hooks/useDebouncedState', () => jest.fn(() => [mockSearchTerm, mockSearchTerm, jest.fn()])); jest.mock('@hooks/useFilteredOptions', () => jest.fn(() => ({options: {recentReports: [], personalDetails: []}, isLoading: false}))); jest.mock('@hooks/useCurrentUserPersonalDetails', () => jest.fn(() => ({email: 'me@expensify.com', accountID: 999}))); jest.mock('@hooks/useSortedActions', () => jest.fn(() => ({}))); @@ -33,10 +37,8 @@ jest.mock('@libs/DeviceCapabilities', () => ({canUseTouchScreen: () => false})); jest.mock('@libs/OptionsListUtils', () => ({ // Build a minimal option object from the report id the component passes in. createOptionFromReport: (report: {reportID: string}) => ({reportID: report.reportID, keyForList: report.reportID, text: `Report ${report.reportID}`}), - getSearchOptions: () => ({options: {recentReports: [], personalDetails: []}}), + getSearchOptions: () => ({options: {recentReports: mockDefaultReports, personalDetails: []}}), filterAndOrderOptions: () => ({recentReports: mockRecentReports, personalDetails: []}), - // Mirror the real helper for an empty search term: the passed-in selectedOptions become the top section verbatim. - formatSectionsFromSearchTerm: (searchTerm: string, selectedOptions: unknown[]) => ({section: {sectionIndex: 0, data: selectedOptions}}), getAlternateText: () => '', })); @@ -54,8 +56,10 @@ describe('InSelector', () => { beforeEach(() => { mockedSelectionList.mockClear(); + mockSearchTerm = ''; // A long list so the "move selected to top" behavior is enabled. mockRecentReports = buildRecentReports(CONST.STANDARD_LIST_ITEM_LIMIT + 2); + mockDefaultReports = mockRecentReports; }); it('floats the initially-selected report to the top of a long list', () => { @@ -100,8 +104,33 @@ describe('InSelector', () => { expect(mainSection.find((item) => item.keyForList === 'r3')?.isSelected).toBe(true); }); + it('keeps the initially-selected report pinned at the top while searching, and drops non-matching pinned reports', () => { + // Simulate the user typing a search term that narrows the list to a few matching reports (r3, r10, r11). + // r10 is pinned and matches -> it stays at the top. r5 is pinned but doesn't match -> it is dropped. + mockSearchTerm = 'report'; + mockRecentReports = [ + {reportID: 'r3', keyForList: 'r3', text: 'Report 3'}, + {reportID: 'r10', keyForList: 'r10', text: 'Report 10'}, + {reportID: 'r11', keyForList: 'r11', text: 'Report 11'}, + ]; + + render( + , + ); + + const sections = getSections(); + // The pinned report that still matches the search stays at the top; the non-matching pinned report is dropped. + expect(sections.at(0)?.data.map((item) => item.keyForList)).toEqual(['r10']); + // The main section shows the remaining matches with the pinned one removed (no duplicate). + expect(sections.at(1)?.data.map((item) => item.keyForList)).toEqual(['r3', 'r11']); + }); + it('does not pin selected reports to the top of a short list', () => { mockRecentReports = buildRecentReports(5); + mockDefaultReports = mockRecentReports; render( Date: Sun, 5 Jul 2026 10:41:28 +0000 Subject: [PATCH 6/6] Remove issue-link references from InSelector code comments Co-authored-by: mkhutornyi --- src/components/Search/FilterComponents/InSelector.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Search/FilterComponents/InSelector.tsx b/src/components/Search/FilterComponents/InSelector.tsx index 00158c4d37fa..26072c9f6de8 100644 --- a/src/components/Search/FilterComponents/InSelector.tsx +++ b/src/components/Search/FilterComponents/InSelector.tsx @@ -111,7 +111,7 @@ function InSelector({value = [], selectionListTextInputStyle, selectionListStyle // Snapshot the reports that were already selected when the filter first opened. On a long list these stay // pinned to the top so they're easy to find, but items toggled during the current session are NOT re-pinned: - // they keep their position so selecting doesn't scroll/jump the list (https://github.com/Expensify/App/issues/61414). + // they keep their position so selecting doesn't scroll/jump the list. const initialValue = useInitialValue(() => value); const defaultOptions = @@ -143,7 +143,7 @@ function InSelector({value = [], selectionListTextInputStyle, selectionListStyle if (!isLoading) { // Only float the initially-selected reports to the top of a long list. Gate on the *unfiltered* list size so the // decision doesn't flip as the user types, and key the pinned section on the snapshot (instead of the live - // `value`) so items toggled during this session stay put (https://github.com/Expensify/App/issues/61414). + // `value`) so items toggled during this session stay put. const shouldMoveSelectedToTop = defaultOptions.recentReports.length >= CONST.STANDARD_LIST_ITEM_LIMIT; const pinnedReportIDs = shouldMoveSelectedToTop ? initialValue : []; const pinnedReportIDSet = new Set(pinnedReportIDs);