11'use client'
22
3- import React , { useCallback , useEffect , useMemo , useRef , useState } from 'react'
3+ import React , { useCallback , useEffect , useLayoutEffect , useMemo , useRef , useState } from 'react'
44import { Label , Switch } from '@/components/emcn'
55import { isApiClientError } from '@/lib/api/client/errors'
66import { requestJson } from '@/lib/api/client/request'
@@ -50,6 +50,8 @@ export function ChunkEditor({
5050 onCreated,
5151} : ChunkEditorProps ) {
5252 const textareaRef = useRef < HTMLTextAreaElement > ( null )
53+ const tokenizedScrollRef = useRef < HTMLDivElement > ( null )
54+ const preservedScrollTopRef = useRef ( 0 )
5355 const { mutateAsync : updateChunk } = useUpdateChunk ( )
5456 const { mutateAsync : createChunk } = useCreateChunk ( )
5557
@@ -170,6 +172,24 @@ export function ChunkEditor({
170172 [ saveRef ]
171173 )
172174
175+ const hasToggledTokenizerRef = useRef ( false )
176+
177+ const handleTokenizerChange = useCallback (
178+ ( value : boolean ) => {
179+ const source = tokenizerOn ? tokenizedScrollRef . current : textareaRef . current
180+ preservedScrollTopRef . current = source ?. scrollTop ?? 0
181+ hasToggledTokenizerRef . current = true
182+ setTokenizerOn ( value )
183+ } ,
184+ [ tokenizerOn ]
185+ )
186+
187+ useLayoutEffect ( ( ) => {
188+ if ( ! hasToggledTokenizerRef . current ) return
189+ const target = tokenizerOn ? tokenizedScrollRef . current : textareaRef . current
190+ if ( target ) target . scrollTop = preservedScrollTopRef . current
191+ } , [ tokenizerOn ] )
192+
173193 const tokenStrings = useMemo ( ( ) => {
174194 if ( ! tokenizerOn || ! editedContent ) return [ ]
175195 return getTokenStrings ( editedContent )
@@ -196,7 +216,10 @@ export function ChunkEditor({
196216 } }
197217 >
198218 { tokenizerOn ? (
199- < div className = 'h-full w-full cursor-default overflow-y-auto whitespace-pre-wrap break-words p-6 font-sans text-[var(--text-body)] text-sm' >
219+ < div
220+ ref = { tokenizedScrollRef }
221+ className = 'h-full w-full cursor-default overflow-y-auto whitespace-pre-wrap break-words p-6 font-sans text-[var(--text-body)] text-sm'
222+ >
200223 { tokenStrings . map ( ( token , index ) => (
201224 < span
202225 key = { index }
@@ -232,7 +255,7 @@ export function ChunkEditor({
232255 < div className = 'flex items-center justify-between border-[var(--border)] border-t px-6 py-2.5' >
233256 < TokenizerToggle
234257 checked = { tokenizerOn }
235- onCheckedChange = { setTokenizerOn }
258+ onCheckedChange = { handleTokenizerChange }
236259 hoveredTokenIndex = { tokenizerOn ? hoveredTokenIndex : null }
237260 />
238261 < span className = 'text-[var(--text-secondary)] text-caption' >
0 commit comments