MCPcopy Create free account
hub / github.com/codeaashu/claude-code / ScrollBox

Function ScrollBox

src/ink/components/ScrollBox.tsx:82–235  ·  view source on GitHub ↗

* A Box with `overflow: scroll` and an imperative scroll API. * * Children are laid out at their full Yoga-computed height inside a * constrained container. At render time, only children intersecting the * visible window (scrollTop..scrollTop+height) are rendered (viewport * culling). Content i

({
  children,
  ref,
  stickyScroll,
  ...style
}: PropsWithChildren<ScrollBoxProps>)

Source from the content-addressed store, hash-verified

80 * Works best inside a fullscreen (constrained-height root) Ink tree.
81 */
82function ScrollBox({
83 children,
84 ref,
85 stickyScroll,
86 ...style
87}: PropsWithChildren<ScrollBoxProps>): React.ReactNode {
88 const domRef = useRef<DOMElement>(null);
89 // scrollTo/scrollBy bypass React: they mutate scrollTop on the DOM node,
90 // mark it dirty, and call the root's throttled scheduleRender directly.
91 // The Ink renderer reads scrollTop from the node — no React state needed,
92 // no reconciler overhead per wheel event. The microtask defer coalesces
93 // multiple scrollBy calls in one input batch (discreteUpdates) into one
94 // render — otherwise scheduleRender's leading edge fires on the FIRST
95 // event before subsequent events mutate scrollTop. scrollToBottom still
96 // forces a React render: sticky is attribute-observed, no DOM-only path.
97 const [, forceRender] = useState(0);
98 const listenersRef = useRef(new Set<() => void>());
99 const renderQueuedRef = useRef(false);
100 const notify = () => {
101 for (const l of listenersRef.current) l();
102 };
103 function scrollMutated(el: DOMElement): void {
104 // Signal background intervals (IDE poll, LSP poll, GCS fetch, orphan
105 // check) to skip their next tick — they compete for the event loop and
106 // contributed to 1402ms max frame gaps during scroll drain.
107 markScrollActivity();
108 markDirty(el);
109 markCommitStart();
110 notify();
111 if (renderQueuedRef.current) return;
112 renderQueuedRef.current = true;
113 queueMicrotask(() => {
114 renderQueuedRef.current = false;
115 scheduleRenderFrom(el);
116 });
117 }
118 useImperativeHandle(ref, (): ScrollBoxHandle => ({
119 scrollTo(y: number) {
120 const el = domRef.current;
121 if (!el) return;
122 // Explicit false overrides the DOM attribute so manual scroll
123 // breaks stickiness. Render code checks ?? precedence.
124 el.stickyScroll = false;
125 el.pendingScrollDelta = undefined;
126 el.scrollAnchor = undefined;
127 el.scrollTop = Math.max(0, Math.floor(y));
128 scrollMutated(el);
129 },
130 scrollToElement(el: DOMElement, offset = 0) {
131 const box = domRef.current;
132 if (!box) return;
133 box.stickyScroll = false;
134 box.pendingScrollDelta = undefined;
135 box.scrollAnchor = {
136 el,
137 offset
138 };
139 scrollMutated(box);

Callers

nothing calls this directly

Calls

no outgoing calls

Tested by

no test coverage detected