From bb2b1f1a96a3f164e3a2d41ea25e695299b2e6c6 Mon Sep 17 00:00:00 2001 From: Sergey Kolesnik Date: Fri, 3 Nov 2023 22:05:12 +0300 Subject: [PATCH] feat(ui): highlight with keyboard and mouse logic --- src/ui/insert.css | 10 ++-- src/ui/insert.tsx | 145 ++++++++++++++++++++++++++++++++++------------ 2 files changed, 113 insertions(+), 42 deletions(-) diff --git a/src/ui/insert.css b/src/ui/insert.css index ebe6907..17395bb 100644 --- a/src/ui/insert.css +++ b/src/ui/insert.css @@ -82,7 +82,7 @@ div { border-radius: 8px; transition-timing-function: ease-out; - transition-duration: .3s; + transition-duration: .15s; transition-property: all; opacity: 0; /* will be set at runtime */ @@ -149,7 +149,7 @@ div { position: relative; } -.item > a { +.item { word-break: break-all; background: 0 0; @@ -183,16 +183,16 @@ div { text-decoration: none; } -.item > a > span { +.item > span { flex: 1 1 0%; } -.item-selected, .item:hover { +.selected { word-break: break-all; background-color: var(--ls-a-chosen-bg); } -.item-selected span, .item:hover span { +.selected span{ color: var(--ls-secondary-text-color); } diff --git a/src/ui/insert.tsx b/src/ui/insert.tsx index eed53d5..7ed953c 100644 --- a/src/ui/insert.tsx +++ b/src/ui/insert.tsx @@ -22,7 +22,8 @@ function InsertUI({ blockUUID }) { const [visible, setVisible] = useState(true) const [searchQueryState, setSearchQueryState] = useState('') const [resultsState, setResultsState] = useState([] as string[]) - const [highlightedResultState, setHighlightedResultState] = useState(null) + const [highlightedIndexState, setHighlightedIndexState] = useState(null as number | null) + // const firstUpdate = useRef(true) function showUI() { // handle show/hide animation @@ -51,54 +52,126 @@ function InsertUI({ blockUUID }) { } useEffect(() => { - console.log('on:SHOW', visible) - if (visible) setTimeout(showUI, 100) }, [visible]) useEffect(() => { - console.log('on:INIT') - logseq.on('ui:visible:changed', ({ visible }) => { if (visible) setVisible(true) }) }, []) - useEffect(() => { - console.log('on:KEYUP') + const saveInputValue = (event) => { + const input = event.target! as HTMLInputElement + setSearchQueryState(input.value) + } - const handleKeyup = (e: KeyboardEvent) => { - if (e.key === 'Escape') { - if (searchQueryState !== '') { - setSearchQueryState('') - return - } + const returnFocus = (event: FocusEvent) => { + const input = event.target! as HTMLInputElement + input.focus() + } - hideUI() + const actWithHighlightedItem = (event: KeyboardEvent) => { + if (event.key === 'ArrowDown') { + event.preventDefault() + if (highlightedIndexState === null) { + setHighlightedIndexState(0) + return + } + const maxIndex = resultsState.length - 1 + if (highlightedIndexState === maxIndex) + return + setHighlightedIndexState(highlightedIndexState + 1) + } + else if (event.key === 'ArrowUp') { + event.preventDefault() + if (highlightedIndexState === null) { + if (resultsState.length > 0) + setHighlightedIndexState(resultsState.length - 1) return } + const minIndex = 0 + if (highlightedIndexState === minIndex) + return + setHighlightedIndexState(highlightedIndexState - 1) + } + else if (event.key === 'Enter') { + event.preventDefault() + insertHighlightedItem() } + } - document.addEventListener('keyup', handleKeyup, false) + const handleEscapeKey = (event: KeyboardEvent) => { + const input = event.target! as HTMLInputElement + if (event.key === 'Escape') { + if (input.value !== '') { + setSearchQueryState('') + return + } - return () => { - document.removeEventListener('keyup', handleKeyup) + hideUI() + return } - }, [searchQueryState]) + } + // filter results useEffect(() => { console.log('on:FILTER', searchQueryState) - let results = ['test template', 'some template', 'cool', 'words', 'And long template names', 'with upper LETTERS'] + let results = [ + 'test template', 'some template', 'cool', 'words', 'And long template names', 'with upper LETTERS', + 'test template', 'some template', 'cool', 'words', 'And long template names', 'with upper LETTERS', + 'test template', 'some template', 'cool', 'words', 'And long template names', 'with upper LETTERS', + ] if (searchQueryState) results = results.filter( (result) => result.toLowerCase().includes(searchQueryState.toLowerCase()) ) setResultsState(results) + updateHighlightFor(results) }, [searchQueryState]) + function updateHighlightFor(results) { + if (results.length == 0) { + setHighlightedIndexState(null) + return + } + + if (highlightedIndexState === null) + setHighlightedIndexState(0) + else + if (highlightedIndexState >= results.length) + setHighlightedIndexState(results.length - 1) + } + + useEffect(() => { + const itemsElement = document.getElementById('items')! + resultsState.forEach((item, index) => { + const div = itemsElement.childNodes[index] as HTMLDivElement + if (highlightedIndexState === null || index !== highlightedIndexState) + div.classList.remove('selected') + else + div.classList.add('selected') + }) + }, [highlightedIndexState]) + + const highlightItem = (event: MouseEvent) => { + const currentItem = (event.target! as HTMLDivElement).closest('.item') + const itemsElement = document.getElementById('items')! + for (const [index, node] of Object.entries(itemsElement.childNodes)) { + if (node === currentItem) { + setHighlightedIndexState(Number(index)) + break + } + } + } + + const insertHighlightedItem = () => { + if (highlightedIndexState !== null) + console.log('INSERT', resultsState[highlightedIndexState]) + } return (