From 8087b521e69bd5773ab201f53815d942ab0bb249 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 29 May 2020 21:42:33 +0100 Subject: [PATCH 1/2] Autocomplete: use scrollIntoView for auto-scroll instead of broken manual scrollTop calculation Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/autocomplete/Components.tsx | 22 +++++++++++++----- src/components/views/rooms/Autocomplete.tsx | 25 ++++++++++----------- 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/src/autocomplete/Components.tsx b/src/autocomplete/Components.tsx index 19a7a969d6..3b4469321d 100644 --- a/src/autocomplete/Components.tsx +++ b/src/autocomplete/Components.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from 'react'; +import React, {createRef} from 'react'; import classNames from 'classnames'; /* These were earlier stateless functional components but had to be converted @@ -30,7 +30,11 @@ interface ITextualCompletionProps { className?: string; } -export class TextualCompletion extends React.PureComponent { +export abstract class Completion extends React.PureComponent { + nodeRef = createRef(); +} + +export class TextualCompletion extends Completion { render() { const { title, @@ -40,7 +44,11 @@ export class TextualCompletion extends React.PureComponent +
{ title } { subtitle } { description } @@ -57,7 +65,7 @@ interface IPillCompletionProps { className?: string; } -export class PillCompletion extends React.PureComponent { +export class PillCompletion extends Completion { render() { const { title, @@ -68,7 +76,11 @@ export class PillCompletion extends React.PureComponent { ...restProps } = this.props; return ( -
+
{ initialComponent } { title } { subtitle } diff --git a/src/components/views/rooms/Autocomplete.tsx b/src/components/views/rooms/Autocomplete.tsx index 975c8e84a5..40f585a5b8 100644 --- a/src/components/views/rooms/Autocomplete.tsx +++ b/src/components/views/rooms/Autocomplete.tsx @@ -15,8 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from 'react'; -import ReactDOM from 'react-dom'; +import React, {createRef} from 'react'; import classNames from 'classnames'; import flatMap from 'lodash/flatMap'; import {ICompletion, ISelectionRange, IProviderCompletions} from '../../../autocomplete/Autocompleter'; @@ -24,6 +23,7 @@ import {Room} from 'matrix-js-sdk/src/models/room'; import SettingsStore from "../../../settings/SettingsStore"; import Autocompleter from '../../../autocomplete/Autocompleter'; +import { Completion } from '../../../autocomplete/Components'; const COMPOSER_SELECTED = 0; @@ -54,7 +54,7 @@ export default class Autocomplete extends React.PureComponent { autocompleter: Autocompleter; queryRequested: string; debounceCompletionsRequest: NodeJS.Timeout; - containerRef: React.RefObject; + private containerRef = createRef(); constructor(props) { super(props); @@ -78,8 +78,6 @@ export default class Autocomplete extends React.PureComponent { forceComplete: false, }; - - this.containerRef = React.createRef(); } componentDidMount() { @@ -256,14 +254,15 @@ export default class Autocomplete extends React.PureComponent { componentDidUpdate(prevProps: IProps) { this.applyNewProps(prevProps.query, prevProps.room); // this is the selected completion, so scroll it into view if needed - const selectedCompletion = this.refs[`completion${this.state.selectionOffset}`]; - if (selectedCompletion && this.containerRef.current) { - const domNode = ReactDOM.findDOMNode(selectedCompletion); - const offsetTop = domNode && (domNode as HTMLElement).offsetTop; - if (offsetTop > this.containerRef.current.scrollTop + this.containerRef.current.offsetHeight || - offsetTop < this.containerRef.current.scrollTop) { - this.containerRef.current.scrollTop = offsetTop - this.containerRef.current.offsetTop; - } + const selectedCompletion = this.refs[`completion${this.state.selectionOffset}`] as Completion; + + if (selectedCompletion && selectedCompletion.nodeRef.current) { + selectedCompletion.nodeRef.current.scrollIntoView({ + behavior: "auto", + block: "nearest", + }); + } else { + this.containerRef.current.scrollTo({ top: 0 }); } } From bc83984a626413443b326cf079bf135a62993d06 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sat, 30 May 2020 13:30:59 +0100 Subject: [PATCH 2/2] tidy up the ref to ref with a forwardRef and initialComponent signature Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/autocomplete/CommunityProvider.tsx | 7 +- src/autocomplete/Components.tsx | 91 ++++++++------------- src/autocomplete/EmojiProvider.tsx | 4 +- src/autocomplete/NotifProvider.tsx | 4 +- src/autocomplete/RoomProvider.tsx | 4 +- src/autocomplete/UserProvider.tsx | 7 +- src/components/views/rooms/Autocomplete.tsx | 7 +- 7 files changed, 51 insertions(+), 73 deletions(-) diff --git a/src/autocomplete/CommunityProvider.tsx b/src/autocomplete/CommunityProvider.tsx index 3edb1ff81d..d7eac59f91 100644 --- a/src/autocomplete/CommunityProvider.tsx +++ b/src/autocomplete/CommunityProvider.tsx @@ -90,11 +90,12 @@ export default class CommunityProvider extends AutocompleteProvider { type: "community", href: makeGroupPermalink(groupId), component: ( - - } title={name} description={groupId} /> + ), range, })) diff --git a/src/autocomplete/Components.tsx b/src/autocomplete/Components.tsx index 3b4469321d..0ee0088f02 100644 --- a/src/autocomplete/Components.tsx +++ b/src/autocomplete/Components.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, {createRef} from 'react'; +import React, {forwardRef} from 'react'; import classNames from 'classnames'; /* These were earlier stateless functional components but had to be converted @@ -30,62 +30,37 @@ interface ITextualCompletionProps { className?: string; } -export abstract class Completion extends React.PureComponent { - nodeRef = createRef(); +export const TextualCompletion = forwardRef((props, ref) => { + const {title, subtitle, description, className, ...restProps} = props; + return ( +
+ { title } + { subtitle } + { description } +
+ ); +}); + +interface IPillCompletionProps extends ITextualCompletionProps { + children?: React.ReactNode, } -export class TextualCompletion extends Completion { - render() { - const { - title, - subtitle, - description, - className, - ...restProps - } = this.props; - return ( -
- { title } - { subtitle } - { description } -
- ); - } -} - -interface IPillCompletionProps { - title?: string; - subtitle?: string; - description?: string; - initialComponent?: React.ReactNode, - className?: string; -} - -export class PillCompletion extends Completion { - render() { - const { - title, - subtitle, - description, - initialComponent, - className, - ...restProps - } = this.props; - return ( -
- { initialComponent } - { title } - { subtitle } - { description } -
- ); - } -} +export const PillCompletion = forwardRef((props, ref) => { + const {title, subtitle, description, className, children, ...restProps} = props; + return ( +
+ { children } + { title } + { subtitle } + { description } +
+ ); +}); diff --git a/src/autocomplete/EmojiProvider.tsx b/src/autocomplete/EmojiProvider.tsx index 3a3cec779e..e1e02fcf87 100644 --- a/src/autocomplete/EmojiProvider.tsx +++ b/src/autocomplete/EmojiProvider.tsx @@ -121,9 +121,9 @@ export default class EmojiProvider extends AutocompleteProvider { return { completion: unicode, component: ( - { unicode } - } /> + ), range, }; diff --git a/src/autocomplete/NotifProvider.tsx b/src/autocomplete/NotifProvider.tsx index b217612b0e..ef1823c0ca 100644 --- a/src/autocomplete/NotifProvider.tsx +++ b/src/autocomplete/NotifProvider.tsx @@ -48,7 +48,9 @@ export default class NotifProvider extends AutocompleteProvider { type: "at-room", suffix: ' ', component: ( - } title="@room" description={_t("Notify the whole room")} /> + + + ), range, }]; diff --git a/src/autocomplete/RoomProvider.tsx b/src/autocomplete/RoomProvider.tsx index 01e770407c..0d8aac4218 100644 --- a/src/autocomplete/RoomProvider.tsx +++ b/src/autocomplete/RoomProvider.tsx @@ -103,7 +103,9 @@ export default class RoomProvider extends AutocompleteProvider { suffix: ' ', href: makeRoomPermalink(room.displayedAlias), component: ( - } title={room.room.name} description={room.displayedAlias} /> + + + ), range, }; diff --git a/src/autocomplete/UserProvider.tsx b/src/autocomplete/UserProvider.tsx index 1680eb5d54..eeb6c7a522 100644 --- a/src/autocomplete/UserProvider.tsx +++ b/src/autocomplete/UserProvider.tsx @@ -125,10 +125,9 @@ export default class UserProvider extends AutocompleteProvider { suffix: (selection.beginning && range.start === 0) ? ': ' : ' ', href: makeUserPermalink(user.userId), component: ( - } - title={displayName} - description={user.userId} /> + + + ), range, }; diff --git a/src/components/views/rooms/Autocomplete.tsx b/src/components/views/rooms/Autocomplete.tsx index 40f585a5b8..63f31c07b9 100644 --- a/src/components/views/rooms/Autocomplete.tsx +++ b/src/components/views/rooms/Autocomplete.tsx @@ -23,7 +23,6 @@ import {Room} from 'matrix-js-sdk/src/models/room'; import SettingsStore from "../../../settings/SettingsStore"; import Autocompleter from '../../../autocomplete/Autocompleter'; -import { Completion } from '../../../autocomplete/Components'; const COMPOSER_SELECTED = 0; @@ -254,10 +253,10 @@ export default class Autocomplete extends React.PureComponent { componentDidUpdate(prevProps: IProps) { this.applyNewProps(prevProps.query, prevProps.room); // this is the selected completion, so scroll it into view if needed - const selectedCompletion = this.refs[`completion${this.state.selectionOffset}`] as Completion; + const selectedCompletion = this.refs[`completion${this.state.selectionOffset}`] as HTMLElement; - if (selectedCompletion && selectedCompletion.nodeRef.current) { - selectedCompletion.nodeRef.current.scrollIntoView({ + if (selectedCompletion) { + selectedCompletion.scrollIntoView({ behavior: "auto", block: "nearest", });