Autocomplete: use scrollIntoView for auto-scroll instead of broken manual scrollTop calculation

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
Michael Telatynski 2020-05-29 21:42:33 +01:00
parent 3f76b73b50
commit 8087b521e6
2 changed files with 29 additions and 18 deletions

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React from 'react'; import React, {createRef} from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
/* These were earlier stateless functional components but had to be converted /* These were earlier stateless functional components but had to be converted
@ -30,7 +30,11 @@ interface ITextualCompletionProps {
className?: string; className?: string;
} }
export class TextualCompletion extends React.PureComponent<ITextualCompletionProps> { export abstract class Completion<T> extends React.PureComponent<T> {
nodeRef = createRef<HTMLDivElement>();
}
export class TextualCompletion extends Completion<ITextualCompletionProps> {
render() { render() {
const { const {
title, title,
@ -40,7 +44,11 @@ export class TextualCompletion extends React.PureComponent<ITextualCompletionPro
...restProps ...restProps
} = this.props; } = this.props;
return ( return (
<div className={classNames('mx_Autocomplete_Completion_block', className)} role="option" {...restProps}> <div {...restProps}
className={classNames('mx_Autocomplete_Completion_block', className)}
role="option"
ref={this.nodeRef}
>
<span className="mx_Autocomplete_Completion_title">{ title }</span> <span className="mx_Autocomplete_Completion_title">{ title }</span>
<span className="mx_Autocomplete_Completion_subtitle">{ subtitle }</span> <span className="mx_Autocomplete_Completion_subtitle">{ subtitle }</span>
<span className="mx_Autocomplete_Completion_description">{ description }</span> <span className="mx_Autocomplete_Completion_description">{ description }</span>
@ -57,7 +65,7 @@ interface IPillCompletionProps {
className?: string; className?: string;
} }
export class PillCompletion extends React.PureComponent<IPillCompletionProps> { export class PillCompletion extends Completion<IPillCompletionProps> {
render() { render() {
const { const {
title, title,
@ -68,7 +76,11 @@ export class PillCompletion extends React.PureComponent<IPillCompletionProps> {
...restProps ...restProps
} = this.props; } = this.props;
return ( return (
<div className={classNames('mx_Autocomplete_Completion_pill', className)} role="option" {...restProps}> <div {...restProps}
className={classNames('mx_Autocomplete_Completion_pill', className)}
role="option"
ref={this.nodeRef}
>
{ initialComponent } { initialComponent }
<span className="mx_Autocomplete_Completion_title">{ title }</span> <span className="mx_Autocomplete_Completion_title">{ title }</span>
<span className="mx_Autocomplete_Completion_subtitle">{ subtitle }</span> <span className="mx_Autocomplete_Completion_subtitle">{ subtitle }</span>

View file

@ -15,8 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React from 'react'; import React, {createRef} from 'react';
import ReactDOM from 'react-dom';
import classNames from 'classnames'; import classNames from 'classnames';
import flatMap from 'lodash/flatMap'; import flatMap from 'lodash/flatMap';
import {ICompletion, ISelectionRange, IProviderCompletions} from '../../../autocomplete/Autocompleter'; 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 SettingsStore from "../../../settings/SettingsStore";
import Autocompleter from '../../../autocomplete/Autocompleter'; import Autocompleter from '../../../autocomplete/Autocompleter';
import { Completion } from '../../../autocomplete/Components';
const COMPOSER_SELECTED = 0; const COMPOSER_SELECTED = 0;
@ -54,7 +54,7 @@ export default class Autocomplete extends React.PureComponent<IProps, IState> {
autocompleter: Autocompleter; autocompleter: Autocompleter;
queryRequested: string; queryRequested: string;
debounceCompletionsRequest: NodeJS.Timeout; debounceCompletionsRequest: NodeJS.Timeout;
containerRef: React.RefObject<HTMLDivElement>; private containerRef = createRef<HTMLDivElement>();
constructor(props) { constructor(props) {
super(props); super(props);
@ -78,8 +78,6 @@ export default class Autocomplete extends React.PureComponent<IProps, IState> {
forceComplete: false, forceComplete: false,
}; };
this.containerRef = React.createRef();
} }
componentDidMount() { componentDidMount() {
@ -256,14 +254,15 @@ export default class Autocomplete extends React.PureComponent<IProps, IState> {
componentDidUpdate(prevProps: IProps) { componentDidUpdate(prevProps: IProps) {
this.applyNewProps(prevProps.query, prevProps.room); this.applyNewProps(prevProps.query, prevProps.room);
// this is the selected completion, so scroll it into view if needed // this is the selected completion, so scroll it into view if needed
const selectedCompletion = this.refs[`completion${this.state.selectionOffset}`]; const selectedCompletion = this.refs[`completion${this.state.selectionOffset}`] as Completion<any>;
if (selectedCompletion && this.containerRef.current) {
const domNode = ReactDOM.findDOMNode(selectedCompletion); if (selectedCompletion && selectedCompletion.nodeRef.current) {
const offsetTop = domNode && (domNode as HTMLElement).offsetTop; selectedCompletion.nodeRef.current.scrollIntoView({
if (offsetTop > this.containerRef.current.scrollTop + this.containerRef.current.offsetHeight || behavior: "auto",
offsetTop < this.containerRef.current.scrollTop) { block: "nearest",
this.containerRef.current.scrollTop = offsetTop - this.containerRef.current.offsetTop; });
} } else {
this.containerRef.current.scrollTo({ top: 0 });
} }
} }