style: 格式化 packages/@ant/ 下所有文件以通过 biome ci

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
claude-code-best
2026-05-01 21:55:51 +08:00
parent c32f26cf21
commit 9ea9859dce
92 changed files with 5903 additions and 5188 deletions

View File

@@ -1,84 +1,63 @@
import React, {
createContext,
type RefObject,
useContext,
useLayoutEffect,
useMemo,
} from 'react'
import type { Key } from '../core/events/input-event.js'
import {
type ChordResolveResult,
getBindingDisplayText,
resolveKeyWithChordState,
} from './resolver.js'
import type {
KeybindingContextName,
ParsedBinding,
ParsedKeystroke,
} from './types.js'
import React, { createContext, type RefObject, useContext, useLayoutEffect, useMemo } from 'react';
import type { Key } from '../core/events/input-event.js';
import { type ChordResolveResult, getBindingDisplayText, resolveKeyWithChordState } from './resolver.js';
import type { KeybindingContextName, ParsedBinding, ParsedKeystroke } from './types.js';
/** Handler registration for action callbacks */
type HandlerRegistration = {
action: string
context: KeybindingContextName
handler: () => void
}
action: string;
context: KeybindingContextName;
handler: () => void;
};
type KeybindingContextValue = {
/** Resolve a key input to an action name (with chord support) */
resolve: (
input: string,
key: Key,
activeContexts: KeybindingContextName[],
) => ChordResolveResult
resolve: (input: string, key: Key, activeContexts: KeybindingContextName[]) => ChordResolveResult;
/** Update the pending chord state */
setPendingChord: (pending: ParsedKeystroke[] | null) => void
setPendingChord: (pending: ParsedKeystroke[] | null) => void;
/** Get display text for an action (e.g., "ctrl+t") */
getDisplayText: (
action: string,
context: KeybindingContextName,
) => string | undefined
getDisplayText: (action: string, context: KeybindingContextName) => string | undefined;
/** All parsed bindings (for help display) */
bindings: ParsedBinding[]
bindings: ParsedBinding[];
/** Current pending chord keystrokes (null if not in a chord) */
pendingChord: ParsedKeystroke[] | null
pendingChord: ParsedKeystroke[] | null;
/** Currently active keybinding contexts (for priority resolution) */
activeContexts: Set<KeybindingContextName>
activeContexts: Set<KeybindingContextName>;
/** Register a context as active (call on mount) */
registerActiveContext: (context: KeybindingContextName) => void
registerActiveContext: (context: KeybindingContextName) => void;
/** Unregister a context (call on unmount) */
unregisterActiveContext: (context: KeybindingContextName) => void
unregisterActiveContext: (context: KeybindingContextName) => void;
/** Register a handler for an action (used by useKeybinding) */
registerHandler: (registration: HandlerRegistration) => () => void
registerHandler: (registration: HandlerRegistration) => () => void;
/** Invoke all handlers for an action (used by ChordInterceptor) */
invokeAction: (action: string) => boolean
}
invokeAction: (action: string) => boolean;
};
const KeybindingContext = createContext<KeybindingContextValue | null>(null)
const KeybindingContext = createContext<KeybindingContextValue | null>(null);
type ProviderProps = {
bindings: ParsedBinding[]
bindings: ParsedBinding[];
/** Ref for immediate access to pending chord (avoids React state delay) */
pendingChordRef: RefObject<ParsedKeystroke[] | null>
pendingChordRef: RefObject<ParsedKeystroke[] | null>;
/** State value for re-renders (UI updates) */
pendingChord: ParsedKeystroke[] | null
setPendingChord: (pending: ParsedKeystroke[] | null) => void
activeContexts: Set<KeybindingContextName>
registerActiveContext: (context: KeybindingContextName) => void
unregisterActiveContext: (context: KeybindingContextName) => void
pendingChord: ParsedKeystroke[] | null;
setPendingChord: (pending: ParsedKeystroke[] | null) => void;
activeContexts: Set<KeybindingContextName>;
registerActiveContext: (context: KeybindingContextName) => void;
unregisterActiveContext: (context: KeybindingContextName) => void;
/** Ref to handler registry (used by ChordInterceptor) */
handlerRegistryRef: RefObject<Map<string, Set<HandlerRegistration>>>
children: React.ReactNode
}
handlerRegistryRef: RefObject<Map<string, Set<HandlerRegistration>>>;
children: React.ReactNode;
};
export function KeybindingProvider({
bindings,
@@ -93,60 +72,54 @@ export function KeybindingProvider({
}: ProviderProps): React.ReactNode {
const value = useMemo<KeybindingContextValue>(() => {
const getDisplay = (action: string, context: KeybindingContextName) =>
getBindingDisplayText(action, context, bindings)
getBindingDisplayText(action, context, bindings);
// Register a handler for an action
const registerHandler = (registration: HandlerRegistration) => {
const registry = handlerRegistryRef.current
if (!registry) return () => {}
const registry = handlerRegistryRef.current;
if (!registry) return () => {};
if (!registry.has(registration.action)) {
registry.set(registration.action, new Set())
registry.set(registration.action, new Set());
}
registry.get(registration.action)!.add(registration)
registry.get(registration.action)!.add(registration);
// Return unregister function
return () => {
const handlers = registry.get(registration.action)
const handlers = registry.get(registration.action);
if (handlers) {
handlers.delete(registration)
handlers.delete(registration);
if (handlers.size === 0) {
registry.delete(registration.action)
registry.delete(registration.action);
}
}
}
}
};
};
// Invoke all handlers for an action
const invokeAction = (action: string): boolean => {
const registry = handlerRegistryRef.current
if (!registry) return false
const registry = handlerRegistryRef.current;
if (!registry) return false;
const handlers = registry.get(action)
if (!handlers || handlers.size === 0) return false
const handlers = registry.get(action);
if (!handlers || handlers.size === 0) return false;
// Find handlers whose context is active
for (const registration of handlers) {
if (activeContexts.has(registration.context)) {
registration.handler()
return true
registration.handler();
return true;
}
}
return false
}
return false;
};
return {
// Use ref for immediate access to pending chord, avoiding React state delay
// This is critical for chord sequences where the second key might be pressed
// before React re-renders with the updated pendingChord state
resolve: (input, key, contexts) =>
resolveKeyWithChordState(
input,
key,
contexts,
bindings,
pendingChordRef.current,
),
resolveKeyWithChordState(input, key, contexts, bindings, pendingChordRef.current),
setPendingChord,
getDisplayText: getDisplay,
bindings,
@@ -156,7 +129,7 @@ export function KeybindingProvider({
unregisterActiveContext,
registerHandler,
invokeAction,
}
};
}, [
bindings,
pendingChordRef,
@@ -166,23 +139,17 @@ export function KeybindingProvider({
registerActiveContext,
unregisterActiveContext,
handlerRegistryRef,
])
]);
return (
<KeybindingContext.Provider value={value}>
{children}
</KeybindingContext.Provider>
)
return <KeybindingContext.Provider value={value}>{children}</KeybindingContext.Provider>;
}
export function useKeybindingContext(): KeybindingContextValue {
const ctx = useContext(KeybindingContext)
const ctx = useContext(KeybindingContext);
if (!ctx) {
throw new Error(
'useKeybindingContext must be used within KeybindingProvider',
)
throw new Error('useKeybindingContext must be used within KeybindingProvider');
}
return ctx
return ctx;
}
/**
@@ -190,7 +157,7 @@ export function useKeybindingContext(): KeybindingContextValue {
* Useful for components that may render before provider is available.
*/
export function useOptionalKeybindingContext(): KeybindingContextValue | null {
return useContext(KeybindingContext)
return useContext(KeybindingContext);
}
/**
@@ -208,18 +175,15 @@ export function useOptionalKeybindingContext(): KeybindingContextValue | null {
* }
* ```
*/
export function useRegisterKeybindingContext(
context: KeybindingContextName,
isActive: boolean = true,
): void {
const keybindingContext = useOptionalKeybindingContext()
export function useRegisterKeybindingContext(context: KeybindingContextName, isActive: boolean = true): void {
const keybindingContext = useOptionalKeybindingContext();
useLayoutEffect(() => {
if (!keybindingContext || !isActive) return
if (!keybindingContext || !isActive) return;
keybindingContext.registerActiveContext(context)
keybindingContext.registerActiveContext(context);
return () => {
keybindingContext.unregisterActiveContext(context)
}
}, [context, keybindingContext, isActive])
keybindingContext.unregisterActiveContext(context);
};
}, [context, keybindingContext, isActive]);
}

View File

@@ -5,49 +5,47 @@
* wrapper. App-specific dependencies (binding loading, change subscription,
* warning display, debug logging) are injected via props.
*/
import React, { useCallback, useEffect, useRef, useState } from 'react'
import type { InputEvent } from '../core/events/input-event.js'
import React, { useCallback, useEffect, useRef, useState } from 'react';
import type { InputEvent } from '../core/events/input-event.js';
// ChordInterceptor intentionally uses useInput to intercept all keystrokes before
// other handlers process them - this is required for chord sequence support
// eslint-disable-next-line custom-rules/prefer-use-keybindings
import useInput from '../hooks/use-input.js'
import type { Key } from '../core/events/input-event.js'
import { KeybindingProvider } from './KeybindingContext.js'
import { resolveKeyWithChordState } from './resolver.js'
import useInput from '../hooks/use-input.js';
import type { Key } from '../core/events/input-event.js';
import { KeybindingProvider } from './KeybindingContext.js';
import { resolveKeyWithChordState } from './resolver.js';
import type {
KeybindingContextName,
KeybindingsLoadResult,
ParsedBinding,
ParsedKeystroke,
KeybindingWarning,
} from './types.js'
} from './types.js';
/**
* Timeout for chord sequences in milliseconds.
* If the user doesn't complete the chord within this time, it's cancelled.
*/
const CHORD_TIMEOUT_MS = 1000
const CHORD_TIMEOUT_MS = 1000;
export type KeybindingSetupProps = {
children: React.ReactNode
children: React.ReactNode;
/** Load bindings synchronously for initial render */
loadBindings: () => KeybindingsLoadResult
loadBindings: () => KeybindingsLoadResult;
/** Subscribe to binding changes; return an unsubscribe function */
subscribeToChanges: (
callback: (result: KeybindingsLoadResult) => void,
) => () => void
subscribeToChanges: (callback: (result: KeybindingsLoadResult) => void) => () => void;
/** Initialize any file watcher (idempotent). Called once on mount. */
initWatcher?: () => void | Promise<void>
initWatcher?: () => void | Promise<void>;
/** Optional callback when warnings are emitted (initial load or reload) */
onWarnings?: (warnings: KeybindingWarning[], isReload: boolean) => void
onWarnings?: (warnings: KeybindingWarning[], isReload: boolean) => void;
/** Optional debug logger */
onDebugLog?: (message: string) => void
}
onDebugLog?: (message: string) => void;
};
export function KeybindingSetup({
children,
@@ -59,115 +57,105 @@ export function KeybindingSetup({
}: KeybindingSetupProps): React.ReactNode {
// Load bindings synchronously for initial render
const [loadResult, setLoadResult] = useState<KeybindingsLoadResult>(() => {
const result = loadBindings()
const result = loadBindings();
onDebugLog?.(
`[keybindings] KeybindingSetup initialized with ${result.bindings.length} bindings, ${result.warnings.length} warnings`,
)
return result
})
);
return result;
});
const { bindings, warnings } = loadResult
const { bindings, warnings } = loadResult;
// Track if this is a reload (not initial load)
const [isReload, setIsReload] = useState(false)
const [isReload, setIsReload] = useState(false);
// Notify about warnings
useEffect(() => {
onWarnings?.(warnings, isReload)
}, [warnings, isReload, onWarnings])
onWarnings?.(warnings, isReload);
}, [warnings, isReload, onWarnings]);
// Chord state management - use ref for immediate access, state for re-renders
const pendingChordRef = useRef<ParsedKeystroke[] | null>(null)
const [pendingChord, setPendingChordState] = useState<
ParsedKeystroke[] | null
>(null)
const chordTimeoutRef = useRef<NodeJS.Timeout | null>(null)
const pendingChordRef = useRef<ParsedKeystroke[] | null>(null);
const [pendingChord, setPendingChordState] = useState<ParsedKeystroke[] | null>(null);
const chordTimeoutRef = useRef<NodeJS.Timeout | null>(null);
// Handler registry for action callbacks (used by ChordInterceptor to invoke handlers)
const handlerRegistryRef = useRef(
new Map<
string,
Set<{
action: string
context: KeybindingContextName
handler: () => void
action: string;
context: KeybindingContextName;
handler: () => void;
}>
>(),
)
);
// Active context tracking for keybinding priority resolution
const activeContextsRef = useRef<Set<KeybindingContextName>>(new Set())
const activeContextsRef = useRef<Set<KeybindingContextName>>(new Set());
const registerActiveContext = useCallback(
(context: KeybindingContextName) => {
activeContextsRef.current.add(context)
},
[],
)
const registerActiveContext = useCallback((context: KeybindingContextName) => {
activeContextsRef.current.add(context);
}, []);
const unregisterActiveContext = useCallback(
(context: KeybindingContextName) => {
activeContextsRef.current.delete(context)
},
[],
)
const unregisterActiveContext = useCallback((context: KeybindingContextName) => {
activeContextsRef.current.delete(context);
}, []);
// Clear chord timeout when component unmounts or chord changes
const clearChordTimeout = useCallback(() => {
if (chordTimeoutRef.current) {
clearTimeout(chordTimeoutRef.current)
chordTimeoutRef.current = null
clearTimeout(chordTimeoutRef.current);
chordTimeoutRef.current = null;
}
}, [])
}, []);
// Wrapper for setPendingChord that manages timeout and syncs ref+state
const setPendingChord = useCallback(
(pending: ParsedKeystroke[] | null) => {
clearChordTimeout()
clearChordTimeout();
if (pending !== null) {
// Set timeout to cancel chord if not completed
chordTimeoutRef.current = setTimeout(
(pendingChordRef, setPendingChordState) => {
onDebugLog?.('[keybindings] Chord timeout - cancelling')
pendingChordRef.current = null
setPendingChordState(null)
onDebugLog?.('[keybindings] Chord timeout - cancelling');
pendingChordRef.current = null;
setPendingChordState(null);
},
CHORD_TIMEOUT_MS,
pendingChordRef,
setPendingChordState,
)
);
}
// Update ref immediately for synchronous access in resolve()
pendingChordRef.current = pending
pendingChordRef.current = pending;
// Update state to trigger re-renders for UI updates
setPendingChordState(pending)
setPendingChordState(pending);
},
[clearChordTimeout, onDebugLog],
)
);
useEffect(() => {
// Initialize file watcher (idempotent - only runs once)
void initWatcher?.()
void initWatcher?.();
// Subscribe to changes
const unsubscribe = subscribeToChanges(result => {
// Any callback invocation is a reload since initial load happens
// synchronously in useState, not via this subscription
setIsReload(true)
setIsReload(true);
setLoadResult(result)
onDebugLog?.(
`[keybindings] Reloaded: ${result.bindings.length} bindings, ${result.warnings.length} warnings`,
)
})
setLoadResult(result);
onDebugLog?.(`[keybindings] Reloaded: ${result.bindings.length} bindings, ${result.warnings.length} warnings`);
});
return () => {
unsubscribe()
clearChordTimeout()
}
}, [subscribeToChanges, initWatcher, clearChordTimeout, onDebugLog])
unsubscribe();
clearChordTimeout();
};
}, [subscribeToChanges, initWatcher, clearChordTimeout, onDebugLog]);
return (
<KeybindingProvider
@@ -189,7 +177,7 @@ export function KeybindingSetup({
/>
{children}
</KeybindingProvider>
)
);
}
/**
@@ -203,10 +191,10 @@ export function KeybindingSetup({
* system could recognize it as completing a chord.
*/
type HandlerRegistration = {
action: string
context: KeybindingContextName
handler: () => void
}
action: string;
context: KeybindingContextName;
handler: () => void;
};
function ChordInterceptor({
bindings,
@@ -215,11 +203,11 @@ function ChordInterceptor({
activeContexts,
handlerRegistryRef,
}: {
bindings: ParsedBinding[]
pendingChordRef: React.RefObject<ParsedKeystroke[] | null>
setPendingChord: (pending: ParsedKeystroke[] | null) => void
activeContexts: Set<KeybindingContextName>
handlerRegistryRef: React.RefObject<Map<string, Set<HandlerRegistration>>>
bindings: ParsedBinding[];
pendingChordRef: React.RefObject<ParsedKeystroke[] | null>;
setPendingChord: (pending: ParsedKeystroke[] | null) => void;
activeContexts: Set<KeybindingContextName>;
handlerRegistryRef: React.RefObject<Map<string, Set<HandlerRegistration>>>;
}): null {
const handleInput = useCallback(
(input: string, key: Key, event: InputEvent) => {
@@ -228,94 +216,78 @@ function ChordInterceptor({
// here. Skip the registry scan. Mid-chord wheel still falls through so
// scrolling cancels the pending chord like any other non-matching key.
if ((key.wheelUp || key.wheelDown) && pendingChordRef.current === null) {
return
return;
}
// Build context list from registered handlers + activeContexts + Global
const registry = handlerRegistryRef.current
const handlerContexts = new Set<KeybindingContextName>()
const registry = handlerRegistryRef.current;
const handlerContexts = new Set<KeybindingContextName>();
if (registry) {
for (const handlers of registry.values()) {
for (const registration of handlers) {
handlerContexts.add(registration.context)
handlerContexts.add(registration.context);
}
}
}
const contexts: KeybindingContextName[] = [
...handlerContexts,
...activeContexts,
'Global',
]
const contexts: KeybindingContextName[] = [...handlerContexts, ...activeContexts, 'Global'];
// Track whether we're completing a chord (pending was non-null)
const wasInChord = pendingChordRef.current !== null
const wasInChord = pendingChordRef.current !== null;
// Check if this keystroke is part of a chord sequence
const result = resolveKeyWithChordState(
input,
key,
contexts,
bindings,
pendingChordRef.current,
)
const result = resolveKeyWithChordState(input, key, contexts, bindings, pendingChordRef.current);
switch (result.type) {
case 'chord_started':
// This key starts a chord - store pending state and stop propagation
setPendingChord(result.pending)
event.stopImmediatePropagation()
break
setPendingChord(result.pending);
event.stopImmediatePropagation();
break;
case 'match': {
// Clear pending state
setPendingChord(null)
setPendingChord(null);
// Only invoke handlers and stop propagation for chord completions
// (multi-keystroke sequences). Single-keystroke matches should propagate
// to per-hook handlers to avoid interfering with other input handling.
if (wasInChord) {
const contextsSet = new Set(contexts)
const contextsSet = new Set(contexts);
if (registry) {
const handlers = registry.get(result.action)
const handlers = registry.get(result.action);
if (handlers && handlers.size > 0) {
for (const registration of handlers) {
if (contextsSet.has(registration.context)) {
registration.handler()
event.stopImmediatePropagation()
break
registration.handler();
event.stopImmediatePropagation();
break;
}
}
}
}
}
break
break;
}
case 'chord_cancelled':
setPendingChord(null)
event.stopImmediatePropagation()
break
setPendingChord(null);
event.stopImmediatePropagation();
break;
case 'unbound':
setPendingChord(null)
event.stopImmediatePropagation()
break
setPendingChord(null);
event.stopImmediatePropagation();
break;
case 'none':
// No chord involvement - let other handlers process
break
break;
}
},
[
bindings,
pendingChordRef,
setPendingChord,
activeContexts,
handlerRegistryRef,
],
)
[bindings, pendingChordRef, setPendingChord, activeContexts, handlerRegistryRef],
);
useInput(handleInput)
useInput(handleInput);
return null
return null;
}