Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
95 commits
Select commit Hold shift + click to select a range
7d4be68
feat: migrate report actions to chronological LegendList
chrispader Sep 9, 2026
2a3323a
chore: merge main into LegendList report actions
chrispader Sep 9, 2026
d815c1a
chore: merge LegendList foundation updates
chrispader Sep 10, 2026
ba852b2
fix: address LegendList report action review
chrispader Sep 10, 2026
15312bb
chore: merge main into LegendList report actions
chrispader Sep 10, 2026
0a811f9
chore: merge main into LegendList report actions
chrispader Sep 10, 2026
750e92d
chore: merge main into LegendList report actions
chrispader Sep 10, 2026
332a7b3
fix: stabilize LegendList initial loading
chrispader Sep 10, 2026
e385011
chore: merge main into LegendList report actions
chrispader Sep 10, 2026
9117baa
chore: merge main into LegendList report actions
chrispader Sep 10, 2026
e5c4ec3
chore: merge main into LegendList report actions
chrispader Sep 10, 2026
3a782f9
test: model content growth in pagination test
chrispader Sep 10, 2026
fa5194f
fix: stabilize report actions loading behavior
chrispader Sep 10, 2026
52df1e9
refactor: remove compiled report memoization
chrispader Sep 11, 2026
c80ea5a
fix: allow report action menus to overflow list rows
chrispader Sep 14, 2026
5cb81be
chore: merge main into LegendList report actions
chrispader Sep 16, 2026
c873dca
chore: merge main into LegendList report actions
chrispader Sep 16, 2026
f91ebe0
chore: merge main into LegendList report actions
chrispader Sep 17, 2026
255f4d3
chore: merge main into LegendList report actions
chrispader Sep 17, 2026
62a7c0d
fix: supply report name in modified expense tests
chrispader Sep 17, 2026
0964d53
Merge branch 'main' into @chrispader/feat/legend-list-report-actions
chrispader Sep 17, 2026
09057d5
fix: address report actions list review feedback
chrispader Sep 18, 2026
61dc6ad
chore: merge main into report actions branch
chrispader Sep 18, 2026
ffa18ae
docs: explain report action recycling state
chrispader Sep 18, 2026
53f3b9f
Merge branch 'main' into @chrispader/feat/legend-list-report-actions
chrispader Sep 18, 2026
f12b575
fix: remove duplicate report list spacing
chrispader Sep 22, 2026
34b345d
fix: synchronize sent comment scrolling
chrispader Sep 22, 2026
da3b443
fix: preserve hydrated report list state
chrispader Sep 22, 2026
55cb72a
fix: refresh recycled unread marker rows
chrispader Sep 22, 2026
a5670fa
chore: merge main into report actions branch
chrispader Sep 22, 2026
8188df1
fix: keep report actions live across incomplete loads
chrispader Sep 24, 2026
9bd382a
fix: preserve local action scrolling without realtime echo jumps
chrispader Sep 24, 2026
f0b046a
fix: key recycled media state by action and image
chrispader Sep 24, 2026
a8fc4fa
fix: keep highlighted final action in view
chrispader Sep 24, 2026
6e7f045
test: verify unread and read thresholds from saved offset
chrispader Sep 24, 2026
ca78b55
refactor: share web report scroll-to-end implementation
chrispader Sep 24, 2026
59a5397
docs: note Android visible-position patch scope
chrispader Sep 24, 2026
d2f2cc8
fix: scroll after edited action layout settles
chrispader Sep 24, 2026
08e1f69
test: type captured list load callback
chrispader Sep 24, 2026
5c7ffc8
chore: merge main into LegendList report actions
chrispader Sep 24, 2026
84b0f19
fix: preserve Concierge tail when revealing history
chrispader Sep 24, 2026
a1e0d25
test: remove nonvirtualized report list benchmark
chrispader Sep 24, 2026
e37edb4
test: mock edited-action draft clearing
chrispader Sep 24, 2026
b43b59e
Merge branch 'main' into @chrispader/feat/legend-list-report-actions
chrispader Sep 24, 2026
e904b99
test: wait for edited action scroll frame
chrispader Sep 24, 2026
40f7719
chore: update LegendList to 3.4.0
chrispader Sep 24, 2026
a68ee3c
fix: cover warm report actions during initial layout
chrispader Sep 24, 2026
e117ffc
fix: render Concierge notification banner before chat scrolls to end
chrispader Sep 25, 2026
4adbdaa
fix: keep latest chat action visible after list measurement
chrispader Sep 25, 2026
c92613d
fix: preserve Concierge scroll position when showing history
chrispader Sep 25, 2026
7096033
fix: preserve unread message alignment at the latest action
chrispader Sep 29, 2026
c0751dd
fix: cancel report end corrections when the reader scrolls
chrispader Sep 29, 2026
0cb9d7b
fix: keep report scroll events flowing on web
chrispader Sep 29, 2026
0f53485
fix: keep chat skeleton visible until initial scrolling settles
chrispader Sep 29, 2026
b611f38
fix: resume end following after scrolling past a linked message
chrispader Sep 29, 2026
779eaac
fix: preserve chat content when clearing linked messages
chrispader Oct 1, 2026
5dab08a
chore: merge main into LegendList report actions
chrispader Oct 7, 2026
519cb5b
fix: prevent Android LegendList viewport flashes
chrispader Oct 8, 2026
8229bce
fix: keep linked messages visible after navigation and layout changes
chrispader Oct 8, 2026
ed4e31a
fix: emit web scroll events for chronological lists
chrispader Oct 8, 2026
d25f5b6
fix: preserve linked-message position through late layout
chrispader Oct 9, 2026
e83aec2
chore: merge main into LegendList report actions
chrispader Oct 9, 2026
b6c6df2
fix: preserve LegendList web scroll completion
chrispader Oct 13, 2026
40a2338
fix: resolve the current composer after focus delays
chrispader Oct 13, 2026
49ab56d
fix: avoid duplicate Android linked-message adjustments
chrispader Oct 13, 2026
e1bbfda
chore: merge main and resolve report scrolling conflicts
chrispader Oct 19, 2026
e8974de
chore: merge main into LegendList report actions
chrispader Oct 19, 2026
1de8343
fix: keyboard focus
chrispader Oct 19, 2026
bd9ce7c
fix: restore Android composer blur on keyboard dismissal
chrispader Oct 20, 2026
e60414a
Merge branch 'main' into @chrispader/fix/legend-list-reported-issues
chrispader Oct 21, 2026
1c04141
fix: fill the available height in report action lists
chrispader Oct 21, 2026
c12c90c
fix: keep the reader's position when returning to a linked chat
chrispader Oct 21, 2026
89ef8b2
fix: keep unread chats at the newest message while older rows load
chrispader Oct 21, 2026
eb148d3
fix: stop Android chats from flashing older messages at the end
chrispader Oct 21, 2026
c71756d
ci: retrigger unit tests
chrispader Oct 21, 2026
9b1bf78
refactor: consolidate report scroll-to-bottom commands
chrispader Oct 22, 2026
311f777
refactor: share linked-message positioning cleanup
chrispader Oct 22, 2026
5c905a6
fix: preserve shared-list scroll event behavior
chrispader Oct 22, 2026
e3b9d15
docs: clarify edit-message scroll scheduling
chrispader Oct 22, 2026
c8de6b3
fix: preserve LegendList native scroll event cadence
chrispader Oct 22, 2026
e466ab7
fix: retain the 250-pixel incoming-message follow range
chrispader Oct 22, 2026
4b8a445
fix: end warm report timing when covered content is visible
chrispader Oct 22, 2026
49eafc2
fix: complete edit-message scrolling after list layout
chrispader Oct 22, 2026
8dc16d8
docs: link upstream LegendList and Android anchoring fixes
chrispader Oct 22, 2026
5c00ae5
chore: merge main into the LegendList migration
chrispader Oct 23, 2026
e0b5420
ci: retrigger Reassure performance tests
chrispader Oct 24, 2026
df38ea5
test: bound the LegendList row-write benchmark page
chrispader Oct 25, 2026
be9a884
fix: stabilize composer focus and list scroll notifications
chrispader Oct 26, 2026
e01e0fb
refactor: prepare report list refs and recycled row state
chrispader Oct 26, 2026
2eed882
chore: combine LegendList preparation branches
chrispader Oct 26, 2026
2891fd8
refactor: separate LegendList migration from its preparation PRs
chrispader Oct 26, 2026
945622d
fix: keep action event source type internal
chrispader Oct 26, 2026
c5e8046
chore: sync composer preparation type cleanup
chrispader Oct 26, 2026
8e20b9c
chore: sync preparation type cleanup into migration
chrispader Oct 26, 2026
983add0
chore(deps): update LegendList to 3.7.0
chrispader Oct 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@
"@fullstory/react-native": "^1.10.2",
"@gorhom/portal": "^1.0.14",
"@invertase/react-native-apple-authentication": "^2.5.0",
"@legendapp/list": "^3.3.10",
"@legendapp/list": "^3.7.0",
"@lottiefiles/dotlottie-react": "0.13.5",
"@onfido/react-native-sdk": "15.1.0",
"@pusher/pusher-websocket-react-native": "^1.3.1",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
diff --git a/node_modules/@legendapp/list/react-native.js b/node_modules/@legendapp/list/react-native.js
index 59ecd66..432f476 100644
--- a/node_modules/@legendapp/list/react-native.js
+++ b/node_modules/@legendapp/list/react-native.js
@@ -1642,6 +1642,19 @@ function getPredictedNativeClamp(state, unresolvedAmount, totalSize) {
}
return 0;
}
+// Android clamps the offset to the new end while mounting a content shrink, then native MVCP adds the
+// anchor delta on top. Sync JS state to that clamp and only request the rest of the movement.
+function getAndroidNativeClampedAdjust(ctx, positionDiff) {
+ const state = ctx.state;
+ const maxScroll = Math.max(0, getContentSize(ctx) - state.scrollLength);
+ if (state.scroll <= maxScroll + MVCP_POSITION_EPSILON) {
+ return positionDiff;
+ }
+ const targetScroll = Math.min(state.scroll + positionDiff, maxScroll);
+ state.scroll = maxScroll;
+ state.scrollPending = maxScroll;
+ return targetScroll - maxScroll;
+}
function getProgressTowardAmount(targetDelta, nativeDelta) {
return targetDelta < 0 ? -nativeDelta : nativeDelta;
}
@@ -1884,6 +1897,9 @@ function prepareMVCP(ctx, dataChanged, adjustmentSource) {
now,
positionDiff
});
+ if (Platform.OS === "android" && !dataChanged && scrollTarget === void 0) {
+ positionDiff = getAndroidNativeClampedAdjust(ctx, positionDiff);
+ }
if (shouldQueueNativeMVCPAdjust(dataChanged, state, positionDiff, prevTotalSize, prevScroll, scrollTarget)) {
state.pendingNativeMVCPAdjust = {
amount: positionDiff,
diff --git a/node_modules/@legendapp/list/react-native.mjs b/node_modules/@legendapp/list/react-native.mjs
index 5bf1496..8262cbd 100644
--- a/node_modules/@legendapp/list/react-native.mjs
+++ b/node_modules/@legendapp/list/react-native.mjs
@@ -1621,6 +1621,19 @@ function getPredictedNativeClamp(state, unresolvedAmount, totalSize) {
}
return 0;
}
+// Android clamps the offset to the new end while mounting a content shrink, then native MVCP adds the
+// anchor delta on top. Sync JS state to that clamp and only request the rest of the movement.
+function getAndroidNativeClampedAdjust(ctx, positionDiff) {
+ const state = ctx.state;
+ const maxScroll = Math.max(0, getContentSize(ctx) - state.scrollLength);
+ if (state.scroll <= maxScroll + MVCP_POSITION_EPSILON) {
+ return positionDiff;
+ }
+ const targetScroll = Math.min(state.scroll + positionDiff, maxScroll);
+ state.scroll = maxScroll;
+ state.scrollPending = maxScroll;
+ return targetScroll - maxScroll;
+}
function getProgressTowardAmount(targetDelta, nativeDelta) {
return targetDelta < 0 ? -nativeDelta : nativeDelta;
}
@@ -1863,6 +1876,9 @@ function prepareMVCP(ctx, dataChanged, adjustmentSource) {
now,
positionDiff
});
+ if (Platform.OS === "android" && !dataChanged && scrollTarget === void 0) {
+ positionDiff = getAndroidNativeClampedAdjust(ctx, positionDiff);
+ }
if (shouldQueueNativeMVCPAdjust(dataChanged, state, positionDiff, prevTotalSize, prevScroll, scrollTarget)) {
state.pendingNativeMVCPAdjust = {
amount: positionDiff,
16 changes: 16 additions & 0 deletions patches/@legendapp/list/details.md
Comment thread
chrispader marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# `@legendapp/list` patches

The Fabric content-size fix from https://github.com/LegendApp/legend-list/commit/a2d20a9b is included in [LegendList 3.7.0](https://github.com/LegendApp/legend-list/blob/v3.7.0/CHANGELOG.md#370), so its local patch has been removed.

### [@legendapp+list+3.7.0+001+account-for-android-end-clamp.patch](@legendapp+list+3.7.0+001+account-for-android-end-clamp.patch)

- Reason: On Android, a chat that opens at its newest messages can briefly show older messages before it snaps back to the end. When rows above the viewport get their real, smaller heights, the content shrinks. Android clamps the scroll offset to the new end, and LegendList's scroll-preservation anchor then moves the offset by the full shrink again. The list stays above the end by that amount until the next scroll to the end. Account for Android's clamp and only move the anchor by what the clamp doesn't cover.
- Scope: Only item-size scroll preservation on Android, and only when the content shrinks below the current offset. Data changes and active scroll targets already handle the clamp and are unchanged. iOS and web are unchanged.
- Upstream PR/issue: https://github.com/LegendApp/legend-list/pull/582 upstreams this patch. https://github.com/LegendApp/legend-list/pull/481 covers the related bottom-inset case. Keep this patch until the installed LegendList release includes this fix.
- E/App issue: https://github.com/Expensify/App/pull/100733#issuecomment-6287555197
- PR introducing patch: https://github.com/Expensify/App/pull/100733
- Reproduction: In a DM, mark the third-newest message as unread and go back to the inbox. Open the DM again. It should open at the newest messages without a frame showing older messages. Also scroll through older history and send a new message to check scroll preservation and end following.

### Related upstream work

- https://github.com/LegendApp/legend-list/pull/583 refreshes the cached end-follow state when `maintainScrollAtEndThreshold` changes. This supports a fixed pixel follow distance across viewport changes when using LegendList's built-in end following.
9 changes: 7 additions & 2 deletions patches/react-native/details.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,15 @@

### [react-native+0.86.0+002+fixMVCPAndroid.patch](react-native+0.86.0+002+fixMVCPAndroid.patch)

- Reason: Fixes content jumping issues with `MaintainVisibleContentPosition` on Android, particularly in bidirectional pagination scenarios. The patch makes two key improvements:
- Reason: Fixes content jumping issues with `MaintainVisibleContentPosition` on Android, particularly in bidirectional pagination scenarios. The patch:
1. Changes when the first visible view is calculated - now happens on scroll events instead of during Fabric's willMountItems lifecycle, which was causing incorrect updates
2. Improves first visible view selection logic to handle Fabric's z-index-based view reordering by finding the view with the smallest position that's still greater than the scroll position
- Upstream PR/issue: https://github.com/facebook/react-native/pull/46247
3. Preserves a positioned, zero-sized first child as a scroll anchor. LegendList moves this anchor to compensate for item measurements. Selecting its surrounding container or rejecting its empty frame prevents native scroll compensation and makes the chat jump as estimated rows shrink. LegendList 3.6 places the anchor at its adjustment offset plus 10,000,000 pixels, beyond the viewport. The strict visibility check also prevents a fixed, zero-sized spacer at or above the scroll offset in another scroll view from taking over its visible-content anchor.
- Scope: This changes Android `MaintainVisibleContentPosition` for every React Native scroll view that enables it, not only the report-action list. Keep this in mind when updating React Native or investigating anchoring in other scroll views.
- Upstream PR/issue: https://github.com/react/react-native/pull/57959 covers z-index anchor selection. The follow-up guard preserving LegendList's positioned, zero-sized anchor is proposed in https://github.com/react/react-native/pull/57959#issuecomment-6341492122.
- Reproducer: https://github.com/react/react-native/pull/59360 adds the RNTester reproduction, without a runtime fix. The standalone reproduction and proposed anchor patch are in https://github.com/margelo/react-native-mvcp-reproducer.
- Earlier proposal: https://github.com/react/react-native/pull/46247 closed without merging.
- Removal: Drop this patch only when the adopted React Native release preserves all three anchoring behaviors described above.
- E/App issue: 🛑
- PR Introducing Patch: https://github.com/Expensify/App/pull/46315 (introduced), https://github.com/Expensify/App/pull/45289 (refactored)

Expand Down

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MelvinBot Is the change to this patch reflected in the upstream react/react-native#46247?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No — none of this patch is upstream, and the new change in this PR isn't even part of #46247's diff.

Two separate gaps:

  1. maintainVisibleContentPosition fixes on Android was never merged. react-native-bot closed it as stale on 2026-04-05 after 180 days with no activity (merged: false, no merge commit). RN main today still has the original naive computeTargetView() — first-match loop, called from willMountItems. So points 1 and 2 in details.md are still patch-only.

  2. The change added here has no upstream counterpart at all. Allow Search command to run in support mode #46247 touches only view selection (z-index-aware) and when it runs (on scroll). It never special-cases a zero-sized anchor. Both new bits here are new behavior:

Suggestion: the Upstream PR/issue: line in details.md now points at a closed PR, which reads as "this is on its way upstream" when it isn't. Worth marking it closed/not-landed, and either filing a new upstream PR for the LegendList anchor case or setting it to 🛑 like the E/App issue: line.

How I checked
  • gh api repos/facebook/react-native/pulls/46247 → state: closed, merged: false, merged_at: null, closed_at: 2026-04-05T05:31:12Z
  • Last comment on the PR: react-native-bot: This PR was closed because it has been stalled for 7 days with no activity.
  • Compared Allow Search command to run in support mode #46247's diff of MaintainVisibleScrollPositionHelper.java against this PR's patch hunks — no width == 0 && height == 0 anchor branch, and willMountItemsInternal() stores the hit rect unconditionally with no zero-area guard.
  • Fetched MaintainVisibleScrollPositionHelper.kt from RN main to confirm the upstream refactor (onScroll() / didMountItemsInternal() / firstVisibleViewPosition) is absent.

view run

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chrispader at some point we need to get this merged upstream. I talked to Janic about this about a month ago, but Expensify didn't want to deal with the overhead of onboarding a new agency just for that.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll look into it!

@chrispader chrispader Oct 22, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Chris's AI co-pilot checking in. 🤖

RN #57959 covers the z-index anchor selection fix. I raised the missing LegendList zero-sized anchor guard in that PR's discussion and added RNTester reproducer #59360. The upstream links and patch-removal conditions are now recorded in details.md.

@roryabraham

Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,16 @@ index 2bee605..ba26e7b 100644
for (i in config.minIndexForVisible until contentView.childCount) {
val child = contentView.getChildAt(i)

@@ -128,27 +135,49 @@ internal class MaintainVisibleScrollPositionHelper<ScrollViewT>(
@@ -128,27 +135,57 @@ internal class MaintainVisibleScrollPositionHelper<ScrollViewT>(
val position = if (horizontal) child.x + child.width else child.y + child.height

+ // Virtualized lists can use a zero-sized, positioned first child as their scroll anchor.
+ // Preserve it instead of choosing the container that holds all rendered items.
+ if (i == config.minIndexForVisible && child.width == 0 && child.height == 0 && position > currentScroll) {
+ firstVisibleView = child
+ break
+ }
+
// If the child is partially visible or this is the last child, select it as the anchor.
- if (position > currentScroll || i == contentView.childCount - 1) {
- firstVisibleViewRef = WeakReference(child)
Expand Down Expand Up @@ -70,7 +77,8 @@ index 2bee605..ba26e7b 100644
}
+ val frame = Rect()
+ firstVisibleView.getHitRect(frame)
+ if (frame.width() > 0 || frame.height() > 0) {
+ // Zero-sized anchors have a meaningful position even though they have no area.
+ if (frame.width() > 0 || frame.height() > 0 || frame.left != 0 || frame.top != 0) {
+ prevFirstVisibleFrame = frame
+ } else {
+ prevFirstVisibleFrame = null
Expand Down

This file was deleted.

28 changes: 0 additions & 28 deletions src/components/FlashList/InvertedFlashList/index.tsx

This file was deleted.

2 changes: 1 addition & 1 deletion src/components/FlashList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {FlashList as ShopifyFlashList} from '@shopify/flash-list';
import React from 'react';

function FlashList<T>({onScroll: onScrollProp, inverted, ...restProps}: FlashListProps<T>) {
const emitComposerScrollEvents = useEmitComposerScrollEvents({enabled: true, inverted});
const emitComposerScrollEvents = useEmitComposerScrollEvents({enabled: !!inverted});

const handleScroll = (e: NativeSyntheticEvent<NativeScrollEvent>) => {
onScrollProp?.(e);
Expand Down
7 changes: 0 additions & 7 deletions src/components/FlashList/types.ts

This file was deleted.

2 changes: 1 addition & 1 deletion src/components/FlatList/FlatList/index.ios.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ function CustomFlatList<T>({
[onMomentumScrollEnd],
);

const emitComposerScrollEvents = useEmitComposerScrollEvents({enabled: !enableAnimatedKeyboardDismissal, inverted: restProps.inverted});
const emitComposerScrollEvents = useEmitComposerScrollEvents({enabled: !enableAnimatedKeyboardDismissal && !!restProps.inverted});
const handleScroll = useCallback(
(e: NativeSyntheticEvent<NativeScrollEvent>) => {
onScrollProp?.(e);
Expand Down
2 changes: 1 addition & 1 deletion src/components/FlatList/FlatList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ function MVCPFlatList<T>({
};
}, []);

const emitComposerScrollEvents = useEmitComposerScrollEvents({enabled: true, inverted: restProps.inverted});
const emitComposerScrollEvents = useEmitComposerScrollEvents({enabled: !!restProps.inverted});
const handleScroll = useCallback(
(e: NativeSyntheticEvent<NativeScrollEvent>) => {
onScrollProp?.(e);
Expand Down
4 changes: 2 additions & 2 deletions src/components/KeyboardDismissibleFlatList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import {useAnimatedScrollHandler, useComposedEventHandler} from 'react-native-re

import {useKeyboardDismissibleFlatListActions} from './KeyboardDismissibleFlatListContext';

function KeyboardDismissibleFlatList<T>({onScroll: onScrollProp, inverted, ref, ...restProps}: AnimatedFlatListWithCellRendererProps<T>) {
function KeyboardDismissibleFlatList<T>({onScroll: onScrollProp, ref, ...restProps}: AnimatedFlatListWithCellRendererProps<T>) {
const {onScroll: onScrollHandleKeyboard} = useKeyboardDismissibleFlatListActions();

const emitComposerScrollEvents = useEmitComposerScrollEvents({enabled: true, inverted});
const emitComposerScrollEvents = useEmitComposerScrollEvents({enabled: !!restProps.inverted});

const additionalOnScroll = useAnimatedScrollHandler({
onScroll: emitComposerScrollEvents,
Expand Down
19 changes: 19 additions & 0 deletions src/components/LegendList/allowLegendListItemOverflow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import {isRecord} from '@libs/ObjectUtils';

import type {LegendListRef} from '@legendapp/list/react-native';

/**
* LegendList uses paint containment for web item containers, which clips descendants that render
* outside their row. Remove only paint containment so inline menus can overflow while layout and
* style containment keep the rest of the row isolated.
*/
function allowLegendListItemOverflow(list: LegendListRef | null, index: number): void {
const itemContainer: unknown = list?.getState().elementAtIndex(index);
if (!isRecord(itemContainer) || !isRecord(itemContainer.style) || typeof itemContainer.style.contain !== 'string') {
return;
}

itemContainer.style.contain = 'layout style';
}

export default allowLegendListItemOverflow;
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,20 @@ function MoneyRequestReportActionsListContent({reportIDFromRoute, onLayout}: Mon
fullPolicySnapshot: report?.policyID && fullPolicySnapshotLastModified ? {policyID: report.policyID, lastModified: fullPolicySnapshotLastModified} : undefined,
});

const [editScrollRequestID, setEditScrollRequestID] = useState(0);
const completedEditScrollRequestIDRef = useRef(0);
const requestScrollToNewestAfterEdit = () => {
// Force a layout commit even when closing the editor leaves the row's height unchanged.
setEditScrollRequestID((requestID) => requestID + 1);
};
const onCommitLayoutEffect = () => {
if (editScrollRequestID <= completedEditScrollRequestIDRef.current) {
return;
}
completedEditScrollRequestIDRef.current = editScrollRequestID;
scrollToBottom();
};

// Wait for the actions to arrive before jumping, and clear the route param afterwards so a later remount
// doesn't yank the user back down.
useEffect(() => {
Expand All @@ -251,7 +265,7 @@ function MoneyRequestReportActionsListContent({reportIDFromRoute, onLayout}: Mon
const isNewestReportAction = indexWithinReportActions === visibleReportActions.length - 1;

return (
<ReportActionScrollToNewestContext.Provider value={scrollToBottom}>
<ReportActionScrollToNewestContext.Provider value={requestScrollToNewestAfterEdit}>
<ReportActionPositionContextProvider
index={indexWithinReportActions}
isNewest={isNewestReportAction}
Expand Down Expand Up @@ -364,6 +378,7 @@ function MoneyRequestReportActionsListContent({reportIDFromRoute, onLayout}: Mon
onLastItemIndexChange={updateLastItemIndex}
accessibilityLabel={translate('sidebarScreen.listOfChatMessages')}
onListLayout={onListLayout}
onCommitLayoutEffect={onCommitLayoutEffect}
onScroll={trackVerticalScrolling}
onScrollBeginDrag={onListScrollBeginDrag}
onContentSizeChange={onListContentSizeChange}
Expand All @@ -374,7 +389,7 @@ function MoneyRequestReportActionsListContent({reportIDFromRoute, onLayout}: Mon
contentContainerStyle={[styles.pt5, shouldReserveBulkActionBarSpace && styles.bulkActionBarListSpacing]}
isLoadingInitialActions={isInitialReportLoadPending}
/* This list is not inverted, so the footer is the bottom of the message feed —
the same position the indicator occupies in the inverted ReportActionsList. */
the same position the indicator occupies in ReportActionsList. */
listFooterComponent={<ConciergeThinkingMessage reportID={report.reportID} />}
/>
)}
Expand Down
Loading
Loading