import SwiftUI import WebKit import Combine /// SwiftUI wrapper around WKWebView. Exposes reload / navigation /// commands through a Coordinator that holds the WKWebView reference. struct WebView: NSViewRepresentable { let url: URL @Binding var canGoBack: Bool @Binding var canGoForward: Bool @Binding var isLoading: Bool @Binding var pageTitle: String let developerExtras: Bool /// Token broadcast by `App` commands to trigger navigation actions. let action: PassthroughSubject func makeCoordinator() -> Coordinator { Coordinator(self) } func makeNSView(context: Context) -> WKWebView { let cfg = WKWebViewConfiguration() cfg.websiteDataStore = .nonPersistent() if developerExtras { cfg.preferences.setValue(true, forKey: "developerExtrasEnabled") } let wv = WKWebView(frame: .zero, configuration: cfg) wv.navigationDelegate = context.coordinator wv.uiDelegate = context.coordinator wv.allowsBackForwardNavigationGestures = true wv.allowsMagnification = true wv.load(URLRequest(url: url)) context.coordinator.bind(webView: wv) return wv } func updateNSView(_ wv: WKWebView, context: Context) { // Reload if the URL prop changed (e.g. user edited it in Settings). if wv.url?.absoluteString != url.absoluteString { wv.load(URLRequest(url: url)) } } enum WebAction { case reload, back, forward, home, openInBrowser, copyURL } // ------------------------------------------------------------------------- final class Coordinator: NSObject, WKNavigationDelegate, WKUIDelegate { var parent: WebView weak var webView: WKWebView? private var cancellables = Set() private var observers: [NSKeyValueObservation] = [] init(_ p: WebView) { parent = p } func bind(webView: WKWebView) { self.webView = webView observers.append(webView.observe(\.canGoBack) { [weak self] wv, _ in Task { @MainActor in self?.parent.canGoBack = wv.canGoBack } }) observers.append(webView.observe(\.canGoForward) { [weak self] wv, _ in Task { @MainActor in self?.parent.canGoForward = wv.canGoForward } }) observers.append(webView.observe(\.isLoading) { [weak self] wv, _ in Task { @MainActor in self?.parent.isLoading = wv.isLoading } }) observers.append(webView.observe(\.title) { [weak self] wv, _ in Task { @MainActor in self?.parent.pageTitle = wv.title ?? "" } }) parent.action .receive(on: DispatchQueue.main) .sink { [weak self] act in self?.handle(act) } .store(in: &cancellables) } private func handle(_ a: WebAction) { guard let wv = webView else { return } switch a { case .reload: wv.reload() case .back: if wv.canGoBack { wv.goBack() } case .forward: if wv.canGoForward { wv.goForward() } case .home: wv.load(URLRequest(url: parent.url)) case .openInBrowser: if let u = wv.url { NSWorkspace.shared.open(u) } case .copyURL: if let u = wv.url?.absoluteString { let pb = NSPasteboard.general pb.clearContents() pb.setString(u, forType: .string) } } } } }