Keyboard focus related bugs (#299)

* Focus when opening the emoji board and editing a message

* Clean emoji board after closing

* Focus room search and member search

* Resolve conversations
This commit is contained in:
ginnyTheCat 2022-02-08 12:43:59 +01:00 committed by GitHub
parent d0e9728c26
commit cdd909f2dd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 45 additions and 12 deletions

View file

@ -8,7 +8,7 @@ function Input({
id, label, name, value, placeholder, id, label, name, value, placeholder,
required, type, onChange, forwardRef, required, type, onChange, forwardRef,
resizable, minHeight, onResize, state, resizable, minHeight, onResize, state,
onKeyDown, disabled, onKeyDown, disabled, autoFocus,
}) { }) {
return ( return (
<div className="input-container"> <div className="input-container">
@ -30,6 +30,7 @@ function Input({
onResize={onResize} onResize={onResize}
onKeyDown={onKeyDown} onKeyDown={onKeyDown}
disabled={disabled} disabled={disabled}
autoFocus={autoFocus}
/> />
) : ( ) : (
<input <input
@ -45,6 +46,8 @@ function Input({
onChange={onChange} onChange={onChange}
onKeyDown={onKeyDown} onKeyDown={onKeyDown}
disabled={disabled} disabled={disabled}
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus={autoFocus}
/> />
)} )}
</div> </div>
@ -67,6 +70,7 @@ Input.defaultProps = {
state: 'normal', state: 'normal',
onKeyDown: null, onKeyDown: null,
disabled: false, disabled: false,
autoFocus: false,
}; };
Input.propTypes = { Input.propTypes = {
@ -85,6 +89,7 @@ Input.propTypes = {
state: PropTypes.oneOf(['normal', 'success', 'error']), state: PropTypes.oneOf(['normal', 'success', 'error']),
onKeyDown: PropTypes.func, onKeyDown: PropTypes.func,
disabled: PropTypes.bool, disabled: PropTypes.bool,
autoFocus: PropTypes.bool,
}; };
export default Input; export default Input;

View file

@ -1,5 +1,7 @@
/* eslint-disable react/prop-types */ /* eslint-disable react/prop-types */
import React, { useState, useEffect, useCallback, useRef } from 'react'; import React, {
useState, useEffect, useCallback, useRef,
} from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import './Message.scss'; import './Message.scss';
@ -245,6 +247,14 @@ MessageBody.propTypes = {
function MessageEdit({ body, onSave, onCancel }) { function MessageEdit({ body, onSave, onCancel }) {
const editInputRef = useRef(null); const editInputRef = useRef(null);
useEffect(() => {
editInputRef.current.focus();
// Setting the value here instead of using the value prop below
// makes the cursor end up at the end of the line instead of the begining
editInputRef.current.value = body;
}, []);
const handleKeyDown = (e) => { const handleKeyDown = (e) => {
if (e.keyCode === 13 && e.shiftKey === false) { if (e.keyCode === 13 && e.shiftKey === false) {
e.preventDefault(); e.preventDefault();
@ -257,7 +267,6 @@ function MessageEdit({ body, onSave, onCancel }) {
<Input <Input
forwardRef={editInputRef} forwardRef={editInputRef}
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
value={body}
placeholder="Edit message" placeholder="Edit message"
required required
resizable resizable

View file

@ -1,4 +1,6 @@
import React, { useState, useEffect, useCallback } from 'react'; import React, {
useState, useEffect, useCallback, useRef,
} from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import './RoomMembers.scss'; import './RoomMembers.scss';
@ -14,6 +16,7 @@ import Input from '../../atoms/input/Input';
import { MenuHeader } from '../../atoms/context-menu/ContextMenu'; import { MenuHeader } from '../../atoms/context-menu/ContextMenu';
import SegmentedControls from '../../atoms/segmented-controls/SegmentedControls'; import SegmentedControls from '../../atoms/segmented-controls/SegmentedControls';
import PeopleSelector from '../people-selector/PeopleSelector'; import PeopleSelector from '../people-selector/PeopleSelector';
import settings from '../../../client/state/settings';
const PER_PAGE_MEMBER = 50; const PER_PAGE_MEMBER = 50;
@ -138,7 +141,11 @@ function RoomMembers({ roomId }) {
return ( return (
<div className="room-members"> <div className="room-members">
<MenuHeader>Search member</MenuHeader> <MenuHeader>Search member</MenuHeader>
<Input onChange={handleSearch} placeholder="Search for name" /> <Input
onChange={handleSearch}
placeholder="Search for name"
autoFocus={!settings.isTouchScreenDevice}
/>
<div className="room-members__header"> <div className="room-members__header">
<MenuHeader>{`${searchMembers ? `Found — ${mList.length}` : members.length} members`}</MenuHeader> <MenuHeader>{`${searchMembers ? `Found — ${mList.length}` : members.length} members`}</MenuHeader>
<SegmentedControls <SegmentedControls

View file

@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect, useRef } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import './RoomSearch.scss'; import './RoomSearch.scss';
@ -145,6 +145,7 @@ function RoomSearch({ roomId }) {
placeholder="Search for keywords" placeholder="Search for keywords"
name="room-search-input" name="room-search-input"
disabled={isRoomEncrypted} disabled={isRoomEncrypted}
autoFocus
/> />
<Button iconSrc={SearchIC} variant="primary" type="submit">Search</Button> <Button iconSrc={SearchIC} variant="primary" type="submit">Search</Button>
</div> </div>

View file

@ -128,8 +128,7 @@ function SearchedEmoji() {
return <EmojiGroup key="-1" name={searchedEmojis.emojis.length === 0 ? 'No search result found' : 'Search results'} groupEmojis={searchedEmojis.emojis} />; return <EmojiGroup key="-1" name={searchedEmojis.emojis.length === 0 ? 'No search result found' : 'Search results'} groupEmojis={searchedEmojis.emojis} />;
} }
function EmojiBoard({ onSelect }) { function EmojiBoard({ onSelect, searchRef }) {
const searchRef = useRef(null);
const scrollEmojisRef = useRef(null); const scrollEmojisRef = useRef(null);
const emojiInfo = useRef(null); const emojiInfo = useRef(null);
@ -182,8 +181,8 @@ function EmojiBoard({ onSelect }) {
setEmojiInfo({ shortcode: shortcodes[0], src, unicode }); setEmojiInfo({ shortcode: shortcodes[0], src, unicode });
} }
function handleSearchChange(e) { function handleSearchChange() {
const term = e.target.value; const term = searchRef.current.value;
asyncSearch.search(term); asyncSearch.search(term);
scrollEmojisRef.current.scrollTop = 0; scrollEmojisRef.current.scrollTop = 0;
} }
@ -213,9 +212,16 @@ function EmojiBoard({ onSelect }) {
setAvailableEmojis(packs); setAvailableEmojis(packs);
}; };
const onOpen = () => {
searchRef.current.value = '';
handleSearchChange();
};
navigation.on(cons.events.navigation.ROOM_SELECTED, updateAvailableEmoji); navigation.on(cons.events.navigation.ROOM_SELECTED, updateAvailableEmoji);
navigation.on(cons.events.navigation.EMOJIBOARD_OPENED, onOpen);
return () => { return () => {
navigation.removeListener(cons.events.navigation.ROOM_SELECTED, updateAvailableEmoji); navigation.removeListener(cons.events.navigation.ROOM_SELECTED, updateAvailableEmoji);
navigation.removeListener(cons.events.navigation.EMOJIBOARD_OPENED, onOpen);
}; };
}, []); }, []);
@ -312,6 +318,7 @@ function EmojiBoard({ onSelect }) {
EmojiBoard.propTypes = { EmojiBoard.propTypes = {
onSelect: PropTypes.func.isRequired, onSelect: PropTypes.func.isRequired,
searchRef: PropTypes.shape({}).isRequired,
}; };
export default EmojiBoard; export default EmojiBoard;

View file

@ -2,6 +2,7 @@ import React, { useEffect, useRef } from 'react';
import cons from '../../../client/state/cons'; import cons from '../../../client/state/cons';
import navigation from '../../../client/state/navigation'; import navigation from '../../../client/state/navigation';
import settings from '../../../client/state/settings';
import ContextMenu from '../../atoms/context-menu/ContextMenu'; import ContextMenu from '../../atoms/context-menu/ContextMenu';
import EmojiBoard from './EmojiBoard'; import EmojiBoard from './EmojiBoard';
@ -10,6 +11,7 @@ let requestCallback = null;
let isEmojiBoardVisible = false; let isEmojiBoardVisible = false;
function EmojiBoardOpener() { function EmojiBoardOpener() {
const openerRef = useRef(null); const openerRef = useRef(null);
const searchRef = useRef(null);
function openEmojiBoard(cords, requestEmojiCallback) { function openEmojiBoard(cords, requestEmojiCallback) {
if (requestCallback !== null || isEmojiBoardVisible) { if (requestCallback !== null || isEmojiBoardVisible) {
@ -25,7 +27,9 @@ function EmojiBoardOpener() {
function afterEmojiBoardToggle(isVisible) { function afterEmojiBoardToggle(isVisible) {
isEmojiBoardVisible = isVisible; isEmojiBoardVisible = isVisible;
if (!isVisible) { if (isVisible) {
if (!settings.isTouchScreenDevice) searchRef.current.focus();
} else {
setTimeout(() => { setTimeout(() => {
if (!isEmojiBoardVisible) requestCallback = null; if (!isEmojiBoardVisible) requestCallback = null;
}, 500); }, 500);
@ -46,7 +50,7 @@ function EmojiBoardOpener() {
return ( return (
<ContextMenu <ContextMenu
content={( content={(
<EmojiBoard onSelect={addEmoji} /> <EmojiBoard onSelect={addEmoji} searchRef={searchRef} />
)} )}
afterToggle={afterEmojiBoardToggle} afterToggle={afterEmojiBoardToggle}
render={(toggleMenu) => ( render={(toggleMenu) => (