Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
268 commits
Select commit Hold shift + click to select a range
5717b79
refactor: editing logic
chrispader Feb 13, 2026
d45f5d1
refactor: composer ref refactorings
chrispader Feb 13, 2026
e7615d1
feat: only allow editing one comment
chrispader Feb 13, 2026
5494c9c
remove unused code
chrispader Feb 13, 2026
7b25160
refactor: move editing state up into context
chrispader Feb 13, 2026
ade943e
remove: console.log
chrispader Feb 13, 2026
722699e
feat: implement persisting text selection
chrispader Feb 13, 2026
f450f44
fix: missing React import
chrispader Feb 13, 2026
c363712
fix: message editing
chrispader Feb 13, 2026
3672eed
fix: draft not unset
chrispader Feb 13, 2026
c48635a
fix: pass `originalReportID`
chrispader Feb 13, 2026
e520745
fix: allow passing unset reportAction
chrispader Feb 13, 2026
4cbc1ba
fix: allow passing empty draft to delete message
chrispader Feb 13, 2026
e7f098a
fix: add debounce to draft message save
chrispader Feb 13, 2026
cb8d969
fix: TS error
chrispader Feb 13, 2026
34cae56
Merge branch 'main' into pr/76741
chrispader Feb 16, 2026
a86daae
fix: default string in `useOnyx` hook
chrispader Feb 16, 2026
2803685
fix: remove unused import
chrispader Feb 16, 2026
5b4103c
fix: pass missing `isEditing` flag
chrispader Feb 16, 2026
05b972d
test: extract logic into test utils
chrispader Feb 16, 2026
ce4fecc
fix: pass `maxCommentLength` prop
chrispader Feb 16, 2026
ba5f1c0
remove unused code
chrispader Feb 16, 2026
8c7e1d3
fix: use checkmark icon for editing in send button
chrispader Feb 16, 2026
a9c5406
fix: tests with new `SendButton`
chrispader Feb 16, 2026
682ea8a
fix: extract COMPOSER test id
chrispader Feb 16, 2026
106bc0e
refactor: SendButton props
chrispader Feb 16, 2026
42c9dc6
fix: ignore react-hooks rule specifically
chrispader Feb 16, 2026
e6451cc
Update index.ts
chrispader Feb 16, 2026
672a989
Merge branch 'main' into pr/76741
chrispader Feb 16, 2026
c588aac
fix: simplify `useImperativeHandle` for `ComposerWithSuggestions`
chrispader Feb 16, 2026
7b87df4
feat: also add `DraftMessageVideoAttributeCache` for `ReportActionCom…
chrispader Feb 16, 2026
cba88e8
Update ReportActionItemMessageEdit.tsx
chrispader Feb 16, 2026
225d2b9
fix: remove unknown cast
chrispader Feb 16, 2026
58e986a
refactor: remove unnecessary callback
chrispader Feb 17, 2026
0aa900a
refactor: simplify `ReportActionsList` draftMessage retrieval
chrispader Feb 17, 2026
1bd7e17
refactor: rename `handleSendMessage` callback
chrispader Feb 17, 2026
89395b7
fix: `SendButton` disabled state
chrispader Feb 17, 2026
64e4b88
fix: value resetting immediately
chrispader Feb 17, 2026
fa8a9f2
fix: only clear composer value when we switch from narrow to wide layout
chrispader Feb 17, 2026
fa328e6
fix: clearing and resetting of composer and edit message composers
chrispader Feb 17, 2026
d8f03f8
fix: composer value states
chrispader Feb 17, 2026
3e133b1
fix: focus composer after setting selection
chrispader Feb 17, 2026
485c0f7
fix: composer value states
chrispader Feb 17, 2026
9abe87e
Merge branch 'main' into pr/76741
chrispader Feb 17, 2026
50dec5f
fix: send button incompatible with tests
chrispader Feb 17, 2026
7544264
fix: failing tests
chrispader Feb 17, 2026
43d6e9f
fix: Composer ref
chrispader Feb 17, 2026
6692e98
Update ReportActionComposeUtils.tsx
chrispader Feb 17, 2026
7942284
revert: unrelated change
chrispader Feb 11, 2026
1dd03ba
fix: extract `ReportActionComposeWrapper` to utils
chrispader Feb 17, 2026
13e4d6f
fix: extract composer handle
chrispader Feb 17, 2026
f56af3e
fix: composer ref
chrispader Feb 17, 2026
bc3276c
Merge branch 'main' into pr/76741
chrispader Feb 17, 2026
acb4a53
Merge branch 'main' into pr/76741
chrispader Feb 23, 2026
6086a30
Merge branch 'main' into pr/76741
chrispader Feb 24, 2026
ba352b5
fix: remove `useOnyx` `canBeMissing` flag
chrispader Feb 24, 2026
2cd9f35
Merge branch 'main' into pr/76741
chrispader Feb 24, 2026
c7d92fc
Merge branch 'main' into pr/76741
chrispader Feb 25, 2026
32d196b
refactor: remove unused gesture detector test id
chrispader Feb 25, 2026
8ae2a53
fix: duplicate click event
chrispader Feb 25, 2026
21f3597
fix: don't update `isCommentEmpty` based on editing message
chrispader Feb 25, 2026
b939f9c
refactor: simplify condition
chrispader Feb 25, 2026
7a1bba6
fix: don't re-mount composer based on editing state
chrispader Feb 25, 2026
8de4c4c
fix: invalid array index on object and refactor
chrispader Feb 25, 2026
dda59f7
fix: Composer imports
chrispader Feb 25, 2026
f8815c8
fix: prettier
chrispader Feb 25, 2026
eed0b02
fix: ComposerRef import
chrispader Feb 25, 2026
9c39ab6
fix: editing main thread message
chrispader Feb 25, 2026
6578581
fix: remove duplicate `deleteDraft` call
chrispader Feb 25, 2026
d9139ce
fix: prevent value changes when comment/draft has been submitted
chrispader Feb 25, 2026
b3901c3
fix: remove `canBeMissing` flag
chrispader Feb 25, 2026
cd765b5
Merge branch 'main' into pr/76741
chrispader Feb 25, 2026
85892fa
fix: remove unused import
chrispader Feb 25, 2026
27a7f88
Merge branch 'main' into pr/76741
chrispader Feb 27, 2026
670efa2
Merge branch 'main' into pr/76741
chrispader Mar 10, 2026
f26156d
Merge branch 'main' into pr/76741
chrispader Mar 10, 2026
37dfe56
refactor: remove unnecessary nested folder
chrispader Mar 10, 2026
b9c7105
refactor: update `ComposerWithSuggestions` import
chrispader Mar 10, 2026
471fa77
refactor: import order
chrispader Mar 10, 2026
400a1e1
fix: improve editing state ref
chrispader Mar 10, 2026
65ee2fc
fix: inconsistent state naming in `ComposerWithSuggestions`
chrispader Mar 10, 2026
b450e88
fix: don't clear composer if editing and draftComment is set
chrispader Mar 10, 2026
6db8fba
fix: edit draft not being saved
chrispader Mar 10, 2026
340b98a
fix: manually set the editing report action without debounce
chrispader Mar 10, 2026
22a579d
refactor: use editing state ref
chrispader Mar 10, 2026
ff433aa
refactor: use state instead of ref in render
chrispader Mar 10, 2026
6bad09b
refactor: pass along set state action
chrispader Mar 10, 2026
db08232
refactor: unnecessary check for editing state
chrispader Mar 10, 2026
1a44924
fix: selection issues
chrispader Mar 10, 2026
130be1a
fix: reset to previous draft when switching from narrow to wide
chrispader Mar 10, 2026
1e0fcec
fix: onSelectionChange crashing
chrispader Mar 10, 2026
0bc83b9
fix: simplify `setSelection` call
chrispader Mar 10, 2026
4da5159
refactor: extract imperative composer selection callback
chrispader Mar 10, 2026
125e106
refactor: change default selection type
chrispader Mar 10, 2026
fb0d927
refactor: remove hardcoded selection position value
chrispader Mar 10, 2026
bf110c0
fix: composer text selection on edit
chrispader Mar 11, 2026
246888b
fix: go back to previous selection if editing stops
chrispader Mar 11, 2026
589ea10
fix: remove unnecessary `ONYXKEYS.SHOULD_SHOW_COMPOSE_INPUT` key
chrispader Mar 11, 2026
63bf657
fix: always show composer
chrispader Mar 11, 2026
eafcebe
refactor: remove unused variable
chrispader Mar 11, 2026
cc4d336
fix: clear report action drafts when report is switched
chrispader Mar 11, 2026
e8417cb
refactor: simplify report action draft clearance
chrispader Mar 11, 2026
c63f242
fix: editing thread messages
chrispader Mar 12, 2026
f90b647
refactor: remove manual memoization
chrispader Mar 12, 2026
ad44dfd
fix: use editing message state in `ReportActionItemMessageEdit`
chrispader Mar 12, 2026
a2c9308
refactor: Remove `useEffect` in and simplify `ReportActionEditMessage…
chrispader Mar 12, 2026
0b1037c
feat: allow full size composer while editing
chrispader Mar 12, 2026
84718e9
feat: allow editing multiple parent reports
chrispader Mar 12, 2026
8d3ad90
Merge branch 'main' into pr/76741
chrispader Mar 12, 2026
657132e
Merge branch 'main' into pr/76741
chrispader Mar 17, 2026
fc0f17c
refactor: split context into separate state and actions
chrispader Mar 17, 2026
8865cb1
fix: update editing state if report action changes
chrispader Mar 17, 2026
34240c3
refactor: remove redundant `parentReportAction` path in favor of ance…
chrispader Mar 17, 2026
16f7df3
fix: text selection on comment update and refactor
chrispader Mar 17, 2026
0ca44fc
fix: TS checks
chrispader Mar 17, 2026
e526745
Merge branch 'main' into pr/76741
chrispader Mar 18, 2026
58de521
refactor: rename callback
chrispader Mar 18, 2026
e559a9f
fix: reset whole state on edit end
chrispader Mar 18, 2026
03c9d87
fix: composer height on edit
chrispader Mar 18, 2026
99f85eb
remove console.log
chrispader Mar 18, 2026
0f7ccca
Merge branch 'main' into pr/76741
chrispader Mar 18, 2026
4142b73
fix: TS and ESLint errors
chrispader Mar 18, 2026
74ae3d5
Merge branch 'main' into pr/76741
chrispader Mar 18, 2026
c090745
fix: send button disabled state
chrispader Mar 18, 2026
8c544f4
fix: send button disabled after editing
chrispader Mar 18, 2026
33c37bc
refactor: use editing state instead of report action id
chrispader Mar 18, 2026
4c13fd2
fix: remove console.log
chrispader Mar 18, 2026
ac4ef84
refactor: extract and memoize styles
chrispader Mar 18, 2026
9bbe7b3
refactor: simplify `ExpandCollapseComposerButton` component and remov…
chrispader Mar 18, 2026
9db5daa
chore: add back JSDoc comment
chrispader Mar 18, 2026
fcb41fb
test: add integration test for task title length validation in Report…
chrispader Mar 18, 2026
59f8a18
test: add more ui tests
chrispader Mar 18, 2026
49845d2
test: add unit tests for new hooks
chrispader Mar 18, 2026
e719285
refactor: change file type
chrispader Mar 18, 2026
d984925
refactor: fix imports
chrispader Mar 18, 2026
707f526
Merge branch 'main' into pr/76741
chrispader Mar 23, 2026
e6fbee3
Merge branch 'main' into pr/76741
chrispader Mar 24, 2026
9ddf956
fix: always delete all edit message drafts
chrispader Mar 24, 2026
d5ca35c
Merge branch 'main' into pr/76741
chrispader Mar 24, 2026
8324e33
fix: `Onyx.merge` and `Onyx.setCollection` not queued in the correct …
chrispader Mar 24, 2026
96dd851
refactor: other usages of Onyx draft message key
chrispader Mar 24, 2026
d01abbd
refactor: add back selectors for report actions and drafts
chrispader Mar 24, 2026
4eddd23
refactor: remove unnecessary `clearReportActionDrafts` call
chrispader Mar 24, 2026
c0d3909
refactor: make `editingState` non-nullable
chrispader Mar 25, 2026
c1683ac
fix: add missing import in tests
chrispader Mar 25, 2026
f33f1a7
refactor: remove unused import
chrispader Mar 25, 2026
730a787
fix: editing stops after delete message modal opens
chrispader Mar 25, 2026
00cb085
refactor: remove `cancelled` editing state
chrispader Mar 25, 2026
0b59e75
fix: remove unused import
chrispader Mar 25, 2026
1ace4ee
Merge branch 'main' into pr/76741
chrispader Mar 25, 2026
4f1fa08
refactor: simplify `ComposerWithSuggestions` edit state effect by add…
chrispader Mar 25, 2026
7e0cb25
fix: composer toggle key based on report action id
chrispader Mar 25, 2026
af1870b
Merge branch 'main' into pr/76741
chrispader Mar 25, 2026
ba5f1c5
fix: selection issues after publishing an edit
chrispader Mar 25, 2026
0d45d8f
Merge branch 'main' into pr/76741
chrispader Mar 25, 2026
1f846cc
Merge branch 'main' into pr/76741
chrispader Mar 26, 2026
c9c3a2a
Merge branch 'main' into pr/76741
chrispader Apr 3, 2026
d71b213
fix: composer not visible
chrispader Apr 3, 2026
585c3d3
fix: blur composer when the user stops editing
chrispader Apr 3, 2026
02eabb6
fix: only blur composer focus when no draft comment exists
chrispader Apr 3, 2026
c8df1b2
Revert "refactor: simplify `ComposerWithSuggestions` edit state effec…
chrispader Apr 3, 2026
eaca09d
fix: natively update the text value to force re-layout
chrispader Apr 3, 2026
acfd28c
fix: remove unused code
chrispader Apr 3, 2026
383ec9f
fix: remove unnecessary duplicate update of composer value
chrispader Apr 3, 2026
00bb466
fix: composer resizing without adding react key to composer
chrispader Apr 3, 2026
85bc5cb
fix: restore previous focus after editing stops
chrispader Apr 3, 2026
7f6e757
fix: still update composer value when editingReportAction changes
chrispader Apr 3, 2026
da8420a
Merge branch 'main' into pr/76741
chrispader Apr 9, 2026
a3a5f44
Merge branch 'kureev/new-editing-model' of https://github.com/margelo…
chrispader Apr 9, 2026
79c1db5
Merge branch 'main' into pr/76741
chrispader Apr 9, 2026
518a807
Merge branch 'main' into pr/76741
chrispader Apr 10, 2026
1c3992f
Update Mobile-Expensify
chrispader Apr 10, 2026
da6c220
fix: TS and ESLint errors
chrispader Apr 10, 2026
cf683c4
Merge branch 'main' into pr/76741
chrispader Apr 10, 2026
e3ca84d
fix: apply changes from PR also in newly added `ComposerProvider` and…
chrispader Apr 10, 2026
99c6168
Update package-lock.json
chrispader Apr 10, 2026
84af4ce
Revert "Update package-lock.json"
chrispader Apr 10, 2026
acc8ba5
fix: TS and ESLint errors
chrispader Apr 10, 2026
adc7b33
Merge branch 'main' into pr/76741
chrispader Apr 13, 2026
408e50e
Merge branch 'main' into pr/76741
chrispader Apr 16, 2026
a610c28
fix: React Compiler compliance
chrispader Apr 16, 2026
810f94a
fix: `ImageSVG` icon fill color not updating on Android
chrispader Apr 17, 2026
cfd172d
fix: `isCommentEmpty` not updated after editing stopped
chrispader Apr 17, 2026
0b74044
refactor: simplify composer focus in `ReportActionCompose`
chrispader Apr 17, 2026
4891390
fix: allow re-focussing input on Android
chrispader Apr 17, 2026
64430aa
refactor: use `focusComposerWithDelay` in `refocusComposerAfterPreven…
chrispader Apr 17, 2026
17b0a31
fix: composer not focussing in edit mode
chrispader Apr 17, 2026
ac41780
fix: remove unused import
chrispader Apr 17, 2026
9a285ed
fix: prevent composer blur when edit mode is disabled
chrispader Apr 17, 2026
bc3093d
refactor: extract composer toggle logic into separate hook
chrispader Apr 17, 2026
70b73f3
docs: update JSDoc of `useEditComposerToggle`
chrispader Apr 17, 2026
e341cc0
Merge branch 'main' into pr/76741
chrispader Apr 20, 2026
32088ca
refactor: move `onValueChange` to `ComposerInput`
chrispader Apr 20, 2026
2aa7159
fix: expand/collapse button components
chrispader Apr 20, 2026
5b2fffe
Merge branch 'main' into pr/76741
chrispader Apr 20, 2026
4de296f
fix: missing import of React
chrispader Apr 20, 2026
6c9d19f
fix: add back `KeyboardStateProvider` to `ReportScreenProviders` for …
chrispader Apr 20, 2026
5f08a7f
fix: add missing React import
chrispader Apr 20, 2026
ab909d9
Merge branch 'main' into pr/76741
chrispader Apr 20, 2026
e43a493
fix: use extracted message edit test components
chrispader Apr 20, 2026
4331149
fix: tests
chrispader Apr 20, 2026
337cccc
revert: reassure test changes
chrispader Apr 21, 2026
8999859
Merge branch 'main' into pr/76741
chrispader Apr 21, 2026
0f59b45
fix: don't remove edit state when screen is not focused
chrispader Apr 21, 2026
05a45af
fix: clear report action drafts on mount and unmount
chrispader Apr 21, 2026
26338f0
fix: context menu opening in edit mode
chrispader Apr 21, 2026
4c01dff
refactor: move draftMessage derivation to `PureReportActionItem`
chrispader Apr 21, 2026
7e1db37
fix: cursor not updating when emoji is picked
chrispader Apr 21, 2026
8576e17
Merge branch 'main' into pr/76741
chrispader Apr 21, 2026
ec639ef
refactor: remove commented out code
chrispader Apr 21, 2026
88fa159
refactor: Composer web implementation improvements
chrispader Apr 21, 2026
33929df
refactor: `ComposerProvider` improvements
chrispader Apr 21, 2026
ebfda2a
fix: duplicate selection update
chrispader Apr 21, 2026
e5d0a57
fix: inifinite setState loop due to missing memoization in `PureRepor…
chrispader Apr 21, 2026
bf651d8
fix: add back draft message prop for `DuplicateTransactionItem`
chrispader Apr 22, 2026
5a191fc
refactor: update `clearAllReportActionDrafts` function name and JSDoc
chrispader Apr 22, 2026
061ae7e
refactor: extract `requestKeyboardForFocusedComposer` from `focusComp…
chrispader Apr 22, 2026
02ffbc3
fix: disable `shouldScrollToLastMessage` for composer editing
chrispader Apr 22, 2026
a37aea2
Merge branch 'main' into pr/76741
chrispader Apr 22, 2026
75eec23
fix: also run `SilentCommentUpdater` on narrow layout
chrispader Apr 22, 2026
1f068a1
fix: only apply selection after focus in `focusComposerWithDelay`
chrispader Apr 22, 2026
99232e6
test: add unit and ui tests for new editing modes
chrispader Apr 22, 2026
e919d28
chore: add test ids to editing components
chrispader Apr 22, 2026
62ffcc3
test: remove redundant `act()` and fix ESLint errors
chrispader Apr 22, 2026
90f5715
test: improve `ReportActionMessageEditLayoutTest` suite and handle mo…
chrispader Apr 22, 2026
108f842
fix: move testID to pressable
chrispader Apr 22, 2026
2289cd5
fix: test mock type errors
chrispader Apr 22, 2026
1760177
Merge branch 'main' into pr/76741
chrispader Apr 22, 2026
0699582
Merge branch 'main' into pr/76741
chrispader Apr 23, 2026
3fd68a7
fix: `clearComposer` callback invalid use of `scheduleOnUI`
chrispader Apr 23, 2026
b6f0cae
Merge branch 'main' into pr/76741
chrispader Apr 24, 2026
28f9b82
fix: forward expand/collapse button style
chrispader Apr 24, 2026
186adec
Merge branch 'main' into pr/76741
chrispader Apr 28, 2026
aa394b7
fix: remove `canEvict` `useOnyx` flag
chrispader Apr 28, 2026
a1ab2cb
Merge branch 'main' into pr/76741
chrispader Apr 28, 2026
fde16e7
Merge branch 'main' into pr/76741
chrispader Apr 29, 2026
9117a24
refactor: use `useComposerEditState` instead of `useReportActionActiv…
chrispader Apr 29, 2026
83c613d
refactor: move `didResetComposerHeight` reset call to `useEditCompose…
chrispader Apr 29, 2026
3421d13
refactor: remove `InteractionManager.runAfterInteractions`
chrispader Apr 29, 2026
30f26fe
refactor: remove manual memoization from `useDebounce`
chrispader Apr 29, 2026
6dede48
fix: callback memoization not able to get preserved (RC)
chrispader Apr 29, 2026
1eb90fd
refactor: extract debounced save functions to separate hook
chrispader Apr 29, 2026
0e13386
fix: keep `editingReportActionID` as a dependency of debounced saveDr…
chrispader Apr 29, 2026
5536df0
test: fix invalid mock
chrispader Apr 29, 2026
a2475f3
refactor: draftMessage usage
chrispader Apr 29, 2026
a82b009
chore: fix `eslint-seatbelt` complaining about moved file
chrispader Apr 29, 2026
59959c7
Merge branch 'main' into pr/76741
chrispader Apr 29, 2026
aba42c0
Merge branch 'main' into pr/76741
chrispader Apr 29, 2026
3a5e5fe
fix: update draftComment composer hook
chrispader Apr 29, 2026
2b422eb
fix: remove unused `useOnyx` call
chrispader Apr 29, 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
4 changes: 2 additions & 2 deletions config/eslint/eslint.seatbelt.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -379,8 +379,8 @@
"../../src/pages/inbox/report/PureReportActionItem.tsx" "react-hooks/refs" 2
"../../src/pages/inbox/report/PureReportActionItem.tsx" "react-hooks/set-state-in-effect" 1
"../../src/pages/inbox/report/ReactionList/HeaderReactionList.tsx" "no-restricted-syntax" 1
"../../src/pages/inbox/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx" "react-hooks/preserve-manual-memoization" 2
"../../src/pages/inbox/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx" "react-hooks/refs" 8
"../../src/pages/inbox/report/ReportActionCompose/ComposerWithSuggestions.tsx" "react-hooks/preserve-manual-memoization" 2
"../../src/pages/inbox/report/ReportActionCompose/ComposerWithSuggestions.tsx" "react-hooks/refs" 8
"../../src/pages/inbox/report/ReportActionCompose/SuggestionEmoji.tsx" "react-hooks/refs" 1
"../../src/pages/inbox/report/ReportActionCompose/SuggestionMention.tsx" "react-hooks/refs" 3
"../../src/pages/inbox/report/ReportActionItemMessageEdit.tsx" "no-restricted-syntax" 1
Expand Down
12 changes: 12 additions & 0 deletions src/CONST/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1832,6 +1832,18 @@ const CONST = {
MAX_LINES_FULL: -1,
// The minimum height needed to enable the full screen composer
FULL_COMPOSER_MIN_HEIGHT: 60,
/**
* TestIDs for the main report composer vs inline message editor (E2E / integration tests only).
* See tests/ui/ReportActionMessageEditLayoutTest.tsx
*/
TEST_ID: {
REPORT_ACTION_COMPOSE: 'reportActionCompose',
DRAFT_MESSAGE_ACTION_ROW: 'reportActionCompose_draftMessageActionRow',
EDITING_MESSAGE_ACTION_ROW: 'reportActionCompose_editingMessageActionRow',
REPORT_ACTION_ITEM_MESSAGE_EDIT: 'reportActionItemMessageEdit',
MESSAGE_EDIT_CANCEL_MAIN_COMPOSER: 'messageEditCancel_mainComposer',
MESSAGE_EDIT_CANCEL_INLINE: 'messageEditCancel_inlineMessageEdit',
},
},
MODAL: {
MODAL_TYPE: {
Expand Down
4 changes: 0 additions & 4 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -417,9 +417,6 @@ const ONYXKEYS = {
/** The policyID of the last workspace whose settings were accessed by the user */
LAST_ACCESSED_WORKSPACE_POLICY_ID: 'lastAccessedWorkspacePolicyID',

/** Whether we should show the compose input or not */
SHOULD_SHOW_COMPOSE_INPUT: 'shouldShowComposeInput',

/** Is app in beta version */
IS_BETA: 'isBeta',

Expand Down Expand Up @@ -1448,7 +1445,6 @@ type OnyxValuesMapping = {
[ONYXKEYS.HAS_LOADED_APP]: boolean;
[ONYXKEYS.WALLET_TRANSFER]: OnyxTypes.WalletTransfer;
[ONYXKEYS.LAST_ACCESSED_WORKSPACE_POLICY_ID]: string;
[ONYXKEYS.SHOULD_SHOW_COMPOSE_INPUT]: boolean;
[ONYXKEYS.IS_BETA]: boolean;
[ONYXKEYS.RAM_ONLY_IS_CHECKING_PUBLIC_ROOM]: boolean;
[ONYXKEYS.MY_DOMAIN_SECURITY_GROUPS]: Record<string, string>;
Expand Down
18 changes: 9 additions & 9 deletions src/components/Composer/implementation/index.native.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import type {MarkdownStyle} from '@expensify/react-native-live-markdown';
import type {MarkdownStyle, MarkdownTextInput} from '@expensify/react-native-live-markdown';
import mimeDb from 'mime-db';
import React, {useCallback, useEffect, useMemo, useRef} from 'react';
import type {NativeSyntheticEvent, TextInputChangeEvent, TextInputPasteEventData} from 'react-native';
import {StyleSheet} from 'react-native';
import type {ComposerProps} from '@components/Composer/types';
import type {ComposerProps, ComposerRef} from '@components/Composer/types';
import type {AnimatedMarkdownTextInputRef} from '@components/RNMarkdownTextInput';
import RNMarkdownTextInput from '@components/RNMarkdownTextInput';
import useIsInLandscapeMode from '@hooks/useIsInLandscapeMode';
Expand Down Expand Up @@ -38,7 +38,7 @@ function Composer({
ref,
...props
}: ComposerProps) {
const textInput = useRef<AnimatedMarkdownTextInputRef | null>(null);
const textInputRef = useRef<MarkdownTextInput | null>(null);
const textContainsOnlyEmojis = useMemo(() => containsOnlyEmojis(Parser.htmlToText(Parser.replace(value ?? ''))), [value]);
const theme = useTheme();
const markdownStyle = useMarkdownStyle(textContainsOnlyEmojis, !isGroupPolicyReport ? excludeReportMentionStyle : excludeNoStyles);
Expand All @@ -47,7 +47,7 @@ function Composer({
const isInLandscapeMode = useIsInLandscapeMode();

useEffect(() => {
if (!textInput.current?.setSelection || !selection || isComposerFullSize) {
if (!textInputRef.current?.setSelection || !selection || isComposerFullSize) {
return;
}

Expand All @@ -56,8 +56,8 @@ function Composer({
// (see https://github.com/Expensify/App/pull/50520#discussion_r1861960311 for more context)
const timeoutID = setTimeout(() => {
// We are setting selection twice to trigger a scroll to the cursor on toggling to smaller composer size.
textInput.current?.setSelection((selection.start || 1) - 1, selection.start);
textInput.current?.setSelection(selection.start, selection.start);
textInputRef.current?.setSelection((selection.start || 1) - 1, selection.start);
textInputRef.current?.setSelection(selection.start, selection.start);
}, 0);

return () => clearTimeout(timeoutID);
Expand All @@ -71,17 +71,17 @@ function Composer({
*/
const setTextInputRef = useCallback(
(el: AnimatedMarkdownTextInputRef | null) => {
textInput.current = isInLandscapeMode ? getLandscapeTextInputRefProxy(el) : el;
textInputRef.current = isInLandscapeMode ? getLandscapeTextInputRefProxy(el) : el;

if (typeof ref !== 'function' || textInput.current === null) {
if (typeof ref !== 'function' || textInputRef.current === null) {
return;
}

// This callback prop is used by the parent component using the constructor to
// get a ref to the inner textInput element e.g. if we do
// <constructor ref={el => this.textInput = el} /> this will not
// return a ref to the component, but rather the HTML element by default
ref(textInput.current);
ref(textInputRef.current as ComposerRef);
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[isInLandscapeMode],
Expand Down
91 changes: 50 additions & 41 deletions src/components/Composer/implementation/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import React, {useCallback, useEffect, useImperativeHandle, useMemo, useRef, use
// eslint-disable-next-line no-restricted-imports
import type {TextInputKeyPressEvent, TextInputSelectionChangeEvent} from 'react-native';
import {DeviceEventEmitter, StyleSheet} from 'react-native';
import type {ComposerProps} from '@components/Composer/types';
import type {ComposerProps, ComposerRef} from '@components/Composer/types';
import {useSession} from '@components/OnyxListItemProvider';
import type {AnimatedMarkdownTextInputRef} from '@components/RNMarkdownTextInput';
import RNMarkdownTextInput from '@components/RNMarkdownTextInput';
Expand Down Expand Up @@ -65,7 +65,7 @@ function Composer({
const addAuthTokenToImageURL = useCallback((url: string) => addEncryptedAuthTokenToURL(url, encryptedAuthToken), [encryptedAuthToken]);
const markdownStyle = useMarkdownStyle(textContainsOnlyEmojis, !isGroupPolicyReport ? excludeReportMentionStyle : excludeNoStyles);
const StyleUtils = useStyleUtils();
const textInput = useRef<AnimatedMarkdownTextInputRef | null>(null);
const textInputRef = useRef<AnimatedMarkdownTextInputRef | null>(null);
const [selection, setSelection] = useState<
| {
start: number;
Expand All @@ -80,7 +80,7 @@ function Composer({
});
const [isRendered, setIsRendered] = useState(false);

const isScrollBarVisible = useIsScrollBarVisible(textInput, value ?? '');
const isScrollBarVisible = useIsScrollBarVisible(textInputRef, value ?? '');
const [prevScroll, setPrevScroll] = useState<number | undefined>();
const [prevHeight, setPrevHeight] = useState<number | undefined>();
const isReportFlatListScrolling = useRef(false);
Expand All @@ -96,19 +96,26 @@ function Composer({
/**
* Adds the cursor position to the selection change event.
*/
const addCursorPositionToSelectionChange = (event: TextInputSelectionChangeEvent) => {
const sel = window.getSelection();
if (shouldCalculateCaretPosition && isRendered && sel) {
const addCursorPositionToSelectionChange = useCallback(
(event: TextInputSelectionChangeEvent) => {
const sel = window.getSelection();
const canCalculateCaretPosition = shouldCalculateCaretPosition && isRendered && sel;
if (!canCalculateCaretPosition) {
onSelectionChange(event);
setSelection(event.nativeEvent.selection);
return;
}

const range = sel.getRangeAt(0).cloneRange();
range.collapse(true);
const rect = range.getClientRects()[0] || range.startContainer.parentElement?.getClientRects()[0];
const containerRect = textInput.current?.getBoundingClientRect();
const containerRect = textInputRef.current?.getBoundingClientRect();

let x = 0;
let y = 0;
if (rect && containerRect) {
x = rect.left - containerRect.left;
y = rect.top - containerRect.top + (textInput?.current?.scrollTop ?? 0) - rect.height / 2;
y = rect.top - containerRect.top + (textInputRef?.current?.scrollTop ?? 0) - rect.height / 2;
}

const selectionValue = {
Expand All @@ -126,11 +133,9 @@ function Composer({
},
});
setSelection(selectionValue);
} else {
onSelectionChange(event);
setSelection(event.nativeEvent.selection);
}
};
},
[isRendered, onSelectionChange, shouldCalculateCaretPosition],
);

/**
* Check the paste event for an attachment, parse the data and call onPasteFile from props with the selected file,
Expand All @@ -139,14 +144,14 @@ function Composer({
const handlePaste = useCallback(
(event: ClipboardEvent) => {
const isVisible = checkComposerVisibility();
const isFocused = textInput.current?.isFocused();
const isFocused = textInputRef.current?.isFocused();
const isContenteditableDivFocused = document.activeElement?.nodeName === 'DIV' && document.activeElement?.hasAttribute('contenteditable');

if (!(isVisible || isFocused)) {
return true;
}

if (textInput.current !== event.target && !(isContenteditableDivFocused && !event.clipboardData?.files.length)) {
if (textInputRef.current !== event.target && !(isContenteditableDivFocused && !event.clipboardData?.files.length)) {
const eventTarget = event.target as HTMLInputElement | HTMLTextAreaElement | null;
// To make sure the composer does not capture paste events from other inputs, we check where the event originated
// If it did originate in another input, we return early to prevent the composer from handling the paste
Expand All @@ -155,7 +160,7 @@ function Composer({
return true;
}

textInput.current?.focus();
textInputRef.current?.focus();
}

event.preventDefault();
Expand Down Expand Up @@ -210,19 +215,23 @@ function Composer({
);

useEffect(() => {
if (!textInput.current) {
if (!textInputRef.current) {
return;
}

const inputRef = textInputRef.current;

const debouncedSetPrevScroll = lodashDebounce(() => {
if (!textInput.current) {
if (!inputRef) {
return;
}
setPrevScroll(textInput.current.scrollTop);
setPrevScroll(inputRef.scrollTop);
}, 100);

textInput.current.addEventListener('scroll', debouncedSetPrevScroll);
inputRef.addEventListener('scroll', debouncedSetPrevScroll);

return () => {
textInput.current?.removeEventListener('scroll', debouncedSetPrevScroll);
inputRef?.removeEventListener('scroll', debouncedSetPrevScroll);
};
}, []);

Expand All @@ -235,6 +244,8 @@ function Composer({
}, []);

useEffect(() => {
const inputRef = textInputRef.current;

const handleWheel = (e: MouseEvent) => {
if (isReportFlatListScrolling.current) {
e.preventDefault();
Expand All @@ -243,40 +254,40 @@ function Composer({

// When the composer has no scrollable content, the stopPropagation will prevent the inverted wheel event handler on the Chat body
// which defaults to the browser wheel behavior. This causes the chat body to scroll in the opposite direction creating jerky behavior.
if (textInput.current && textInput.current.scrollHeight <= textInput.current.clientHeight) {
if (inputRef && inputRef.scrollHeight <= inputRef.clientHeight) {
return;
}
e.stopPropagation();
};
textInput.current?.addEventListener('wheel', handleWheel, {passive: false});
inputRef?.addEventListener('wheel', handleWheel, {passive: false});

return () => {
textInput.current?.removeEventListener('wheel', handleWheel);
inputRef?.removeEventListener('wheel', handleWheel);
};
}, []);

useEffect(() => {
if (!textInput.current || prevScroll === undefined || prevHeight === undefined) {
if (!textInputRef.current || prevScroll === undefined || prevHeight === undefined) {
return;
}
textInput.current.scrollTop = prevScroll + prevHeight - textInput.current.clientHeight;
textInputRef.current.scrollTop = prevScroll + prevHeight - textInputRef.current.clientHeight;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isComposerFullSize]);

const isActive = useIsFocused();
useHtmlPaste(textInput, handlePaste, isActive);
useHtmlPaste(textInputRef, handlePaste, isActive);

useEffect(() => {
setIsRendered(true);
}, []);

const clear = useCallback(() => {
if (!textInput.current) {
if (!textInputRef.current) {
return;
}

const currentText = textInput.current.value;
textInput.current.clear();
const currentText = textInputRef.current.value;
textInputRef.current.clear();

// We need to reset the selection to 0,0 manually after clearing the text input on web
const selectionEvent = {
Expand All @@ -294,22 +305,22 @@ function Composer({
}, [onClear, onSelectionChange]);

useImperativeHandle(ref, () => {
const textInputRef = textInput.current;
if (!textInputRef) {
throw new Error('textInputRef is not available. This should never happen and indicates a developer error.');
const textInput = textInputRef.current;
if (!textInput) {
throw new Error('textInput is not available. This should never happen and indicates a developer error.');
}

return {
...textInputRef,
...textInput,
// Overwrite clear with our custom implementation, which mimics how the native TextInput's clear method works
clear,
// We have to redefine these methods as they are inherited by prototype chain and are not accessible directly
blur: () => textInputRef.blur(),
focus: () => textInputRef.focus(),
blur: () => textInput.blur(),
focus: () => textInput.focus(),
get scrollTop() {
return textInputRef.scrollTop;
return textInput.scrollTop;
},
};
} as ComposerRef;
}, [clear]);

const handleKeyPress = useCallback(
Expand Down Expand Up @@ -351,9 +362,7 @@ function Composer({
autoComplete="off"
autoCorrect={!isMobileSafari()}
placeholderTextColor={theme.placeholderText}
ref={(el) => {
textInput.current = el;
}}
ref={textInputRef}
selection={selection}
style={[inputStyleMemo]}
markdownStyle={markdownStyle}
Expand Down
10 changes: 6 additions & 4 deletions src/components/Composer/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type {MarkdownTextInput} from '@expensify/react-native-live-markdown';
import type {Ref} from 'react';
import type {StyleProp, TextInput, TextInputProps, TextInputSelectionChangeEvent, TextStyle} from 'react-native';
import type {AnimatedMarkdownTextInputRef} from '@components/RNMarkdownTextInput';
import type {StyleProp, TextInputProps, TextInputSelectionChangeEvent, TextStyle} from 'react-native';
import type {ForwardedFSClassProps} from '@libs/Fullstory/types';
import type {FileObject} from '@src/types/utils/Attachment';

Expand All @@ -15,6 +15,8 @@ type CustomSelectionChangeEvent = TextInputSelectionChangeEvent & {
positionY?: number;
};

type ComposerRef = MarkdownTextInput & HTMLInputElement & HTMLTextAreaElement;

type ComposerProps = Omit<TextInputProps, 'onClear'> &
ForwardedFSClassProps & {
/** Indicate whether input is multiline */
Expand Down Expand Up @@ -74,7 +76,7 @@ type ComposerProps = Omit<TextInputProps, 'onClear'> &
isGroupPolicyReport?: boolean;

/** Ref exposing imperative methods on the underlying text input */
ref?: Ref<TextInput | HTMLInputElement | HTMLTextAreaElement | AnimatedMarkdownTextInputRef>;
ref?: Ref<ComposerRef>;
};

export type {TextSelection, ComposerProps, CustomSelectionChangeEvent};
export type {TextSelection, ComposerProps, CustomSelectionChangeEvent, ComposerRef};
2 changes: 1 addition & 1 deletion src/components/ExceededCommentLength.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ type ExceededCommentLengthProps = {
isTaskTitle?: boolean;
};

function ExceededCommentLength({maxCommentLength = CONST.MAX_COMMENT_LENGTH, isTaskTitle}: ExceededCommentLengthProps) {
function ExceededCommentLength({maxCommentLength = CONST.MAX_COMMENT_LENGTH, isTaskTitle = false}: ExceededCommentLengthProps) {
const styles = useThemeStyles();
const {numberFormat, translate} = useLocalize();

Expand Down
7 changes: 4 additions & 3 deletions src/components/ImageSVG/index.android.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import getImageRecyclingKey from '@libs/getImageRecyclingKey';
import type ImageSVGProps from './types';

function ImageSVG({src, width = '100%', height = '100%', fill, contentFit = 'cover', style, onLoadEnd}: ImageSVGProps) {
const tintColorProp = fill ? {tintColor: fill} : {};
const isReactComponent = typeof src === 'function';

// Clear memory cache when unmounting images to avoid memory overload
Expand Down Expand Up @@ -61,8 +60,10 @@ function ImageSVG({src, width = '100%', height = '100%', fill, contentFit = 'cov
source={src}
recyclingKey={getImageRecyclingKey(src)}
style={[{width, height}, style as ExpoImageProps['style']]}
// eslint-disable-next-line react/jsx-props-no-spreading
{...tintColorProp}
tintColor={fill}
// On android, there's an issue where the fill color of the icon does not change,
// unless the component is remounted. (https://github.com/Expensify/App/pull/76741#issuecomment-4245274687)
key={fill}
/>
);
}
Expand Down
Loading
Loading