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:
parent
d0e9728c26
commit
cdd909f2dd
6 changed files with 45 additions and 12 deletions
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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) => (
|
||||||
|
|
Loading…
Reference in a new issue