refactor: store refs in components
This commit is contained in:
@@ -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<number | 'auto'>('auto');
|
||||
|
||||
const dropdownRef = useRef<HTMLDivElement>();
|
||||
const inputRef = useRef<HTMLInputElement>();
|
||||
|
||||
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 (
|
||||
<form
|
||||
onSubmit={onFormSubmit}
|
||||
@@ -81,18 +96,14 @@ export const AutocompleteTagInput = observer(({ appState }: Props) => {
|
||||
>
|
||||
<Disclosure open={dropdownVisible} onChange={showDropdown}>
|
||||
<input
|
||||
ref={(element) => {
|
||||
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 && (
|
||||
|
||||
@@ -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<HTMLButtonElement>();
|
||||
|
||||
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 (
|
||||
<button
|
||||
ref={(element) => {
|
||||
if (element) {
|
||||
appState.noteTags.setAutocompleteTagResultElement(
|
||||
tagResult,
|
||||
element
|
||||
);
|
||||
}
|
||||
}}
|
||||
ref={tagResultRef}
|
||||
type="button"
|
||||
className="sn-dropdown-item"
|
||||
onClick={() => onTagOptionClick(tagResult)}
|
||||
onBlur={closeOnBlur}
|
||||
onFocus={onFocus}
|
||||
onBlur={onBlur}
|
||||
onKeyDown={onKeyDown}
|
||||
>
|
||||
<Icon type="hashtag" className="color-neutral mr-2 min-h-5 min-w-5" />
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Icon } from './Icon';
|
||||
import { useRef, useState } from 'preact/hooks';
|
||||
import { useEffect, useRef, useState } from 'preact/hooks';
|
||||
import { AppState } from '@/ui_models/app_state';
|
||||
import { SNTag } from '@standardnotes/snjs/dist/@types';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
@@ -10,11 +10,16 @@ type Props = {
|
||||
};
|
||||
|
||||
export const NoteTag = observer(({ appState, tag }: Props) => {
|
||||
const { focusedTagUuid, tags } = appState.noteTags;
|
||||
|
||||
const [showDeleteButton, setShowDeleteButton] = useState(false);
|
||||
const [tagClicked, setTagClicked] = useState(false);
|
||||
const deleteTagRef = useRef<HTMLButtonElement>();
|
||||
|
||||
const tagRef = useRef<HTMLButtonElement>();
|
||||
|
||||
const deleteTag = () => {
|
||||
appState.noteTags.focusPreviousTag(tag);
|
||||
appState.noteTags.removeTagFromActiveNote(tag);
|
||||
};
|
||||
|
||||
@@ -34,39 +39,49 @@ export const NoteTag = observer(({ appState, tag }: Props) => {
|
||||
};
|
||||
|
||||
const onFocus = () => {
|
||||
appState.noteTags.setFocusedTagUuid(tag.uuid);
|
||||
setShowDeleteButton(true);
|
||||
};
|
||||
|
||||
const onBlur = (event: FocusEvent) => {
|
||||
const relatedTarget = event.relatedTarget as Node;
|
||||
if (relatedTarget !== deleteTagRef.current) {
|
||||
appState.noteTags.setFocusedTagUuid(undefined);
|
||||
setShowDeleteButton(false);
|
||||
}
|
||||
};
|
||||
|
||||
const onKeyDown = (event: KeyboardEvent) => {
|
||||
const tagIndex = appState.noteTags.getTagIndex(tag, tags);
|
||||
switch (event.key) {
|
||||
case 'Backspace':
|
||||
deleteTag();
|
||||
break;
|
||||
case 'ArrowLeft':
|
||||
appState.noteTags.getPreviousTagElement(tag)?.focus();
|
||||
appState.noteTags.focusPreviousTag(tag);
|
||||
break;
|
||||
case 'ArrowRight':
|
||||
appState.noteTags.getNextTagElement(tag)?.focus();
|
||||
if (tagIndex === tags.length - 1) {
|
||||
appState.noteTags.setAutocompleteInputFocused(true);
|
||||
} else {
|
||||
appState.noteTags.focusNextTag(tag);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (focusedTagUuid === tag.uuid) {
|
||||
tagRef.current.focus();
|
||||
appState.noteTags.setFocusedTagUuid(undefined);
|
||||
}
|
||||
}, [appState.noteTags, focusedTagUuid, tag]);
|
||||
|
||||
return (
|
||||
<button
|
||||
ref={(element) => {
|
||||
if (element) {
|
||||
appState.noteTags.setTagElement(tag, element);
|
||||
}
|
||||
}}
|
||||
ref={tagRef}
|
||||
className="sn-tag pl-1 pr-2 mr-2"
|
||||
onClick={onTagClick}
|
||||
onKeyDown={onKeyDown}
|
||||
|
||||
Reference in New Issue
Block a user