mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-06-15 19:19:35 +08:00
🐛 fix(sidebar): 修复 v2 搜索关闭交互异常
- 关闭命令搜索前提交同步筛选值,避免输入框清空覆盖侧栏筛选 - 限制弹窗打开期间才同步命令输入到侧栏持久筛选 - 增加全局 ESC 关闭监听,修复焦点离开弹窗后无法关闭 - 补充回归测试覆盖筛选保留和全局 ESC 关闭规则
This commit is contained in:
@@ -14,6 +14,7 @@ import Sidebar, {
|
||||
hasSidebarLazyChildren,
|
||||
normalizeSidebarTreeRelativeDropPosition,
|
||||
parseV2CommandSearchQuery,
|
||||
resolveV2CommandSearchPersistentFilter,
|
||||
type V2CommandSearchItem,
|
||||
resolveSidebarDropNodeFromDomEvent,
|
||||
resolveSidebarTagDropInsertBefore,
|
||||
@@ -27,6 +28,7 @@ import Sidebar, {
|
||||
shouldSkipSidebarLoadOnExpandWhileDragging,
|
||||
shouldSkipSidebarSelectWhileDragging,
|
||||
shouldLoadSidebarNodeOnExpand,
|
||||
shouldCloseV2CommandSearchOnGlobalKey,
|
||||
shouldRunV2CommandSearchEnter,
|
||||
sortSidebarTableEntries,
|
||||
} from './Sidebar';
|
||||
@@ -302,6 +304,51 @@ describe('Sidebar locate toolbar', () => {
|
||||
})).toBe(false);
|
||||
});
|
||||
|
||||
it('keeps v2 command search persisted filter after closing the palette', () => {
|
||||
expect(resolveV2CommandSearchPersistentFilter({
|
||||
commandSearchValue: ' org ',
|
||||
persistedFilter: '',
|
||||
enabled: true,
|
||||
isOpen: true,
|
||||
})).toBe('org');
|
||||
|
||||
expect(resolveV2CommandSearchPersistentFilter({
|
||||
commandSearchValue: '',
|
||||
persistedFilter: 'org',
|
||||
enabled: true,
|
||||
isOpen: false,
|
||||
})).toBe('org');
|
||||
|
||||
expect(resolveV2CommandSearchPersistentFilter({
|
||||
commandSearchValue: 'org',
|
||||
persistedFilter: 'org',
|
||||
enabled: false,
|
||||
isOpen: true,
|
||||
})).toBe('');
|
||||
});
|
||||
|
||||
it('closes v2 command search on global escape only while the palette is open', () => {
|
||||
expect(shouldCloseV2CommandSearchOnGlobalKey({
|
||||
key: 'Escape',
|
||||
isOpen: true,
|
||||
})).toBe(true);
|
||||
|
||||
expect(shouldCloseV2CommandSearchOnGlobalKey({
|
||||
key: 'Esc',
|
||||
isOpen: true,
|
||||
})).toBe(true);
|
||||
|
||||
expect(shouldCloseV2CommandSearchOnGlobalKey({
|
||||
key: 'Escape',
|
||||
isOpen: false,
|
||||
})).toBe(false);
|
||||
|
||||
expect(shouldCloseV2CommandSearchOnGlobalKey({
|
||||
key: 'Enter',
|
||||
isOpen: true,
|
||||
})).toBe(false);
|
||||
});
|
||||
|
||||
it('keeps all loaded v2 command table matches once a keyword is entered', () => {
|
||||
const items: V2CommandSearchItem[] = Array.from({ length: 40 }, (_, index) => ({
|
||||
key: `node-table-${index}`,
|
||||
@@ -538,6 +585,8 @@ describe('Sidebar locate toolbar', () => {
|
||||
expect(source).toContain('handleV2CommandSearchValueChange(event.target.value)');
|
||||
expect(source).toContain('toggleV2CommandSearchPersistentFilter');
|
||||
expect(source).toContain('gn-v2-command-filter-switch');
|
||||
expect(source).toContain("window.addEventListener('keydown', handleV2CommandSearchGlobalKeyDown, true)");
|
||||
expect(source).toContain("window.removeEventListener('keydown', handleV2CommandSearchGlobalKeyDown, true)");
|
||||
expect(source).toContain('onClick={() => setV2ExplorerFilter(item.key)}');
|
||||
expect(source).toContain('treeData={isV2Ui ? v2VisibleTreeData : displayTreeData}');
|
||||
expect(markup).toContain('gn-v2-sidebar-log-footer');
|
||||
|
||||
@@ -648,6 +648,38 @@ export const shouldRunV2CommandSearchEnter = ({
|
||||
return activeItemCount > 0;
|
||||
};
|
||||
|
||||
export interface V2CommandSearchPersistentFilterState {
|
||||
commandSearchValue: string;
|
||||
persistedFilter: string;
|
||||
enabled: boolean;
|
||||
isOpen: boolean;
|
||||
}
|
||||
|
||||
export const resolveV2CommandSearchPersistentFilter = ({
|
||||
commandSearchValue,
|
||||
persistedFilter,
|
||||
enabled,
|
||||
isOpen,
|
||||
}: V2CommandSearchPersistentFilterState): string => {
|
||||
if (!enabled) return '';
|
||||
if (!isOpen) return String(persistedFilter ?? '').trim();
|
||||
return String(commandSearchValue ?? '').trim();
|
||||
};
|
||||
|
||||
export interface V2CommandSearchGlobalKeyState {
|
||||
key: string;
|
||||
isOpen: boolean;
|
||||
}
|
||||
|
||||
export const shouldCloseV2CommandSearchOnGlobalKey = ({
|
||||
key,
|
||||
isOpen,
|
||||
}: V2CommandSearchGlobalKeyState): boolean => {
|
||||
if (!isOpen) return false;
|
||||
const normalizedKey = String(key || '').toLowerCase();
|
||||
return normalizedKey === 'escape' || normalizedKey === 'esc';
|
||||
};
|
||||
|
||||
export const resolveSidebarConnectionIdFromKey = (
|
||||
key: unknown,
|
||||
connectionIds: string[],
|
||||
@@ -1156,11 +1188,23 @@ const Sidebar: React.FC<{
|
||||
setV2CommandActiveIndex(0);
|
||||
}, []);
|
||||
|
||||
const commitV2CommandSearchPersistentFilter = useCallback((value = v2CommandSearchValue) => {
|
||||
if (!v2CommandSearchPersistentFilterEnabled) {
|
||||
return;
|
||||
}
|
||||
const nextFilter = value.trim();
|
||||
setSearchValue(nextFilter);
|
||||
if (nextFilter !== v2PersistedSidebarFilter) {
|
||||
setAppearance({ v2SidebarPersistedFilter: nextFilter });
|
||||
}
|
||||
}, [setAppearance, v2CommandSearchPersistentFilterEnabled, v2CommandSearchValue, v2PersistedSidebarFilter]);
|
||||
|
||||
const closeV2CommandSearch = useCallback(() => {
|
||||
commitV2CommandSearchPersistentFilter();
|
||||
setIsV2CommandSearchOpen(false);
|
||||
setV2CommandSearchValue('');
|
||||
setV2CommandActiveIndex(0);
|
||||
}, []);
|
||||
}, [commitV2CommandSearchPersistentFilter]);
|
||||
|
||||
useEffect(() => {
|
||||
setSearchValue(v2PersistedSidebarFilter);
|
||||
@@ -1184,13 +1228,21 @@ const Sidebar: React.FC<{
|
||||
if (!v2CommandSearchPersistentFilterEnabled) {
|
||||
return;
|
||||
}
|
||||
const nextFilter = deferredV2CommandSearchValue.trim();
|
||||
if (!isV2CommandSearchOpen) {
|
||||
return;
|
||||
}
|
||||
const nextFilter = resolveV2CommandSearchPersistentFilter({
|
||||
commandSearchValue: deferredV2CommandSearchValue,
|
||||
persistedFilter: v2PersistedSidebarFilter,
|
||||
enabled: v2CommandSearchPersistentFilterEnabled,
|
||||
isOpen: isV2CommandSearchOpen,
|
||||
});
|
||||
setSearchValue(nextFilter);
|
||||
const timer = window.setTimeout(() => {
|
||||
setAppearance({ v2SidebarPersistedFilter: nextFilter });
|
||||
}, 160);
|
||||
return () => window.clearTimeout(timer);
|
||||
}, [deferredV2CommandSearchValue, setAppearance, v2CommandSearchPersistentFilterEnabled]);
|
||||
}, [deferredV2CommandSearchValue, isV2CommandSearchOpen, setAppearance, v2CommandSearchPersistentFilterEnabled, v2PersistedSidebarFilter]);
|
||||
|
||||
const toggleV2CommandSearchPersistentFilter = useCallback((enabled: boolean) => {
|
||||
const nextFilter = enabled ? v2CommandSearchValue.trim() : '';
|
||||
@@ -1264,6 +1316,20 @@ const Sidebar: React.FC<{
|
||||
}, 0);
|
||||
return () => window.clearTimeout(timer);
|
||||
}, [isV2CommandSearchOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isV2CommandSearchOpen) return;
|
||||
const handleV2CommandSearchGlobalKeyDown = (event: KeyboardEvent) => {
|
||||
if (!shouldCloseV2CommandSearchOnGlobalKey({ key: event.key, isOpen: isV2CommandSearchOpen })) {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
closeV2CommandSearch();
|
||||
};
|
||||
window.addEventListener('keydown', handleV2CommandSearchGlobalKeyDown, true);
|
||||
return () => window.removeEventListener('keydown', handleV2CommandSearchGlobalKeyDown, true);
|
||||
}, [closeV2CommandSearch, isV2CommandSearchOpen]);
|
||||
|
||||
// Connection Status State: key -> 'success' | 'error'
|
||||
const [connectionStates, setConnectionStates] = useState<Record<string, 'success' | 'error'>>({});
|
||||
|
||||
Reference in New Issue
Block a user