init claude-code
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
import { useCallback, useContext, useLayoutEffect, useRef } from 'react'
|
||||
import CursorDeclarationContext from '../components/CursorDeclarationContext.js'
|
||||
import type { DOMElement } from '../dom.js'
|
||||
|
||||
/**
|
||||
* Declares where the terminal cursor should be parked after each frame.
|
||||
*
|
||||
* Terminal emulators render IME preedit text at the physical cursor
|
||||
* position, and screen readers / screen magnifiers track the native
|
||||
* cursor — so parking it at the text input's caret makes CJK input
|
||||
* appear inline and lets accessibility tools follow the input.
|
||||
*
|
||||
* Returns a ref callback to attach to the Box that contains the input.
|
||||
* The declared (line, column) is interpreted relative to that Box's
|
||||
* nodeCache rect (populated by renderNodeToOutput).
|
||||
*
|
||||
* Timing: Both ref attach and useLayoutEffect fire in React's layout
|
||||
* phase — after resetAfterCommit calls scheduleRender. scheduleRender
|
||||
* defers onRender via queueMicrotask, so onRender runs AFTER layout
|
||||
* effects commit and reads the fresh declaration on the first frame
|
||||
* (no one-keystroke lag). Test env uses onImmediateRender (synchronous,
|
||||
* no microtask), so tests compensate by calling ink.onRender()
|
||||
* explicitly after render.
|
||||
*/
|
||||
export function useDeclaredCursor({
|
||||
line,
|
||||
column,
|
||||
active,
|
||||
}: {
|
||||
line: number
|
||||
column: number
|
||||
active: boolean
|
||||
}): (element: DOMElement | null) => void {
|
||||
const setCursorDeclaration = useContext(CursorDeclarationContext)
|
||||
const nodeRef = useRef<DOMElement | null>(null)
|
||||
|
||||
const setNode = useCallback((node: DOMElement | null) => {
|
||||
nodeRef.current = node
|
||||
}, [])
|
||||
|
||||
// When active, set unconditionally. When inactive, clear conditionally
|
||||
// (only if the currently-declared node is ours). The node-identity check
|
||||
// handles two hazards:
|
||||
// 1. A memo()ized active instance elsewhere (e.g. the search input in
|
||||
// a memo'd Footer) doesn't re-render this commit — an inactive
|
||||
// instance re-rendering here must not clobber it.
|
||||
// 2. Sibling handoff (menu focus moving between list items) — when
|
||||
// focus moves opposite to sibling order, the newly-inactive item's
|
||||
// effect runs AFTER the newly-active item's set. Without the node
|
||||
// check it would clobber.
|
||||
// No dep array: must re-declare every commit so the active instance
|
||||
// re-claims the declaration after another instance's unmount-cleanup or
|
||||
// sibling handoff nulls it.
|
||||
useLayoutEffect(() => {
|
||||
const node = nodeRef.current
|
||||
if (active && node) {
|
||||
setCursorDeclaration({ relativeX: column, relativeY: line, node })
|
||||
} else {
|
||||
setCursorDeclaration(null, node)
|
||||
}
|
||||
})
|
||||
|
||||
// Clear on unmount (conditionally — another instance may own by then).
|
||||
// Separate effect with empty deps so cleanup only fires once — not on
|
||||
// every line/column change, which would transiently null between commits.
|
||||
useLayoutEffect(() => {
|
||||
return () => {
|
||||
setCursorDeclaration(null, nodeRef.current)
|
||||
}
|
||||
}, [setCursorDeclaration])
|
||||
|
||||
return setNode
|
||||
}
|
||||
Reference in New Issue
Block a user