@@ -53,7 +53,7 @@ struct HTMLRenderer {
5353 }
5454}
5555
56- // MARK: - WebKit Crash-Aware Delegate
56+ // MARK: - WebKit Crash Delegate
5757final class PreviewNavDelegate : NSObject , WKNavigationDelegate {
5858 var onCrash : ( ( ) -> Void ) ?
5959
@@ -73,7 +73,7 @@ final class PreviewNavDelegate: NSObject, WKNavigationDelegate {
7373 }
7474}
7575
76- // MARK: - WebView (Coordinator reuse, safe refresh)
76+ // MARK: - WebView
7777struct WebView : NSViewRepresentable {
7878 let html : String
7979 let baseURL : URL ?
@@ -347,9 +347,13 @@ struct EditorAreaFileView: View {
347347 @State private var watcher = DirectoryWatcher ( )
348348 @State private var lastKnownFileMTime : Date ?
349349
350- // Fixed size constants
351- private let FIXED_PREVIEW_HEIGHT : CGFloat = 320
352- private let FIXED_PREVIEW_WIDTH : CGFloat = 420
350+ // Width-resize state
351+ @State private var previewWidth : CGFloat = 420 // default width for split mode
352+ @State private var dragStartPreviewWidth : CGFloat ?
353+
354+ // Min/max constraints for width-only resizing
355+ private let MIN_PREVIEW_WIDTH : CGFloat = 200
356+ private let MAX_PREVIEW_WIDTH : CGFloat = 1400
353357
354358 private func bindContent( ) {
355359 NotificationCenter . default. publisher (
@@ -501,6 +505,26 @@ struct EditorAreaFileView: View {
501505
502506 Divider ( )
503507
508+ // thin draggable vertical divider between code and preview (width-resize)
509+ let verticalDivider : some View = Rectangle ( )
510+ . fill ( Color ( NSColor . separatorColor) )
511+ . frame ( width: 2 )
512+ // give a larger transparent hit area so it is easy to grab
513+ . padding ( . horizontal, 6 )
514+ . contentShape ( Rectangle ( ) )
515+ . gesture ( DragGesture ( minimumDistance: 1 ) . onChanged { value in
516+ if dragStartPreviewWidth == nil { dragStartPreviewWidth = previewWidth }
517+ let start = dragStartPreviewWidth ?? previewWidth
518+ let delta = value. location. x - value. startLocation. x
519+ let newW = start - delta
520+ previewWidth = max ( MIN_PREVIEW_WIDTH, min ( MAX_PREVIEW_WIDTH, newW) )
521+ } . onEnded { _ in
522+ dragStartPreviewWidth = nil
523+ } )
524+ . onHover { hovering in
525+ if hovering { NSCursor . resizeLeftRight. push ( ) } else { NSCursor . pop ( ) }
526+ }
527+
504528 if isHTML {
505529 switch displayMode {
506530 case . code:
@@ -519,7 +543,9 @@ struct EditorAreaFileView: View {
519543 allowJavaScript: webViewAllowJS
520544 )
521545 . id ( webViewRefreshToken)
522- . frame ( maxWidth: . infinity, minHeight: FIXED_PREVIEW_HEIGHT, maxHeight: FIXED_PREVIEW_HEIGHT)
546+ . frame ( maxWidth: . infinity, minHeight: 320 , maxHeight: . infinity)
547+ // ensure top safe area isn't covered — add top padding to push web content down
548+ . padding ( . top, edgeInsets. top)
523549 . background ( Color . white)
524550
525551 VStack ( spacing: 0 ) {
@@ -579,7 +605,7 @@ struct EditorAreaFileView: View {
579605
580606 case . split:
581607 HStack ( spacing: 0 ) {
582- // Wrap code view so we can show the "show preview" button when preview is hidden
608+ // Code area
583609 ZStack {
584610 CodeFileView ( editorInstance: editorInstance, codeFile: codeFile)
585611
@@ -606,53 +632,57 @@ struct EditorAreaFileView: View {
606632 . frame ( minWidth: 200 )
607633
608634 if showPreviewPane {
609- VStack ( spacing: 0 ) {
610- ZStack {
611- WebView (
612- html: renderedHTMLState. isEmpty
613- ? " <!doctype html><html><body style='background:#fff'><h1>Preview Ready</h1></body></html> "
614- : renderedHTMLState,
615- baseURL: codeFile. fileURL? . deletingLastPathComponent ( ) ,
616- onCrash: { /* WebKit always on */ } ,
617- allowJavaScript: webViewAllowJS
618- )
619- . id ( webViewRefreshToken)
620- . frame ( maxWidth: . infinity, minHeight: FIXED_PREVIEW_HEIGHT, maxHeight: FIXED_PREVIEW_HEIGHT)
621- . background ( Color . white)
635+ // divider placed between code and preview (gesture here)
636+ verticalDivider
637+ . zIndex ( 5 )
622638
623- VStack ( spacing: 0 ) {
624- Spacer ( )
625- PreviewBottomBar (
626- refresh: { refreshPreview ( ) } ,
627- reloadIgnoreCache: { webViewRefreshToken = UUID ( ) } ,
628- enableJS: $webViewAllowJS,
629- previewSource: $previewSource,
630- serverErrorMessage: serverErrorMessage
631- )
632- }
639+ // Preview container constrained to adjustable width
640+ ZStack {
641+ WebView (
642+ html: renderedHTMLState. isEmpty
643+ ? " <!doctype html><html><body style='background:#fff'><h1>Preview Ready</h1></body></html> "
644+ : renderedHTMLState,
645+ baseURL: codeFile. fileURL? . deletingLastPathComponent ( ) ,
646+ onCrash: { /* WebKit always on */ } ,
647+ allowJavaScript: webViewAllowJS
648+ )
649+ . id ( webViewRefreshToken)
650+ . frame ( maxWidth: . infinity, minHeight: 320 , maxHeight: . infinity)
651+ // push content below top chrome so h1 isn't cut off
652+ . padding ( . top, edgeInsets. top)
653+ . background ( Color . white)
633654
634- // drag / divider area (kept minimal here, you can replace with more advanced drag logic)
655+ VStack ( spacing: 0 ) {
656+ Spacer ( )
657+ PreviewBottomBar (
658+ refresh: { refreshPreview ( ) } ,
659+ reloadIgnoreCache: { webViewRefreshToken = UUID ( ) } ,
660+ enableJS: $webViewAllowJS,
661+ previewSource: $previewSource,
662+ serverErrorMessage: serverErrorMessage
663+ )
635664 }
636- . overlay ( alignment: . topTrailing) {
637- Button {
638- withAnimation ( . easeInOut( duration: 0.18 ) ) {
639- showPreviewPane = false
640- }
641- } label: {
642- Image ( systemName: " eye.slash " )
643- . font ( . system( size: 14 , weight: . medium) )
644- . padding ( 6 )
645- . background ( Color . black. opacity ( 0.12 ) )
646- . clipShape ( Circle ( ) )
665+ }
666+ . overlay ( alignment: . topTrailing) {
667+ Button {
668+ withAnimation ( . easeInOut( duration: 0.18 ) ) {
669+ showPreviewPane = false
647670 }
648- . buttonStyle ( . plain)
649- . help ( " Hide Preview " )
650- . padding ( . top, edgeInsets. top + 8 )
651- . padding ( . trailing, 8 )
652- . zIndex ( 10 )
671+ } label: {
672+ Image ( systemName: " eye.slash " )
673+ . font ( . system( size: 14 , weight: . medium) )
674+ . padding ( 6 )
675+ . background ( Color . black. opacity ( 0.12 ) )
676+ . clipShape ( Circle ( ) )
653677 }
678+ . buttonStyle ( . plain)
679+ . help ( " Hide Preview " )
680+ . padding ( . top, edgeInsets. top + 8 )
681+ . padding ( . trailing, 8 )
682+ . zIndex ( 10 )
654683 }
655- . frame ( width: FIXED_PREVIEW_WIDTH)
684+ // Constrain preview to the adjustable width
685+ . frame ( width: previewWidth)
656686 . frame ( maxHeight: . infinity)
657687 . background ( Color . white)
658688 }
@@ -668,7 +698,8 @@ struct EditorAreaFileView: View {
668698 VStack ( spacing: 0 ) {
669699 ZStack {
670700 MarkdownView ( source: contentString)
671- . frame ( maxWidth: . infinity, minHeight: FIXED_PREVIEW_HEIGHT, maxHeight: FIXED_PREVIEW_HEIGHT)
701+ . frame ( maxWidth: . infinity, minHeight: 320 , maxHeight: . infinity)
702+ . padding ( . top, edgeInsets. top)
672703 . background ( Color . white)
673704
674705 VStack ( spacing: 0 ) {
@@ -727,7 +758,7 @@ struct EditorAreaFileView: View {
727758
728759 case . split:
729760 HStack ( spacing: 0 ) {
730- // Wrap code view so we can show the "show preview" button when preview is hidden
761+ // Code area
731762 ZStack {
732763 CodeFileView ( editorInstance: editorInstance, codeFile: codeFile)
733764
@@ -753,10 +784,14 @@ struct EditorAreaFileView: View {
753784 . frame ( minWidth: 200 )
754785
755786 if showPreviewPane {
787+ verticalDivider
788+ . zIndex ( 5 )
789+
756790 VStack ( spacing: 0 ) {
757791 ZStack {
758792 MarkdownView ( source: contentString)
759- . frame ( maxWidth: . infinity, minHeight: FIXED_PREVIEW_HEIGHT, maxHeight: FIXED_PREVIEW_HEIGHT)
793+ . frame ( maxWidth: . infinity, minHeight: 320 , maxHeight: . infinity)
794+ . padding ( . top, edgeInsets. top)
760795 . background ( Color . white)
761796
762797 VStack ( spacing: 0 ) {
@@ -789,9 +824,9 @@ struct EditorAreaFileView: View {
789824 . zIndex ( 10 )
790825 }
791826 }
792- . frame ( minWidth: FIXED_PREVIEW_WIDTH ,
793- idealWidth: FIXED_PREVIEW_WIDTH ,
794- maxWidth: FIXED_PREVIEW_WIDTH ,
827+ . frame ( minWidth: previewWidth ,
828+ idealWidth: previewWidth ,
829+ maxWidth: previewWidth ,
795830 maxHeight: . infinity, alignment: . center)
796831 . background ( Color . white)
797832 }
0 commit comments