From 2b40ccfe132731920f8d80c3f129cbd7b6ae53e6 Mon Sep 17 00:00:00 2001 From: Antonella Sgarlatta Date: Thu, 3 Jun 2021 17:45:43 -0300 Subject: [PATCH] refactor: store refs in components --- .../components/AutocompleteTagInput.tsx | 49 +++--- .../components/AutocompleteTagResult.tsx | 39 +++-- app/assets/javascripts/components/NoteTag.tsx | 31 +++- .../ui_models/app_state/note_tags_state.ts | 141 ++++++++---------- 4 files changed, 139 insertions(+), 121 deletions(-) diff --git a/app/assets/javascripts/components/AutocompleteTagInput.tsx b/app/assets/javascripts/components/AutocompleteTagInput.tsx index a4a57e49b..5756301da 100644 --- a/app/assets/javascripts/components/AutocompleteTagInput.tsx +++ b/app/assets/javascripts/components/AutocompleteTagInput.tsx @@ -12,12 +12,10 @@ type Props = { export const AutocompleteTagInput = observer(({ appState }: Props) => { const { + autocompleteInputFocused, autocompleteSearchQuery, autocompleteTagHintVisible, autocompleteTagResults, - autocompleteTagResultElements, - autocompleteInputElement, - tagElements, tags, } = appState.noteTags; @@ -26,6 +24,7 @@ export const AutocompleteTagInput = observer(({ appState }: Props) => { useState('auto'); const dropdownRef = useRef(); + const inputRef = useRef(); const [closeOnBlur] = useCloseOnBlur(dropdownRef, (visible: boolean) => { setDropdownVisible(visible); @@ -33,11 +32,9 @@ export const AutocompleteTagInput = observer(({ appState }: Props) => { }); const showDropdown = () => { - if (autocompleteInputElement) { - const { clientHeight } = document.documentElement; - const inputRect = autocompleteInputElement.getBoundingClientRect(); - setDropdownMaxHeight(clientHeight - inputRect.bottom - 32 * 2); - } + const { clientHeight } = document.documentElement; + const inputRect = inputRef.current.getBoundingClientRect(); + setDropdownMaxHeight(clientHeight - inputRect.bottom - 32 * 2); setDropdownVisible(true); }; @@ -55,14 +52,15 @@ export const AutocompleteTagInput = observer(({ appState }: Props) => { const onKeyDown = (event: KeyboardEvent) => { switch (event.key) { case 'Backspace': - if (autocompleteSearchQuery === '' && tagElements.length > 0) { - tagElements[tagElements.length - 1]?.focus(); + case 'ArrowLeft': + if (autocompleteSearchQuery === '' && tags.length > 0) { + appState.noteTags.setFocusedTagUuid(tags[tags.length - 1].uuid); } break; case 'ArrowDown': event.preventDefault(); - if (autocompleteTagResultElements.length > 0) { - autocompleteTagResultElements[0]?.focus(); + if (autocompleteTagResults.length > 0) { + appState.noteTags.setFocusedTagResultUuid(autocompleteTagResults[0].uuid); } break; default: @@ -70,10 +68,27 @@ export const AutocompleteTagInput = observer(({ appState }: Props) => { } }; + const onFocus = () => { + showDropdown(); + appState.noteTags.setAutocompleteInputFocused(true); + }; + + const onBlur = (event: FocusEvent) => { + closeOnBlur(event); + appState.noteTags.setAutocompleteInputFocused(false); + }; + useEffect(() => { appState.noteTags.searchActiveNoteAutocompleteTags(); }, [appState.noteTags]); + useEffect(() => { + if (autocompleteInputFocused) { + inputRef.current.focus(); + appState.noteTags.setAutocompleteInputFocused(false); + } + }, [appState.noteTags, autocompleteInputFocused]); + return (
{ > { - if (element) { - appState.noteTags.setAutocompleteInputElement(element); - } - }} + ref={inputRef} className="w-80 bg-default text-xs color-text no-border h-7 focus:outline-none focus:shadow-none focus:border-bottom" value={autocompleteSearchQuery} onChange={onSearchQueryChange} type="text" placeholder="Add tag" - onBlur={closeOnBlur} - onFocus={showDropdown} + onBlur={onBlur} + onFocus={onFocus} onKeyDown={onKeyDown} /> {dropdownVisible && ( diff --git a/app/assets/javascripts/components/AutocompleteTagResult.tsx b/app/assets/javascripts/components/AutocompleteTagResult.tsx index 4dd36aab0..6a82555ac 100644 --- a/app/assets/javascripts/components/AutocompleteTagResult.tsx +++ b/app/assets/javascripts/components/AutocompleteTagResult.tsx @@ -1,6 +1,7 @@ import { AppState } from '@/ui_models/app_state'; import { SNTag } from '@standardnotes/snjs'; import { observer } from 'mobx-react-lite'; +import { useEffect, useRef } from 'preact/hooks'; import { Icon } from './Icon'; type Props = { @@ -11,7 +12,9 @@ type Props = { export const AutocompleteTagResult = observer( ({ appState, tagResult, closeOnBlur }: Props) => { - const { autocompleteInputElement, autocompleteSearchQuery, autocompleteTagResults } = appState.noteTags; + const { autocompleteSearchQuery, autocompleteTagResults, focusedTagResultUuid } = appState.noteTags; + + const tagResultRef = useRef(); const onTagOptionClick = async (tag: SNTag) => { await appState.noteTags.addTagToActiveNote(tag); @@ -24,34 +27,44 @@ export const AutocompleteTagResult = observer( case 'ArrowUp': event.preventDefault(); if (tagResultIndex === 0) { - autocompleteInputElement?.focus(); + appState.noteTags.setAutocompleteInputFocused(true); } else { - appState.noteTags.getPreviousAutocompleteTagResultElement(tagResult)?.focus(); + appState.noteTags.focusPreviousTagResult(tagResult); } break; case 'ArrowDown': event.preventDefault(); - appState.noteTags.getNextAutocompleteTagResultElement(tagResult)?.focus(); + appState.noteTags.focusNextTagResult(tagResult); break; default: return; } }; + const onFocus = () => { + appState.noteTags.setFocusedTagResultUuid(tagResult.uuid); + }; + + const onBlur = (event: FocusEvent) => { + closeOnBlur(event); + appState.noteTags.setFocusedTagResultUuid(undefined); + }; + + useEffect(() => { + if (focusedTagResultUuid === tagResult.uuid) { + tagResultRef.current.focus(); + appState.noteTags.setFocusedTagResultUuid(undefined); + } + }, [appState.noteTags, focusedTagResultUuid, tagResult]); + return (