@@ -118,25 +118,8 @@ struct ContextDetailsView: View {
118118 @Binding var showAddBrowserTabDialog : Bool
119119 @Binding var showAddTerminalDialog : Bool
120120
121- @State private var isEditingTitle = false
122- @State private var draftTitle = " "
123- // New: clearer editing/hover/focus states for the title
124- @State private var isHoveringTitle = false
125- @FocusState private var titleFieldFocused : Bool
126-
127121 // New: Editing state for context items
128122 @State private var editingItemIndex : EditingIndex ? = nil
129-
130- // Force toolbar relayout when the context or title size category changes
131- private var principalLayoutID : String {
132- if isEditingTitle {
133- // Important: do not depend on draftTitle length while editing to avoid TextField resets
134- return " \( context. id) -e "
135- } else {
136- let bucket = max ( 1 , min ( 8 , context. name. count / 12 ) ) // coarse buckets to reduce churn
137- return " \( context. id) -v- \( bucket) "
138- }
139- }
140123
141124 var body : some View {
142125 ZStack {
@@ -285,93 +268,16 @@ struct ContextDetailsView: View {
285268 }
286269 }
287270 . toolbar {
288- // Make the title occupy the principal (expandable) area of the toolbar
271+ // Invisible, flexible item claims the center (principal) space
289272 ToolbarItem ( placement: . principal) {
290- if isEditingTitle {
291- HStack ( spacing: 8 ) {
292- TextField ( " Context Name " , text: $draftTitle, onCommit: { commitTitle ( ) } )
293- . font ( . system( size: 17 , weight: . bold) )
294- . textFieldStyle ( PlainTextFieldStyle ( ) )
295- . focused ( $titleFieldFocused)
296- . onAppear {
297- // Focus the field when entering edit mode; draftTitle is set when toggling edit mode
298- DispatchQueue . main. async { self . titleFieldFocused = true }
299- }
300- . onExitCommand { cancelEditTitle ( ) }
301- . lineLimit ( 1 )
302- . allowsTightening ( true )
303- . minimumScaleFactor ( 0.5 )
304- . fixedSize ( horizontal: false , vertical: true )
305- . frame ( maxWidth: . infinity, alignment: . leading)
306- // Quick actions for explicit save/cancel
307- Button { commitTitle ( ) } label: {
308- Image ( systemName: " checkmark.circle.fill " ) . foregroundColor ( . accentColor)
309- }
310- . buttonStyle ( . plain)
311- . help ( " Save title " )
312- Button { cancelEditTitle ( ) } label: {
313- Image ( systemName: " xmark.circle.fill " ) . foregroundColor ( . secondary)
314- }
315- . buttonStyle ( . plain)
316- . help ( " Cancel editing " )
317- }
318- . padding ( . vertical, 4 )
319- . padding ( . horizontal, 8 )
320- . background (
321- RoundedRectangle ( cornerRadius: 8 )
322- . fill ( Color ( NSColor . textBackgroundColor) )
323- )
324- . overlay (
325- RoundedRectangle ( cornerRadius: 8 )
326- . stroke ( Color . accentColor. opacity ( 0.6 ) , lineWidth: 1 )
327- )
328- // Allow the title editor to expand across available toolbar space
329- . frame ( minWidth: 140 , maxWidth: . infinity, alignment: . leading)
330- // Keep stable id logic
331- . id ( principalLayoutID)
332- } else {
333- HStack ( spacing: 6 ) {
334- Text ( context. name)
335- . font ( . system( size: 17 , weight: . bold) )
336- . lineLimit ( 2 )
337- . truncationMode ( . middle)
338- . allowsTightening ( true )
339- . minimumScaleFactor ( 0.5 )
340- . multilineTextAlignment ( . leading)
341- . fixedSize ( horizontal: false , vertical: true )
342- . frame ( maxWidth: . infinity, alignment: . leading)
343- . contentShape ( Rectangle ( ) )
344- . onTapGesture {
345- draftTitle = context. name
346- isEditingTitle = true
347- }
348- // Reserve space for the pencil to avoid layout shifts and only fade it
349- Image ( systemName: " pencil " )
350- . font ( . system( size: 13 ) )
351- . foregroundColor ( . secondary)
352- . opacity ( isHoveringTitle ? 1 : 0 )
353- . frame ( width: 16 , height: 16 )
354- }
355- . padding ( . vertical, 4 )
356- . padding ( . horizontal, 8 )
357- . background (
358- RoundedRectangle ( cornerRadius: 8 )
359- . fill ( isHoveringTitle ? Color . accentColor. opacity ( 0.08 ) : Color . clear)
360- )
361- . overlay (
362- RoundedRectangle ( cornerRadius: 8 )
363- . stroke ( isHoveringTitle ? Color . accentColor. opacity ( 0.25 ) : Color . clear, lineWidth: 1 )
364- )
365- // Allow the title to expand across available toolbar space
366- . frame ( minWidth: 140 , maxWidth: . infinity, alignment: . leading)
367- . onHover { hovering in
368- isHoveringTitle = hovering
369- }
370- . help ( " Click to rename " )
371- . id ( principalLayoutID)
372- }
273+ Color . clear. frame ( maxWidth: . infinity)
373274 }
374- // Ensure action buttons stay on the trailing edge
275+ // Title anchored on the very left (navigation area), allowed to expand
276+ ToolbarItem ( placement: . navigation) {
277+ ContextTitleView ( context: context, contextManager: contextManager)
278+ . frame ( maxWidth: . infinity, alignment: . leading)
279+ }
280+ // Trailing actions stay on the right
375281 ToolbarItemGroup ( placement: . primaryAction) {
376282 // Add dropdown menu for adding items (unchanged)
377283 Menu {
@@ -380,20 +286,16 @@ struct ContextDetailsView: View {
380286 Button ( " Browser Tab " , action: { showAddBrowserTabDialog = true } )
381287 Button ( " Shell Script " , action: { showAddTerminalDialog = true } )
382288 } label: {
383- // HStack(spacing: 6) {
384289 Image ( systemName: " plus " )
385- // Text("Add")
386- // }
387- // .font(.system(size: 16, weight: .medium))
388- . help ( " Add Item " )
290+ . help ( " Add Item " )
389291 }
390292 // Single context button
391293 ContextButton ( context: context, contextManager: contextManager)
392294 }
393295 }
394296 . navigationTitle ( " " )
395297 . onChange ( of: context. id) { _, _ in
396- isEditingTitle = false
298+ // Title editing state now managed by ContextTitleView
397299 }
398300 }
399301
@@ -416,24 +318,6 @@ struct ContextDetailsView: View {
416318 . listRowInsets ( EdgeInsets ( ) )
417319 . background ( Color . clear)
418320 }
419-
420- // MARK: - Title helpers
421- private func commitTitle( ) {
422- isEditingTitle = false
423- let trimmed = draftTitle. trimmingCharacters ( in: . whitespacesAndNewlines)
424- if !trimmed. isEmpty && trimmed != context. name {
425- context. name = trimmed
426- contextManager. saveContexts ( )
427- } else {
428- // Restore draft to the current name if unchanged
429- draftTitle = context. name
430- }
431- }
432-
433- private func cancelEditTitle( ) {
434- draftTitle = context. name
435- isEditingTitle = false
436- }
437321}
438322
439323struct ContextButton : View {
0 commit comments