fix: quickpanel auto-scroll behaviour (#5950)

* fix: quickpanel scrollto changed to smart

* fix: add scrollTrigger as the replacement for scrollBlock

* fix: add a 'none' trigger to prevent accidental scrolling
This commit is contained in:
one 2025-05-14 00:52:25 +08:00 committed by GitHub
parent 3a1ba4eb20
commit e9afab7725
2 changed files with 33 additions and 7 deletions

View File

@ -64,3 +64,5 @@ export interface QuickPanelContextType {
readonly beforeAction?: (Options: QuickPanelCallBackOptions) => void
readonly afterAction?: (Options: QuickPanelCallBackOptions) => void
}
export type QuickPanelScrollTrigger = 'initial' | 'keyboard' | 'none'

View File

@ -6,13 +6,19 @@ import { theme } from 'antd'
import Color from 'color'
import { t } from 'i18next'
import { Check } from 'lucide-react'
import React, { use, useCallback, useDeferredValue, useEffect, useMemo, useRef, useState } from 'react'
import React, { use, useCallback, useDeferredValue, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'
import { FixedSizeList } from 'react-window'
import styled from 'styled-components'
import * as tinyPinyin from 'tiny-pinyin'
import { QuickPanelContext } from './provider'
import { QuickPanelCallBackOptions, QuickPanelCloseAction, QuickPanelListItem, QuickPanelOpenOptions } from './types'
import {
QuickPanelCallBackOptions,
QuickPanelCloseAction,
QuickPanelListItem,
QuickPanelOpenOptions,
QuickPanelScrollTrigger
} from './types'
const ITEM_HEIGHT = 31
@ -45,6 +51,7 @@ export const QuickPanelView: React.FC<Props> = ({ setInputText }) => {
// 避免上下翻页时,鼠标干扰
const [isMouseOver, setIsMouseOver] = useState(false)
const scrollTriggerRef = useRef<QuickPanelScrollTrigger>('initial')
const [_index, setIndex] = useState(ctx.defaultIndex)
const index = useDeferredValue(_index)
const [historyPanel, setHistoryPanel] = useState<QuickPanelOpenOptions[]>([])
@ -140,6 +147,7 @@ export const QuickPanelView: React.FC<Props> = ({ setInputText }) => {
(action?: QuickPanelCloseAction) => {
ctx.close(action)
setHistoryPanel([])
scrollTriggerRef.current = 'initial'
if (action === 'delete-symbol') {
const textArea = document.querySelector('.inputbar textarea') as HTMLTextAreaElement
@ -249,10 +257,13 @@ export const QuickPanelView: React.FC<Props> = ({ setInputText }) => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ctx.isVisible])
useEffect(() => {
if (index >= 0) {
listRef.current?.scrollToItem(index, 'auto')
}
useLayoutEffect(() => {
if (!listRef.current || index < 0 || scrollTriggerRef.current === 'none') return
const alignment = scrollTriggerRef.current === 'keyboard' ? 'auto' : 'smart'
listRef.current?.scrollToItem(index, alignment)
scrollTriggerRef.current = 'none'
}, [index])
// 处理键盘事件
@ -277,6 +288,7 @@ export const QuickPanelView: React.FC<Props> = ({ setInputText }) => {
switch (e.key) {
case 'ArrowUp':
scrollTriggerRef.current = 'keyboard'
if (isAssistiveKeyPressed) {
setIndex((prev) => {
const newIndex = prev - ctx.pageSize
@ -289,6 +301,7 @@ export const QuickPanelView: React.FC<Props> = ({ setInputText }) => {
break
case 'ArrowDown':
scrollTriggerRef.current = 'keyboard'
if (isAssistiveKeyPressed) {
setIndex((prev) => {
const newIndex = prev + ctx.pageSize
@ -301,6 +314,7 @@ export const QuickPanelView: React.FC<Props> = ({ setInputText }) => {
break
case 'PageUp':
scrollTriggerRef.current = 'keyboard'
setIndex((prev) => {
const newIndex = prev - ctx.pageSize
return newIndex < 0 ? 0 : newIndex
@ -308,6 +322,7 @@ export const QuickPanelView: React.FC<Props> = ({ setInputText }) => {
break
case 'PageDown':
scrollTriggerRef.current = 'keyboard'
setIndex((prev) => {
const newIndex = prev + ctx.pageSize
return newIndex >= list.length ? list.length - 1 : newIndex
@ -317,6 +332,7 @@ export const QuickPanelView: React.FC<Props> = ({ setInputText }) => {
case 'ArrowLeft':
if (!isAssistiveKeyPressed) return
if (!historyPanel.length) return
scrollTriggerRef.current = 'initial'
clearSearchText(false)
if (historyPanel.length > 0) {
const lastPanel = historyPanel.pop()
@ -329,6 +345,7 @@ export const QuickPanelView: React.FC<Props> = ({ setInputText }) => {
case 'ArrowRight':
if (!isAssistiveKeyPressed) return
if (!list?.[index]?.isMenu) return
scrollTriggerRef.current = 'initial'
clearSearchText(false)
handleItemAction(list[index], 'enter')
break
@ -413,7 +430,14 @@ export const QuickPanelView: React.FC<Props> = ({ setInputText }) => {
$selectedColor={selectedColor}
$selectedColorHover={selectedColorHover}
className={ctx.isVisible ? 'visible' : ''}>
<QuickPanelBody ref={bodyRef} onMouseMove={() => setIsMouseOver(true)}>
<QuickPanelBody
ref={bodyRef}
onMouseMove={() =>
setIsMouseOver((prev) => {
scrollTriggerRef.current = 'initial'
return prev ? prev : true
})
}>
<FixedSizeList
ref={listRef}
itemCount={list.length}