@@ -50,12 +50,10 @@ import { HelperPaneToggleButton } from "./HelperPaneToggleButton";
5050import { HelperPane } from "./HelperPane" ;
5151import { listContinuationKeymap } from "../../../ExpandedEditor/utils/templateUtils" ;
5252import { markdown } from "@codemirror/lang-markdown" ;
53+ import { HELPER_PANE_WIDTH } from "../constants" ;
54+ import { processFunctionWithArguments } from "../utils" ;
55+ import { useHelperPaneClickOutside , useHelperPane } from "../hooks/useHelperPane" ;
5356
54- type HelperPaneState = {
55- isOpen : boolean ;
56- top : number ;
57- left : number ;
58- }
5957export type ChipExpressionEditorComponentProps = {
6058 onTokenRemove ?: ( token : string ) => void ;
6159 onTokenClick ?: ( token : string ) => void ;
@@ -93,11 +91,7 @@ export type ChipExpressionEditorComponentProps = {
9391 enableListContinuation ?: boolean ;
9492}
9593
96- const HELPER_PANE_WIDTH = 300 ;
97-
9894export const ChipExpressionEditorComponent = ( props : ChipExpressionEditorComponentProps ) => {
99- const [ helperPaneState , setHelperPaneState ] = useState < HelperPaneState > ( { isOpen : false , top : 0 , left : 0 } ) ;
100-
10195 const editorRef = useRef < HTMLDivElement > ( null ) ;
10296 const helperPaneRef = useRef < HTMLDivElement > ( null ) ;
10397 const fieldContainerRef = useRef < HTMLDivElement > ( null ) ;
@@ -110,6 +104,17 @@ export const ChipExpressionEditorComponent = (props: ChipExpressionEditorCompone
110104 const { expressionEditor } = useFormContext ( ) ;
111105 const expressionEditorRpcManager = expressionEditor ?. rpcManager ;
112106
107+ // Helper pane state management
108+ const { helperPaneState, setHelperPaneState, handleManualToggle, handleKeyboardToggle } = useHelperPane (
109+ {
110+ editorRef,
111+ toggleButtonRef : helperPaneToggleButtonRef ,
112+ helperPaneWidth : HELPER_PANE_WIDTH ,
113+ onStateChange : props . onHelperPaneStateChange
114+ } ,
115+ ( ) => viewRef . current ?. coordsAtPos ( viewRef . current . state . selection . main . head ) || null
116+ ) ;
117+
113118 const needTokenRefetchListner = buildNeedTokenRefetchListner ( ( ) => {
114119 setIsTokenUpdateScheduled ( true ) ;
115120 } ) ;
@@ -158,46 +163,12 @@ export const ChipExpressionEditorComponent = (props: ChipExpressionEditorCompone
158163 return buildCompletionSource ( waitForStateChange ) ;
159164 } , [ props . completions ] ) ;
160165
161- const handleHelperPaneKeyboardToggle = ( ) => {
162- if ( ! viewRef . current ) return ;
163-
164- setHelperPaneState ( prev => {
165- // If helper pane is open, just close it
166- if ( prev . isOpen ) {
167- return { ...prev , isOpen : false } ;
168- }
169-
170- // If helper pane is closed, open it at the cursor position
171- const view = viewRef . current ! ;
172- const cursorCoords = view . coordsAtPos ( view . state . selection . main . head ) ;
173-
174- if ( cursorCoords && editorRef . current ) {
175- const editorRect = editorRef . current . getBoundingClientRect ( ) ;
176- let top = cursorCoords . bottom - editorRect . top ;
177- let left = cursorCoords . left - editorRect . left ;
178-
179- const viewportWidth = window . innerWidth ;
180- const absoluteLeft = cursorCoords . left ;
181- const overflow = absoluteLeft + HELPER_PANE_WIDTH - viewportWidth ;
182-
183- if ( overflow > 0 ) {
184- left -= overflow ;
185- }
186-
187- return { isOpen : true , top, left } ;
188- }
189-
190- // Fallback if cursor coordinates aren't available
191- return { isOpen : true , top : 0 , left : 0 } ;
192- } ) ;
193- } ;
194-
195166 const helperPaneKeymap = buildHelperPaneKeymap (
196167 ( ) => helperPaneState . isOpen ,
197168 ( ) => {
198169 setHelperPaneState ( prev => ( { ...prev , isOpen : false } ) ) ;
199170 } ,
200- handleHelperPaneKeyboardToggle
171+ handleKeyboardToggle
201172 ) ;
202173
203174 const onHelperItemSelect = async ( value : string , options : HelperpaneOnChangeOptions ) => {
@@ -219,36 +190,9 @@ export const ChipExpressionEditorComponent = (props: ChipExpressionEditorCompone
219190 // and extracting args
220191 if ( newValue . endsWith ( '()' ) || newValue . endsWith ( ')}' ) ) {
221192 if ( props . extractArgsFromFunction ) {
222- try {
223- // Extract the function definition from string templates like "${func()}"
224- let functionDef = newValue ;
225- let prefix = '' ;
226- let suffix = '' ;
227-
228- // Check if it's within a string template
229- const stringTemplateMatch = newValue . match ( / ^ ( .* \$ \{ ) ( [ ^ } ] + ) ( \} .* ) $ / ) ;
230- if ( stringTemplateMatch ) {
231- prefix = stringTemplateMatch [ 1 ] ;
232- functionDef = stringTemplateMatch [ 2 ] ;
233- suffix = stringTemplateMatch [ 3 ] ;
234- }
235-
236- let cursorPositionForExtraction = from + prefix . length + functionDef . length - 1 ;
237- if ( functionDef . endsWith ( ')}' ) ) {
238- cursorPositionForExtraction -= 1 ;
239- }
240-
241- const fnSignature = await props . extractArgsFromFunction ( functionDef , cursorPositionForExtraction ) ;
242-
243- if ( fnSignature && fnSignature . args && fnSignature . args . length > 0 ) {
244- const placeholderArgs = fnSignature . args . map ( ( arg , index ) => `$${ index + 1 } ` ) ;
245- const updatedFunctionDef = functionDef . slice ( 0 , - 2 ) + '(' + placeholderArgs . join ( ', ' ) + ')' ;
246- finalValue = prefix + updatedFunctionDef + suffix ;
247- cursorPosition = from + prefix . length + updatedFunctionDef . length - 1 ;
248- }
249- } catch ( error ) {
250- console . warn ( 'Failed to extract function arguments:' , error ) ;
251- }
193+ const result = await processFunctionWithArguments ( newValue , from , props . extractArgsFromFunction ) ;
194+ finalValue = result . finalValue ;
195+ cursorPosition = from + result . cursorAdjustment ;
252196 }
253197 }
254198
@@ -262,50 +206,6 @@ export const ChipExpressionEditorComponent = (props: ChipExpressionEditorCompone
262206 setHelperPaneState ( prev => ( { ...prev , isOpen : ! options . closeHelperPane } ) ) ;
263207 }
264208
265- const handleHelperPaneManualToggle = ( ) => {
266- if (
267- ! helperPaneToggleButtonRef ?. current ||
268- ! editorRef ?. current
269- ) return ;
270-
271- // Save current cursor position before toggling
272- if ( viewRef . current ) {
273- const selection = viewRef . current . state . selection . main ;
274- }
275-
276- const buttonRect = helperPaneToggleButtonRef . current . getBoundingClientRect ( ) ;
277- const editorRect = editorRef . current ?. getBoundingClientRect ( ) ;
278- let top = buttonRect . bottom - editorRect . top ;
279- let left = buttonRect . left - editorRect . left ;
280-
281- // Add overflow correction for window boundaries
282- const viewportWidth = window . innerWidth ;
283- const absoluteLeft = buttonRect . left ;
284- const overflow = absoluteLeft + HELPER_PANE_WIDTH - viewportWidth ;
285-
286- if ( overflow > 0 ) {
287- left -= overflow ;
288- }
289-
290- setHelperPaneState ( prev => ( {
291- ...prev ,
292- top,
293- left,
294- isOpen : ! prev . isOpen
295- } ) ) ;
296- }
297-
298- // Expose helper pane state to parent component
299- useEffect ( ( ) => {
300- if ( props . onHelperPaneStateChange ) {
301- props . onHelperPaneStateChange ( {
302- isOpen : helperPaneState . isOpen ,
303- ref : helperPaneToggleButtonRef ,
304- toggle : handleHelperPaneManualToggle
305- } ) ;
306- }
307- } , [ helperPaneState . isOpen ] ) ;
308-
309209 useEffect ( ( ) => {
310210 if ( ! editorRef . current ) return ;
311211 const startState = EditorState . create ( {
@@ -418,40 +318,23 @@ export const ChipExpressionEditorComponent = (props: ChipExpressionEditorCompone
418318 setIsTokenUpdateScheduled ( true ) ;
419319 } , [ Boolean ( props . sanitizedExpression ) , Boolean ( props . rawExpression ) ] ) ;
420320
421- useEffect ( ( ) => {
422- const handleClickOutside = ( event : MouseEvent ) => {
423- if ( ! helperPaneState . isOpen ) return ;
424-
425- const target = event . target as Element ;
426- const isClickInsideEditor = editorRef . current ?. contains ( target ) ;
427- const isClickInsideHelperPane = helperPaneRef . current ?. contains ( target ) ;
428- const isClickOnToggleButton = helperPaneToggleButtonRef . current ?. contains ( target ) ;
429- const isClickInsideToolbar = props . toolbarRef ?. current ?. contains ( target ) ;
430-
431- if ( ! isClickInsideEditor && ! isClickInsideHelperPane && ! isClickOnToggleButton && ! isClickInsideToolbar ) {
432- setHelperPaneState ( prev => ( { ...prev , isOpen : false } ) ) ;
433- viewRef . current ?. dom . blur ( ) ;
434- }
435- } ;
436-
437- const handleEscapeKey = ( event : KeyboardEvent ) => {
438- if ( ! helperPaneState . isOpen ) return ;
439- if ( event . key === 'Escape' ) {
440- event . preventDefault ( ) ;
441- event . stopPropagation ( ) ;
442- setHelperPaneState ( prev => ( { ...prev , isOpen : false } ) ) ;
443- }
444- } ;
445-
446- if ( helperPaneState . isOpen ) {
447- document . addEventListener ( 'mousedown' , handleClickOutside ) ;
448- document . addEventListener ( 'keydown' , handleEscapeKey ) ;
321+ // Handle click outside and escape key for helper pane
322+ useHelperPaneClickOutside ( {
323+ enabled : helperPaneState . isOpen ,
324+ refs : {
325+ editor : editorRef ,
326+ helperPane : helperPaneRef ,
327+ toggleButton : helperPaneToggleButtonRef ,
328+ toolbar : props . toolbarRef
329+ } ,
330+ onClickOutside : ( ) => {
331+ setHelperPaneState ( prev => ( { ...prev , isOpen : false } ) ) ;
332+ viewRef . current ?. dom . blur ( ) ;
333+ } ,
334+ onEscapeKey : ( ) => {
335+ setHelperPaneState ( prev => ( { ...prev , isOpen : false } ) ) ;
449336 }
450- return ( ) => {
451- document . removeEventListener ( 'mousedown' , handleClickOutside ) ;
452- document . removeEventListener ( 'keydown' , handleEscapeKey ) ;
453- } ;
454- } , [ helperPaneState . isOpen , props . toolbarRef ] ) ;
337+ } ) ;
455338
456339 const showToggle = props . showHelperPaneToggle !== false && props . isExpandedVersion ;
457340
@@ -461,7 +344,7 @@ export const ChipExpressionEditorComponent = (props: ChipExpressionEditorCompone
461344 < HelperPaneToggleButton
462345 ref = { helperPaneToggleButtonRef }
463346 isOpen = { helperPaneState . isOpen }
464- onClick = { handleHelperPaneManualToggle }
347+ onClick = { handleManualToggle }
465348 title = "Toggle Helper Panel (Ctrl+/ or Cmd+/)"
466349 />
467350 ) }
@@ -491,7 +374,7 @@ export const ChipExpressionEditorComponent = (props: ChipExpressionEditorCompone
491374 { ! props . isExpandedVersion &&
492375 < FloatingToggleButton
493376 ref = { helperPaneToggleButtonRef }
494- onClick = { handleHelperPaneManualToggle }
377+ onClick = { handleManualToggle }
495378 title = { helperPaneState . isOpen ? "Close Helper Panel" : "Open Helper Panel" }
496379 >
497380 { helperPaneState . isOpen ? < CloseHelperIcon /> : < OpenHelperIcon /> }
0 commit comments