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 19a7a969d6..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 from 'react'; +import React, {forwardRef} from 'react'; import classNames from 'classnames'; /* These were earlier stateless functional components but had to be converted @@ -30,50 +30,37 @@ interface ITextualCompletionProps { className?: string; } -export class TextualCompletion extends React.PureComponent { - render() { - const { - title, - subtitle, - description, - className, - ...restProps - } = this.props; - return ( -
- { title } - { subtitle } - { description } -
- ); - } +export const TextualCompletion = forwardRef((props, ref) => { + const {title, subtitle, description, className, ...restProps} = props; + return ( +
+ { title } + { subtitle } + { description } +
+ ); +}); + +interface IPillCompletionProps extends ITextualCompletionProps { + children?: React.ReactNode, } -interface IPillCompletionProps { - title?: string; - subtitle?: string; - description?: string; - initialComponent?: React.ReactNode, - className?: string; -} - -export class PillCompletion extends React.PureComponent { - 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 975c8e84a5..63f31c07b9 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'; @@ -54,7 +53,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 +77,6 @@ export default class Autocomplete extends React.PureComponent { forceComplete: false, }; - - this.containerRef = React.createRef(); } componentDidMount() { @@ -256,14 +253,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 HTMLElement; + + if (selectedCompletion) { + selectedCompletion.scrollIntoView({ + behavior: "auto", + block: "nearest", + }); + } else { + this.containerRef.current.scrollTo({ top: 0 }); } }