Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
21 changes: 18 additions & 3 deletions src/components/Composer/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import RNTextInput from '@components/RNTextInput';
import Text from '@components/Text';
import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
import withNavigation from '@components/withNavigation';
import useIsScrollBarVisible from '@hooks/useIsScrollBarVisible';
import useWindowDimensions from '@hooks/useWindowDimensions';
import * as Browser from '@libs/Browser';
import compose from '@libs/compose';
Expand Down Expand Up @@ -86,6 +87,9 @@ const propTypes = {
/** Whether the sull composer is open */
isComposerFullSize: PropTypes.bool,

/** Should make the input only scroll inside the element avoid scroll out to parent */
shouldContainScroll: PropTypes.bool,

...withLocalizePropTypes,
};

Expand Down Expand Up @@ -113,6 +117,7 @@ const defaultProps = {
checkComposerVisibility: () => false,
isReportActionCompose: false,
isComposerFullSize: false,
shouldContainScroll: false,
};

/**
Expand Down Expand Up @@ -164,6 +169,7 @@ function Composer({
selection: selectionProp,
isReportActionCompose,
isComposerFullSize,
shouldContainScroll,
...props
}) {
const theme = useTheme();
Expand All @@ -180,6 +186,7 @@ function Composer({
const [caretContent, setCaretContent] = useState('');
const [valueBeforeCaret, setValueBeforeCaret] = useState('');
const [textInputWidth, setTextInputWidth] = useState('');
const isScrollBarVisible = useIsScrollBarVisible(textInput, value);

useEffect(() => {
if (!shouldClear) {
Expand Down Expand Up @@ -418,18 +425,26 @@ function Composer({
</View>
);

const inputStyleMemo = useMemo(
() => [
const scrollStyleMemo = useMemo(() => {
if (shouldContainScroll) {
return isScrollBarVisible ? [styles.overflowScroll, styles.overscrollBehaviorContain] : styles.overflowHidden;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

are we intentionally hiding the overflow(I.e scrollbar) when isScrollBarVisible=false

@suneox suneox Dec 12, 2023

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.

Yes because when a scrollbar is invisible the overflow hidden is also not affected,
And on ios Safari needs this attribute due to the default input composer having set overflowXHidden so overflowY on safari will be auto
Screenshot 2023-12-13 at 01 58 28

so we need to set it to hidden until the scrollbar is visible

}
return [
// We are hiding the scrollbar to prevent it from reducing the text input width,
// so we can get the correct scroll height while calculating the number of lines.
numberOfLines < maxLines ? styles.overflowHidden : {},
];
}, [shouldContainScroll, isScrollBarVisible, maxLines, numberOfLines, styles.overflowHidden, styles.overflowScroll, styles.overscrollBehaviorContain]);

const inputStyleMemo = useMemo(
() => [
StyleSheet.flatten([style, {outline: 'none'}]),
StyleUtils.getComposeTextAreaPadding(numberOfLines, isComposerFullSize),
Browser.isMobileSafari() || Browser.isSafari() ? styles.rtlTextRenderForSafari : {},
scrollStyleMemo,
],

[numberOfLines, maxLines, styles.overflowHidden, styles.rtlTextRenderForSafari, style, StyleUtils, isComposerFullSize],
[numberOfLines, scrollStyleMemo, styles.rtlTextRenderForSafari, style, StyleUtils, isComposerFullSize],
);

return (
Expand Down
7 changes: 7 additions & 0 deletions src/hooks/useIsScrollBarVisible/index.native.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* Native doesn't have the DOM, so we just return null.
*
*/
const useIsScrollBarVisible = () => null;

export default useIsScrollBarVisible;
28 changes: 28 additions & 0 deletions src/hooks/useIsScrollBarVisible/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {useCallback, useEffect, useState} from 'react';

const useIsScrollBarVisible = (ref: React.RefObject<HTMLDivElement | HTMLTextAreaElement>, value: string) => {
const [isScrollBarVisible, setIsScrollBarVisible] = useState(false);

const handleResize = useCallback(() => {
if (!ref.current) {
return;
}
const {scrollHeight, clientHeight} = ref.current;
setIsScrollBarVisible(scrollHeight > clientHeight);
}, [ref]);

useEffect(() => {
if (!ref.current || !('ResizeObserver' in (window || {}))) {
return;
}

const resizeObserver = new ResizeObserver(handleResize);
resizeObserver.observe(ref.current);
return () => {
resizeObserver.disconnect();
};
}, [handleResize, ref, value]);
return isScrollBarVisible;
};

export default useIsScrollBarVisible;
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import useDebounce from '@hooks/useDebounce';
import useLocalize from '@hooks/useLocalize';
import usePrevious from '@hooks/usePrevious';
import useWindowDimensions from '@hooks/useWindowDimensions';
import * as Browser from '@libs/Browser';
import canFocusInputOnScreenFocus from '@libs/canFocusInputOnScreenFocus';
import compose from '@libs/compose';
import * as ComposerUtils from '@libs/ComposerUtils';
Expand Down Expand Up @@ -556,6 +557,7 @@ function ComposerWithSuggestions({
setComposerHeight(composerLayoutHeight);
}}
onScroll={hideSuggestionMenu}
shouldContainScroll={Browser.isMobileSafari()}
/>
</View>

Expand Down