1- import { useCallback , useMemo , useRef } from "react" ;
1+ import { useCallback , useEffect , useMemo , useRef , useState } from "react" ;
22import QueryEditor , { type QueryEditorHandle } from "../editor/QueryEditor" ;
33import CommandsPanel from "../results/CommandsPanel" ;
44import ResultsGrid from "../results/ResultsGrid" ;
@@ -91,6 +91,38 @@ export default function QueryTab({ tab }: Props) {
9191
9292 const running = tab . runState . phase === "running" ;
9393
94+ const splitRef = useRef < HTMLDivElement | null > ( null ) ;
95+ const [ editorPct , setEditorPct ] = useState < number > ( ( ) => readSplitPct ( ) ) ;
96+ const dragging = useRef ( false ) ;
97+ const onSplitterDown = useCallback ( ( e : React . MouseEvent ) => {
98+ e . preventDefault ( ) ;
99+ dragging . current = true ;
100+ document . body . style . cursor = "row-resize" ;
101+ document . body . style . userSelect = "none" ;
102+ } , [ ] ) ;
103+ useEffect ( ( ) => {
104+ const onMove = ( e : MouseEvent ) => {
105+ if ( ! dragging . current || ! splitRef . current ) return ;
106+ const rect = splitRef . current . getBoundingClientRect ( ) ;
107+ const pct = ( ( e . clientY - rect . top ) / rect . height ) * 100 ;
108+ const clamped = Math . min ( 85 , Math . max ( 15 , pct ) ) ;
109+ setEditorPct ( clamped ) ;
110+ } ;
111+ const onUp = ( ) => {
112+ if ( ! dragging . current ) return ;
113+ dragging . current = false ;
114+ document . body . style . cursor = "" ;
115+ document . body . style . userSelect = "" ;
116+ writeSplitPct ( editorPct ) ;
117+ } ;
118+ window . addEventListener ( "mousemove" , onMove ) ;
119+ window . addEventListener ( "mouseup" , onUp ) ;
120+ return ( ) => {
121+ window . removeEventListener ( "mousemove" , onMove ) ;
122+ window . removeEventListener ( "mouseup" , onUp ) ;
123+ } ;
124+ } , [ editorPct ] ) ;
125+
94126 return (
95127 < div className = "query-tab" >
96128 < div className = "query-toolbar" >
@@ -124,7 +156,11 @@ export default function QueryTab({ tab }: Props) {
124156 < span className = "toolbar-spacer" />
125157 < span className = "toolbar-status" > { statusLine } </ span >
126158 </ div >
127- < div className = "query-split" >
159+ < div
160+ className = "query-split"
161+ ref = { splitRef }
162+ style = { { gridTemplateRows : `${ editorPct } % 6px 1fr` } }
163+ >
128164 < div className = "query-editor-pane" >
129165 < QueryEditor
130166 ref = { editorRef }
@@ -135,6 +171,17 @@ export default function QueryTab({ tab }: Props) {
135171 profileId = { tab . profileId }
136172 />
137173 </ div >
174+ < div
175+ className = "query-splitter"
176+ role = "separator"
177+ aria-orientation = "horizontal"
178+ title = "Drag to resize · double-click to reset"
179+ onMouseDown = { onSplitterDown }
180+ onDoubleClick = { ( ) => {
181+ setEditorPct ( 40 ) ;
182+ writeSplitPct ( 40 ) ;
183+ } }
184+ />
138185 < div className = "query-results-pane" >
139186 { tab . runState . phase === "error" ? (
140187 < div className = "query-error" > { tab . runState . message } </ div >
@@ -160,6 +207,28 @@ export default function QueryTab({ tab }: Props) {
160207 ) ;
161208}
162209
210+ const SPLIT_KEY = "pg-shell.querySplit.editorPct" ;
211+
212+ function readSplitPct ( ) : number {
213+ try {
214+ const raw = localStorage . getItem ( SPLIT_KEY ) ;
215+ if ( ! raw ) return 40 ;
216+ const n = Number . parseFloat ( raw ) ;
217+ if ( ! Number . isFinite ( n ) ) return 40 ;
218+ return Math . min ( 85 , Math . max ( 15 , n ) ) ;
219+ } catch {
220+ return 40 ;
221+ }
222+ }
223+
224+ function writeSplitPct ( pct : number ) : void {
225+ try {
226+ localStorage . setItem ( SPLIT_KEY , String ( pct ) ) ;
227+ } catch {
228+ // ignore
229+ }
230+ }
231+
163232function statusText ( tab : QueryTabState ) : string {
164233 const liveRows = tab . rows . length ;
165234 switch ( tab . runState . phase ) {
0 commit comments