11import { FieldValues , UseFormGetValues } from 'react-hook-form'
22import { PlacementWithLogical } from '@chakra-ui/react'
33import { Editor } from '@tiptap/react'
4- import { HTMLElement , Node , parse , TextNode } from 'node-html-parser'
4+ import {
5+ HTMLElement as NodeHTMLElement ,
6+ Node ,
7+ parse ,
8+ TextNode ,
9+ } from 'node-html-parser'
510
611import type { StepWithVariables } from '@/helpers/variables'
712
@@ -59,12 +64,12 @@ export function genVariableInfoMap(
5964function constructVariableSpanElement (
6065 varInfo : VariableInfoMap ,
6166 id : string ,
62- ) : HTMLElement {
67+ ) : NodeHTMLElement {
6368 const idComponents = id . split ( '.' )
6469 const varInfoForNode = varInfo . get ( `{{${ id } }}` )
6570 const value = varInfoForNode ?. testRunValue || ''
6671 const label = varInfoForNode ?. label || idComponents [ idComponents . length - 1 ]
67- const span = new HTMLElement ( 'span' , { } )
72+ const span = new NodeHTMLElement ( 'span' , { } )
6873 span . setAttribute ( 'data-type' , 'variable' )
6974 span . setAttribute ( 'data-id' , id )
7075 span . setAttribute ( 'data-label' , label )
@@ -93,9 +98,9 @@ function substituteTemplateStringWithSpan(
9398}
9499
95100function recursiveSubstitute (
96- el : HTMLElement ,
101+ el : NodeHTMLElement ,
97102 varInfo : VariableInfoMap ,
98- ) : HTMLElement {
103+ ) : NodeHTMLElement {
99104 const dataIdAttr = el . getAttribute ( 'data-id' )
100105 const dataTypeAttr = el . getAttribute ( 'data-type' )
101106 if ( dataTypeAttr === 'variable' && dataIdAttr != null ) {
@@ -105,7 +110,7 @@ function recursiveSubstitute(
105110 }
106111 const newChildNodes : Node [ ] = [ ]
107112 el . childNodes . forEach ( ( n ) => {
108- if ( n instanceof HTMLElement ) {
113+ if ( n instanceof NodeHTMLElement ) {
109114 newChildNodes . push ( recursiveSubstitute ( n , varInfo ) )
110115 } else if ( n instanceof TextNode ) {
111116 // We cannot use n.textContent here because it will unescape all HTML tags
@@ -139,7 +144,7 @@ export function getPopoverPlacement(
139144 return 'bottom-start'
140145 }
141146
142- const editorElement = editor ?. view . dom as globalThis . HTMLElement
147+ const editorElement = editor ?. view . dom as HTMLElement
143148 if ( ! editorElement ) {
144149 return 'bottom-start'
145150 }
@@ -163,3 +168,86 @@ export const checkAutoFocus = (
163168 const isNewRow = rowData ?. isNew
164169 return { shouldAutoFocus : isNewRow && autoFocusProp , isNewRow, rowData }
165170}
171+
172+ // Add scrolling behavior for single-line mode
173+ export const singleLineEditorScroll = ( editor : Editor ) => {
174+ if ( ! editor ) {
175+ return
176+ }
177+
178+ setTimeout ( ( ) => {
179+ const singleLineEditor = editor . view . dom . closest (
180+ '.single-line-editor' ,
181+ ) as HTMLElement
182+ if ( singleLineEditor ) {
183+ // Get the current cursor position
184+ const pos = editor . state . selection . $head . pos
185+ let targetVariable = findClosestVariableNode (
186+ editor ,
187+ pos ,
188+ singleLineEditor ,
189+ )
190+
191+ // If we still haven't found it, fall back to the last variable
192+ if ( ! targetVariable ) {
193+ const variables = Array . from (
194+ singleLineEditor . getElementsByClassName ( 'node-variable' ) ,
195+ ) as HTMLElement [ ]
196+ if ( variables . length > 0 ) {
197+ targetVariable = variables [ variables . length - 1 ]
198+ }
199+ }
200+
201+ // Scroll to the target variable if found
202+ if ( targetVariable ) {
203+ scrollVariableIntoView ( targetVariable , singleLineEditor )
204+ }
205+ }
206+ } , 10 )
207+ }
208+
209+ export function findClosestVariableNode (
210+ editor : any ,
211+ pos : number ,
212+ container : HTMLElement ,
213+ ) : HTMLElement | null {
214+ for ( let offset = - 1 ; offset <= 1 ; offset ++ ) {
215+ const checkPos = Math . max ( 0 , pos + offset )
216+ const domInfo = editor . view . domAtPos ( checkPos )
217+ const node = domInfo . node as HTMLElement
218+
219+ if ( node . classList ?. contains ( 'node-variable' ) ) {
220+ return node
221+ }
222+
223+ const prevSibling = node . previousElementSibling as HTMLElement
224+ if ( prevSibling ?. classList ?. contains ( 'node-variable' ) ) {
225+ return prevSibling
226+ }
227+
228+ const nextSibling = node . nextElementSibling as HTMLElement
229+ if ( nextSibling ?. classList ?. contains ( 'node-variable' ) ) {
230+ return nextSibling
231+ }
232+ }
233+
234+ // Fallback: last variable in the container
235+ const variables = container . getElementsByClassName ( 'node-variable' )
236+ return variables . length > 0
237+ ? ( variables [ variables . length - 1 ] as HTMLElement )
238+ : null
239+ }
240+
241+ export function scrollVariableIntoView (
242+ target : HTMLElement ,
243+ container : HTMLElement ,
244+ ) {
245+ const targetDiv = target
246+ const containerWidth = container . clientWidth
247+ const scrollLeft =
248+ targetDiv . offsetLeft - containerWidth + targetDiv . offsetWidth + 20
249+ container . scrollTo ( {
250+ left : Math . max ( 0 , scrollLeft ) ,
251+ behavior : 'smooth' ,
252+ } )
253+ }
0 commit comments