@@ -169,6 +169,9 @@ export function WorkflowSearchReplace() {
169169 setActiveMatchId : state . setActiveMatchId ,
170170 } ) )
171171 )
172+ const prevQueryRef = useRef ( query )
173+ const prevIsOpenRef = useRef ( false )
174+ const afterReplaceIndexRef = useRef < number | null > ( null )
172175 const { data : workspaceCredentials } = useWorkspaceCredentials ( { workspaceId, enabled : isOpen } )
173176
174177 useRegisterGlobalCommands ( [
@@ -248,10 +251,7 @@ export function WorkflowSearchReplace() {
248251 )
249252
250253 useEffect ( ( ) => {
251- if ( ! isOpen ) {
252- usePanelEditorSearchStore . getState ( ) . setActiveSearchTarget ( null )
253- return
254- }
254+ if ( ! isOpen ) return
255255 searchInputRef . current ?. focus ( )
256256 searchInputRef . current ?. select ( )
257257 } , [ isOpen ] )
@@ -311,10 +311,7 @@ export function WorkflowSearchReplace() {
311311
312312 return [ ]
313313 } , [ activeMatch , hydratedMatches ] )
314- const eligibleMatchIds = useMemo (
315- ( ) => replaceAllTargetMatches . map ( ( match ) => match . id ) ,
316- [ replaceAllTargetMatches ]
317- )
314+ const eligibleMatchIds = replaceAllTargetMatches . map ( ( match ) => match . id )
318315 const controlTargetMatches = activeMatch ? [ activeMatch ] : [ ]
319316 const usesResourceReplacement = controlTargetMatches . some ( isConstrainedResourceMatch )
320317 const resourceReplacementContextKey =
@@ -324,23 +321,20 @@ export function WorkflowSearchReplace() {
324321 const replacement = resourceReplacementContextKey
325322 ? ( resourceReplacementByContext [ resourceReplacementContextKey ] ?? '' )
326323 : textReplacement
327- const handleReplacementChange = useCallback (
328- ( nextReplacement : string ) => {
329- if ( ! resourceReplacementContextKey ) {
330- setReplacement ( nextReplacement )
331- return
332- }
324+ const handleReplacementChange = ( nextReplacement : string ) => {
325+ if ( ! resourceReplacementContextKey ) {
326+ setReplacement ( nextReplacement )
327+ return
328+ }
333329
334- setResourceReplacementByContext ( ( current ) => ( {
335- ...current ,
336- [ resourceReplacementContextKey ] : nextReplacement ,
337- } ) )
338- } ,
339- [ resourceReplacementContextKey , setReplacement ]
340- )
341- const compatibleResourceOptions = useMemo (
342- ( ) => getCompatibleResourceReplacementOptions ( controlTargetMatches , resourceOptions ) ,
343- [ controlTargetMatches , resourceOptions ]
330+ setResourceReplacementByContext ( ( current ) => ( {
331+ ...current ,
332+ [ resourceReplacementContextKey ] : nextReplacement ,
333+ } ) )
334+ }
335+ const compatibleResourceOptions = getCompatibleResourceReplacementOptions (
336+ controlTargetMatches ,
337+ resourceOptions
344338 )
345339 const hasReplacement = replacement . trim ( ) . length > 0
346340 const activeReplacementIssue = activeMatch
@@ -359,34 +353,51 @@ export function WorkflowSearchReplace() {
359353 } )
360354 : 'No replaceable matches.'
361355
362- const applySubflowUpdate = useCallback (
363- ( update : WorkflowSearchReplaceSubflowUpdate ) => {
364- if ( update . fieldId === WORKFLOW_SEARCH_SUBFLOW_FIELD_IDS . iterations ) {
365- if ( typeof update . nextValue !== 'number' ) return
366- collaborativeUpdateIterationCount ( update . blockId , update . blockType , update . nextValue )
367- return
368- }
356+ const applySubflowUpdate = ( update : WorkflowSearchReplaceSubflowUpdate ) => {
357+ if ( update . fieldId === WORKFLOW_SEARCH_SUBFLOW_FIELD_IDS . iterations ) {
358+ if ( typeof update . nextValue !== 'number' ) return
359+ collaborativeUpdateIterationCount ( update . blockId , update . blockType , update . nextValue )
360+ return
361+ }
369362
370- collaborativeUpdateIterationCollection (
371- update . blockId ,
372- update . blockType ,
373- String ( update . nextValue )
374- )
375- } ,
376- [ collaborativeUpdateIterationCollection , collaborativeUpdateIterationCount ]
377- )
363+ collaborativeUpdateIterationCollection (
364+ update . blockId ,
365+ update . blockType ,
366+ String ( update . nextValue )
367+ )
368+ }
378369
379370 useEffect ( ( ) => {
380- if ( ! isOpen ) return
371+ if ( ! isOpen ) {
372+ prevIsOpenRef . current = false
373+ usePanelEditorSearchStore . getState ( ) . setActiveSearchTarget ( null )
374+ return
375+ }
376+
377+ const justOpened = ! prevIsOpenRef . current
378+ prevIsOpenRef . current = true
379+ const queryChanged = prevQueryRef . current !== query
380+ prevQueryRef . current = query
381381
382382 if ( hydratedMatches . length === 0 ) {
383+ afterReplaceIndexRef . current = null
383384 if ( activeMatchId ) setActiveMatchId ( null )
384385 usePanelEditorSearchStore . getState ( ) . setActiveSearchTarget ( null )
385386 return
386387 }
387388
388389 if ( ! activeMatchId || ! hydratedMatches . some ( ( match ) => match . id === activeMatchId ) ) {
389- handleSelectMatch ( hydratedMatches [ 0 ] . id )
390+ const replaceIndex = afterReplaceIndexRef . current
391+ afterReplaceIndexRef . current = null
392+
393+ if ( queryChanged || justOpened ) {
394+ handleSelectMatch ( hydratedMatches [ 0 ] . id )
395+ } else if ( replaceIndex !== null ) {
396+ handleSelectMatch ( hydratedMatches [ Math . min ( replaceIndex , hydratedMatches . length - 1 ) ] . id )
397+ } else {
398+ setActiveMatchId ( null )
399+ usePanelEditorSearchStore . getState ( ) . setActiveSearchTarget ( null )
400+ }
390401 return
391402 }
392403
@@ -401,14 +412,19 @@ export function WorkflowSearchReplace() {
401412
402413 const handleMoveActiveMatch = ( delta : number ) => {
403414 if ( hydratedMatches . length === 0 ) return
404- const currentIndex = activeMatchIndex >= 0 ? activeMatchIndex : 0
405- const nextIndex = ( currentIndex + delta + hydratedMatches . length ) % hydratedMatches . length
415+ if ( activeMatchIndex < 0 ) {
416+ handleSelectMatch ( hydratedMatches [ delta > 0 ? 0 : hydratedMatches . length - 1 ] . id )
417+ return
418+ }
419+ const nextIndex = ( activeMatchIndex + delta + hydratedMatches . length ) % hydratedMatches . length
406420 handleSelectMatch ( hydratedMatches [ nextIndex ] . id )
407421 }
408422
409- const handleApply = ( matchIds : string [ ] ) => {
423+ const handleApply = ( matchIds : string [ ] , replaceActiveIndex ?: number ) => {
410424 if ( ! workflowId || isApplying || searchReadOnly ) return
425+ if ( replaceActiveIndex !== undefined ) afterReplaceIndexRef . current = replaceActiveIndex
411426 setIsApplying ( true )
427+ let committed = false
412428
413429 try {
414430 const selectedIds = new Set ( matchIds )
@@ -505,14 +521,16 @@ export function WorkflowSearchReplace() {
505521 message : `Replaced ${ replacedCount } field${ replacedCount === 1 ? '' : 's' } .` ,
506522 workflowId,
507523 } )
524+ committed = true
508525 } finally {
509526 setIsApplying ( false )
527+ if ( ! committed ) afterReplaceIndexRef . current = null
510528 }
511529 }
512530
513531 const handleReplaceActive = ( ) => {
514532 if ( ! activeMatch ) return
515- handleApply ( [ activeMatch . id ] )
533+ handleApply ( [ activeMatch . id ] , activeMatchIndex )
516534 }
517535
518536 const handleReplaceAll = ( ) => {
@@ -522,7 +540,9 @@ export function WorkflowSearchReplace() {
522540 const matchCountLabel =
523541 hydratedMatches . length === 0
524542 ? 'No results'
525- : `${ activeMatchIndex >= 0 ? activeMatchIndex + 1 : 1 } of ${ hydratedMatches . length } `
543+ : activeMatchIndex >= 0
544+ ? `${ activeMatchIndex + 1 } of ${ hydratedMatches . length } `
545+ : `0 of ${ hydratedMatches . length } `
526546 return (
527547 < div
528548 role = 'dialog'
@@ -566,7 +586,7 @@ export function WorkflowSearchReplace() {
566586 >
567587 < ChevronRight
568588 className = { cn (
569- 'h-[14px] w -[14px] text-[var(--text-icon)] transition-transform' ,
589+ 'size -[14px] text-[var(--text-icon)] transition-transform' ,
570590 isReplaceExpanded && 'rotate-90'
571591 ) }
572592 />
0 commit comments