Keyboard focus related bugs (#299)
authorginnyTheCat <ginnythecat@lelux.net>
Tue, 8 Feb 2022 11:43:59 +0000 (12:43 +0100)
committerGitHub <noreply@github.com>
Tue, 8 Feb 2022 11:43:59 +0000 (17:13 +0530)
* Focus when opening the emoji board and editing a message

* Clean emoji board after closing

* Focus room search and member search

* Resolve conversations

src/app/atoms/input/Input.jsx
src/app/molecules/message/Message.jsx
src/app/molecules/room-members/RoomMembers.jsx
src/app/molecules/room-search/RoomSearch.jsx
src/app/organisms/emoji-board/EmojiBoard.jsx
src/app/organisms/emoji-board/EmojiBoardOpener.jsx

index b65d5212f2163e6abbce8cb6918f9d20299f86ac..d9f79eb003275151217a8216aa9cad599bd6923b 100644 (file)
@@ -8,7 +8,7 @@ function Input({
   id, label, name, value, placeholder,
   required, type, onChange, forwardRef,
   resizable, minHeight, onResize, state,
-  onKeyDown, disabled,
+  onKeyDown, disabled, autoFocus,
 }) {
   return (
     <div className="input-container">
@@ -30,6 +30,7 @@ function Input({
             onResize={onResize}
             onKeyDown={onKeyDown}
             disabled={disabled}
+            autoFocus={autoFocus}
           />
         ) : (
           <input
@@ -45,6 +46,8 @@ function Input({
             onChange={onChange}
             onKeyDown={onKeyDown}
             disabled={disabled}
+            // eslint-disable-next-line jsx-a11y/no-autofocus
+            autoFocus={autoFocus}
           />
         )}
     </div>
@@ -67,6 +70,7 @@ Input.defaultProps = {
   state: 'normal',
   onKeyDown: null,
   disabled: false,
+  autoFocus: false,
 };
 
 Input.propTypes = {
@@ -85,6 +89,7 @@ Input.propTypes = {
   state: PropTypes.oneOf(['normal', 'success', 'error']),
   onKeyDown: PropTypes.func,
   disabled: PropTypes.bool,
+  autoFocus: PropTypes.bool,
 };
 
 export default Input;
index 488ad4ba846cad697734b2d8dcf0f7c5af7dd8af..16850f291be8b8535694e1f534e6a6847181b076 100644 (file)
@@ -1,5 +1,7 @@
 /* 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 './Message.scss';
 
@@ -245,6 +247,14 @@ MessageBody.propTypes = {
 function MessageEdit({ body, onSave, onCancel }) {
   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) => {
     if (e.keyCode === 13 && e.shiftKey === false) {
       e.preventDefault();
@@ -257,7 +267,6 @@ function MessageEdit({ body, onSave, onCancel }) {
       <Input
         forwardRef={editInputRef}
         onKeyDown={handleKeyDown}
-        value={body}
         placeholder="Edit message"
         required
         resizable
index 1f409f4f6f8a74f63ea7a7c1b46260097a94e68a..1f21ce1868f523d41cecac22042ae412879bfe95 100644 (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 './RoomMembers.scss';
 
@@ -14,6 +16,7 @@ import Input from '../../atoms/input/Input';
 import { MenuHeader } from '../../atoms/context-menu/ContextMenu';
 import SegmentedControls from '../../atoms/segmented-controls/SegmentedControls';
 import PeopleSelector from '../people-selector/PeopleSelector';
+import settings from '../../../client/state/settings';
 
 const PER_PAGE_MEMBER = 50;
 
@@ -138,7 +141,11 @@ function RoomMembers({ roomId }) {
   return (
     <div className="room-members">
       <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">
         <MenuHeader>{`${searchMembers ? `Found — ${mList.length}` : members.length} members`}</MenuHeader>
         <SegmentedControls
index 3a86cbee2294a812f8117382aa197c8afd3f2efd..a88e60256761d783d096fa1ca0c8c630d7c34e98 100644 (file)
@@ -1,4 +1,4 @@
-import React, { useState, useEffect } from 'react';
+import React, { useState, useEffect, useRef } from 'react';
 import PropTypes from 'prop-types';
 import './RoomSearch.scss';
 
@@ -145,6 +145,7 @@ function RoomSearch({ roomId }) {
             placeholder="Search for keywords"
             name="room-search-input"
             disabled={isRoomEncrypted}
+            autoFocus
           />
           <Button iconSrc={SearchIC} variant="primary" type="submit">Search</Button>
         </div>
index c41d8f9319e0ee66461fe585450034c1d43e128f..1e61abd811b63b4004358befede080a907200d97 100644 (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} />;
 }
 
-function EmojiBoard({ onSelect }) {
-  const searchRef = useRef(null);
+function EmojiBoard({ onSelect, searchRef }) {
   const scrollEmojisRef = useRef(null);
   const emojiInfo = useRef(null);
 
@@ -182,8 +181,8 @@ function EmojiBoard({ onSelect }) {
     setEmojiInfo({ shortcode: shortcodes[0], src, unicode });
   }
 
-  function handleSearchChange(e) {
-    const term = e.target.value;
+  function handleSearchChange() {
+    const term = searchRef.current.value;
     asyncSearch.search(term);
     scrollEmojisRef.current.scrollTop = 0;
   }
@@ -213,9 +212,16 @@ function EmojiBoard({ onSelect }) {
       setAvailableEmojis(packs);
     };
 
+    const onOpen = () => {
+      searchRef.current.value = '';
+      handleSearchChange();
+    };
+
     navigation.on(cons.events.navigation.ROOM_SELECTED, updateAvailableEmoji);
+    navigation.on(cons.events.navigation.EMOJIBOARD_OPENED, onOpen);
     return () => {
       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 = {
   onSelect: PropTypes.func.isRequired,
+  searchRef: PropTypes.shape({}).isRequired,
 };
 
 export default EmojiBoard;
index b16a9e343f310fdd9e9e8f3383436a341bc3f956..32b7a8370e29a1b40251f92f921e18c295afbc29 100644 (file)
@@ -2,6 +2,7 @@ import React, { useEffect, useRef } from 'react';
 
 import cons from '../../../client/state/cons';
 import navigation from '../../../client/state/navigation';
+import settings from '../../../client/state/settings';
 
 import ContextMenu from '../../atoms/context-menu/ContextMenu';
 import EmojiBoard from './EmojiBoard';
@@ -10,6 +11,7 @@ let requestCallback = null;
 let isEmojiBoardVisible = false;
 function EmojiBoardOpener() {
   const openerRef = useRef(null);
+  const searchRef = useRef(null);
 
   function openEmojiBoard(cords, requestEmojiCallback) {
     if (requestCallback !== null || isEmojiBoardVisible) {
@@ -25,7 +27,9 @@ function EmojiBoardOpener() {
 
   function afterEmojiBoardToggle(isVisible) {
     isEmojiBoardVisible = isVisible;
-    if (!isVisible) {
+    if (isVisible) {
+      if (!settings.isTouchScreenDevice) searchRef.current.focus();
+    } else {
       setTimeout(() => {
         if (!isEmojiBoardVisible) requestCallback = null;
       }, 500);
@@ -46,7 +50,7 @@ function EmojiBoardOpener() {
   return (
     <ContextMenu
       content={(
-        <EmojiBoard onSelect={addEmoji} />
+        <EmojiBoard onSelect={addEmoji} searchRef={searchRef} />
       )}
       afterToggle={afterEmojiBoardToggle}
       render={(toggleMenu) => (