Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 7 additions & 13 deletions Sources/WelcomeWindow/Views/RecentsListView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,27 @@ public struct RecentsListView: View {

@FocusState.Binding private var focusedField: FocusTarget?
private let dismissWindow: () -> Void
private let openHandler: WelcomeOpenHandler

public init(
recentProjects: Binding<[URL]>,
selection: Binding<Set<URL>>,
focusedField: FocusState<FocusTarget?>.Binding,
dismissWindow: @escaping () -> Void
dismissWindow: @escaping () -> Void,
openHandler: @escaping WelcomeOpenHandler
) {
self._recentProjects = recentProjects
self._selection = selection
self._focusedField = focusedField
self.dismissWindow = dismissWindow
self.openHandler = openHandler
}

private var isFocused: Bool {
focusedField == .recentProjects
}

@ViewBuilder
private var listEmptyView: some View {
@ViewBuilder private var listEmptyView: some View {
VStack {
Spacer()
Text("No Recent Projects")
Expand Down Expand Up @@ -70,11 +72,7 @@ public struct RecentsListView: View {
}
}
} primaryAction: { items in
for url in items {
NSDocumentController.shared.openDocument(at: url) {
dismissWindow()
}
}
openHandler(Array(items), dismissWindow)
}
.onCopyCommand {
selection.map { NSItemProvider(object: $0.path(percentEncoded: false) as NSString) }
Expand All @@ -84,11 +82,7 @@ public struct RecentsListView: View {
}
.background {
Button("") {
selection.forEach { url in
NSDocumentController.shared.openDocument(at: url) {
dismissWindow()
}
}
openHandler(Array(selection), dismissWindow)
}
.keyboardShortcut(.defaultAction)
.hidden()
Expand Down
6 changes: 2 additions & 4 deletions Sources/WelcomeWindow/Views/WelcomeView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,7 @@ public struct WelcomeView<SubtitleView: View>: View {
}
}

@ViewBuilder
private var mainContent: some View {
@ViewBuilder private var mainContent: some View {
VStack(spacing: 0) {
Spacer().frame(height: 32)
ZStack {
Expand Down Expand Up @@ -154,8 +153,7 @@ public struct WelcomeView<SubtitleView: View>: View {
}
}

@ViewBuilder
private var dismissButton: some View {
@ViewBuilder private var dismissButton: some View {
Button(action: dismissWindow) {
Image(systemName: "xmark.circle.fill")
.foregroundColor(isHoveringCloseButton ? Color(.secondaryLabelColor) : Color(.tertiaryLabelColor))
Expand Down
40 changes: 32 additions & 8 deletions Sources/WelcomeWindow/Views/WelcomeWindow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public struct WelcomeWindow<RecentsView: View, SubtitleView: View>: Scene {
private let customRecentsList: ((_ dismissWindow: @escaping () -> Void) -> RecentsView)?
private let onDrop: ((_ url: URL, _ dismiss: @escaping () -> Void) -> Void)?
private let subtitleView: (() -> SubtitleView)?
private let openHandler: WelcomeOpenHandler?

let iconImage: Image?
let title: String?
Expand All @@ -33,14 +34,16 @@ public struct WelcomeWindow<RecentsView: View, SubtitleView: View>: Scene {
@ActionsBuilder actions: @escaping (_ dismissWindow: @escaping () -> Void) -> WelcomeActions,
customRecentsList: ((_ dismissWindow: @escaping () -> Void) -> RecentsView)? = nil,
subtitleView: (() -> SubtitleView)? = nil,
onDrop: ((_ url: URL, _ dismiss: @escaping () -> Void) -> Void)? = nil
onDrop: ((_ url: URL, _ dismiss: @escaping () -> Void) -> Void)? = nil,
openHandler: WelcomeOpenHandler? = nil
) {
self.iconImage = iconImage
self.title = title
self.buildActions = actions
self.customRecentsList = customRecentsList
self.subtitleView = subtitleView
self.onDrop = onDrop
self.openHandler = openHandler
}

public var body: some Scene {
Expand All @@ -52,7 +55,8 @@ public struct WelcomeWindow<RecentsView: View, SubtitleView: View>: Scene {
subtitleView: subtitleView,
buildActions: buildActions,
onDrop: onDrop,
customRecentsList: customRecentsList
customRecentsList: customRecentsList,
openHandler: openHandler
)
.frame(width: 740, height: isMacOS26 ? 460 - 28 : 460)
.task {
Expand All @@ -71,6 +75,20 @@ public struct WelcomeWindow<RecentsView: View, SubtitleView: View>: Scene {
}
}

/// A closure type used to handle opening recent items from the default `RecentsListView`.
///
/// This allows apps to override the default `NSDocumentController` behavior for
/// opening files, making the handling of recent-item URLs fully configurable.
///
/// The closure is executed on the **main actor** to ensure UI safety.
///
/// - Parameters:
/// - urls: The recent-item URLs selected by the user to be opened.
/// - dismiss: A closure to invoke when the opening process is complete and the
/// `RecentsListView` can be dismissed.
///
public typealias WelcomeOpenHandler = @MainActor (_ urls: [URL], _ dismiss: @escaping () -> Void) -> Void

// ──────────────────────────────────────────────────────────────
// 1) NEITHER a custom recents list NOR a subtitle view
// ──────────────────────────────────────────────────────────────
Expand All @@ -81,15 +99,17 @@ extension WelcomeWindow where RecentsView == EmptyView, SubtitleView == EmptyVie
iconImage: Image? = nil,
title: String? = nil,
@ActionsBuilder actions: @escaping (_ dismissWindow: @escaping () -> Void) -> WelcomeActions,
onDrop: ((_ url: URL, _ dismissWindow: @escaping () -> Void) -> Void)? = nil
onDrop: ((_ url: URL, _ dismissWindow: @escaping () -> Void) -> Void)? = nil,
openHandler: WelcomeOpenHandler? = nil
) {
self.init(
iconImage: iconImage,
title: title,
actions: actions,
customRecentsList: nil,
subtitleView: nil,
onDrop: onDrop
onDrop: onDrop,
openHandler: openHandler
)
}
}
Expand All @@ -105,15 +125,17 @@ extension WelcomeWindow where RecentsView == EmptyView {
title: String? = nil,
subtitleView: @escaping () -> SubtitleView,
@ActionsBuilder actions: @escaping (_ dismissWindow: @escaping () -> Void) -> WelcomeActions,
onDrop: ((_ url: URL, _ dismissWindow: @escaping () -> Void) -> Void)? = nil
onDrop: ((_ url: URL, _ dismissWindow: @escaping () -> Void) -> Void)? = nil,
openHandler: WelcomeOpenHandler? = nil
) {
self.init(
iconImage: iconImage,
title: title,
actions: actions,
customRecentsList: nil,
subtitleView: subtitleView,
onDrop: onDrop
onDrop: onDrop,
openHandler: openHandler
)
}
}
Expand All @@ -129,15 +151,17 @@ extension WelcomeWindow where SubtitleView == EmptyView {
title: String? = nil,
@ActionsBuilder actions: @escaping (_ dismissWindow: @escaping () -> Void) -> WelcomeActions,
customRecentsList: ((_ dismissWindow: @escaping () -> Void) -> RecentsView)? = nil,
onDrop: ((_ url: URL, _ dismissWindow: @escaping () -> Void) -> Void)? = nil
onDrop: ((_ url: URL, _ dismissWindow: @escaping () -> Void) -> Void)? = nil,
openHandler: WelcomeOpenHandler? = nil
) {
self.init(
iconImage: iconImage,
title: title,
actions: actions,
customRecentsList: customRecentsList,
subtitleView: nil,
onDrop: onDrop
onDrop: onDrop,
openHandler: openHandler
)
}
}
21 changes: 19 additions & 2 deletions Sources/WelcomeWindow/Views/WelcomeWindowView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public struct WelcomeWindowView<RecentsView: View, SubtitleView: View>: View {
private let onDrop: ((_ url: URL, _ dismiss: @escaping () -> Void) -> Void)?
private let customRecentsList: ((_ dismissWindow: @escaping () -> Void) -> RecentsView)?
private let subtitleView: (() -> SubtitleView)?
private let openHandler: WelcomeOpenHandler?

let iconImage: Image?
let title: String?
Expand All @@ -35,19 +36,34 @@ public struct WelcomeWindowView<RecentsView: View, SubtitleView: View>: View {
subtitleView: (() -> SubtitleView)? = nil,
buildActions: @escaping (_ dismissWindow: @escaping () -> Void) -> WelcomeActions,
onDrop: ((_ url: URL, _ dismiss: @escaping () -> Void) -> Void)? = nil,
customRecentsList: ((_ dismissWindow: @escaping () -> Void) -> RecentsView)? = nil
customRecentsList: ((_ dismissWindow: @escaping () -> Void) -> RecentsView)? = nil,
openHandler: WelcomeOpenHandler? = nil
) {
self.iconImage = iconImage
self.title = title
self.subtitleView = subtitleView
self.buildActions = buildActions
self.onDrop = onDrop
self.customRecentsList = customRecentsList
self.openHandler = openHandler
}

private func defaultOpenHandler(urls: [URL], dismiss: @escaping () -> Void) {
var dismissed = false
for url in urls {
NSDocumentController.shared.openDocument(at: url) {
if !dismissed {
dismissed = true
dismiss()
}
}
}
}

public var body: some View {
let dismiss = dismissWindow.callAsFunction
let actions = buildActions(dismiss)
let effectiveOpen = openHandler ?? defaultOpenHandler

return HStack(spacing: 0) {
WelcomeView(
Expand All @@ -67,7 +83,8 @@ public struct WelcomeWindowView<RecentsView: View, SubtitleView: View>: View {
recentProjects: $recentProjects,
selection: $selection,
focusedField: $focusedField,
dismissWindow: dismiss
dismissWindow: dismiss,
openHandler: effectiveOpen
)
}
}
Expand Down