@@ -13,6 +13,7 @@ struct FavoritesTabView: View {
1313 @State private var selectedFavoriteIds : Set < String > = [ ]
1414 @State private var folderToDelete : SQLFavoriteFolder ?
1515 @State private var showDeleteFolderAlert = false
16+ @FocusState private var isRenameFocused : Bool
1617 let searchText : String
1718 private weak var coordinator : MainContentCoordinator ?
1819
@@ -69,32 +70,92 @@ struct FavoritesTabView: View {
6970
7071 private func favoritesList( _ items: [ FavoriteTreeItem ] ) -> some View {
7172 List ( selection: $selectedFavoriteIds) {
72- ForEach ( items) { item in
73- FavoriteTreeItemRow (
74- item: item,
75- viewModel: viewModel,
76- coordinator: coordinator,
77- onDeleteFolder: { folder in
78- folderToDelete = folder
79- showDeleteFolderAlert = true
80- }
81- )
82- . tag ( item. id)
83- }
73+ flattenedRows ( items)
8474 }
8575 . listStyle ( . sidebar)
8676 . scrollContentBackground ( . hidden)
8777 . onDeleteCommand {
8878 deleteSelectedFavorites ( )
8979 }
90- . contextMenu {
91- if !selectedFavoriteIds. isEmpty {
92- Button ( role: . destructive) {
93- deleteSelectedFavorites ( )
94- } label: {
95- Label ( String ( localized: " Delete Selected " ) , systemImage: " trash " )
80+ }
81+
82+ /// Renders tree items with DisclosureGroup for folders.
83+ /// Each favorite row gets `.tag()` so List selection works across all nesting levels.
84+ private func flattenedRows( _ items: [ FavoriteTreeItem ] ) -> AnyView {
85+ AnyView (
86+ ForEach ( items) { item in
87+ switch item {
88+ case . favorite( let favorite) :
89+ FavoriteRowView ( favorite: favorite)
90+ . tag ( " fav- \( favorite. id) " )
91+ . overlay {
92+ DoubleClickDetector {
93+ coordinator? . insertFavorite ( favorite)
94+ }
95+ }
96+ . contextMenu {
97+ FavoriteItemContextMenu (
98+ favorite: favorite,
99+ viewModel: viewModel,
100+ coordinator: coordinator
101+ )
102+ }
103+ case . folder( let folder, let children) :
104+ DisclosureGroup ( isExpanded: Binding (
105+ get: { viewModel. expandedFolderIds. contains ( folder. id) } ,
106+ set: { expanded in
107+ if expanded {
108+ viewModel. expandedFolderIds. insert ( folder. id)
109+ } else {
110+ viewModel. expandedFolderIds. remove ( folder. id)
111+ }
112+ }
113+ ) ) {
114+ flattenedRows ( children)
115+ } label: {
116+ folderLabel ( folder)
117+ }
96118 }
97119 }
120+ )
121+ }
122+
123+ @ViewBuilder
124+ private func folderLabel( _ folder: SQLFavoriteFolder ) -> some View {
125+ if viewModel. renamingFolderId == folder. id {
126+ HStack ( spacing: 4 ) {
127+ Image ( systemName: " folder " )
128+ TextField (
129+ " " ,
130+ text: Binding (
131+ get: { viewModel. renamingFolderName } ,
132+ set: { viewModel. renamingFolderName = $0 }
133+ )
134+ )
135+ . textFieldStyle ( . roundedBorder)
136+ . focused ( $isRenameFocused)
137+ . onSubmit {
138+ viewModel. commitRenameFolder ( folder)
139+ }
140+ . onExitCommand {
141+ viewModel. renamingFolderId = nil
142+ }
143+ . onAppear {
144+ isRenameFocused = true
145+ }
146+ }
147+ } else {
148+ Label ( folder. name, systemImage: " folder " )
149+ . contextMenu {
150+ FolderContextMenu (
151+ folder: folder,
152+ viewModel: viewModel,
153+ onDelete: { f in
154+ folderToDelete = f
155+ showDeleteFolderAlert = true
156+ }
157+ )
158+ }
98159 }
99160 }
100161
@@ -190,89 +251,6 @@ struct FavoritesTabView: View {
190251 }
191252}
192253
193- // MARK: - Recursive Tree Item View
194-
195- struct FavoriteTreeItemRow : View {
196- let item : FavoriteTreeItem
197- let viewModel : FavoritesSidebarViewModel
198- weak var coordinator : MainContentCoordinator ?
199- var onDeleteFolder : ( ( SQLFavoriteFolder ) -> Void ) ?
200- @FocusState private var isRenameFocused : Bool
201-
202- var body : some View {
203- switch item {
204- case . favorite( let favorite) :
205- FavoriteRowView ( favorite: favorite)
206- . overlay {
207- DoubleClickDetector {
208- coordinator? . insertFavorite ( favorite)
209- }
210- }
211- . contextMenu {
212- FavoriteItemContextMenu (
213- favorite: favorite,
214- viewModel: viewModel,
215- coordinator: coordinator
216- )
217- }
218- case . folder( let folder, let children) :
219- DisclosureGroup ( isExpanded: Binding (
220- get: { viewModel. expandedFolderIds. contains ( folder. id) } ,
221- set: { isExpanded in
222- if isExpanded {
223- viewModel. expandedFolderIds. insert ( folder. id)
224- } else {
225- viewModel. expandedFolderIds. remove ( folder. id)
226- }
227- }
228- ) ) {
229- ForEach ( children) { child in
230- FavoriteTreeItemRow (
231- item: child,
232- viewModel: viewModel,
233- coordinator: coordinator,
234- onDeleteFolder: onDeleteFolder
235- )
236- . tag ( child. id)
237- }
238- } label: {
239- if viewModel. renamingFolderId == folder. id {
240- HStack ( spacing: 4 ) {
241- Image ( systemName: " folder " )
242- TextField (
243- " " ,
244- text: Binding (
245- get: { viewModel. renamingFolderName } ,
246- set: { viewModel. renamingFolderName = $0 }
247- )
248- )
249- . textFieldStyle ( . roundedBorder)
250- . focused ( $isRenameFocused)
251- . onSubmit {
252- viewModel. commitRenameFolder ( folder)
253- }
254- . onExitCommand {
255- viewModel. renamingFolderId = nil
256- }
257- . onAppear {
258- isRenameFocused = true
259- }
260- }
261- } else {
262- Label ( folder. name, systemImage: " folder " )
263- . contextMenu {
264- FolderContextMenu (
265- folder: folder,
266- viewModel: viewModel,
267- onDelete: onDeleteFolder ?? { _ in }
268- )
269- }
270- }
271- }
272- }
273- }
274- }
275-
276254// MARK: - Context Menus
277255
278256private struct FavoriteItemContextMenu : View {
0 commit comments