2.5.2 #12
@@ -28,6 +28,7 @@ struct ModelInfoView: View {
|
||||
|
||||
@Environment(\.dismiss) var dismiss
|
||||
@Bindable private var settings = SettingsService.shared
|
||||
@State private var showDescriptionCopied = false
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
@@ -80,13 +81,34 @@ struct ModelInfoView: View {
|
||||
// lay out). The modal itself already scrolls, so a long description just
|
||||
// means more scrolling, which sidesteps the whole bug class.
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
Text("Description")
|
||||
.font(.subheadline.weight(.medium))
|
||||
.foregroundColor(.secondary)
|
||||
HStack(spacing: 6) {
|
||||
Text("Description")
|
||||
.font(.subheadline.weight(.medium))
|
||||
.foregroundColor(.secondary)
|
||||
Spacer()
|
||||
// A Copy button instead of .textSelection(.enabled): clicking into
|
||||
// a multi-line selectable Text hands it real AppKit first-responder
|
||||
// status, and Escape then gets consumed by that text view's own
|
||||
// cancelOperation: handling before it ever reaches this modal's
|
||||
// onExitCommand — the beep-instead-of-dismiss bug Rune reported.
|
||||
// infoRow's single-line values keep .textSelection(.enabled); only
|
||||
// this multi-line block reproduced the bug.
|
||||
Button(action: copyDescription) {
|
||||
HStack(spacing: 3) {
|
||||
Image(systemName: showDescriptionCopied ? "checkmark" : "doc.on.doc")
|
||||
.font(.system(size: 11))
|
||||
if showDescriptionCopied {
|
||||
Text("Copied!")
|
||||
.font(.system(size: 11))
|
||||
}
|
||||
}
|
||||
.foregroundColor(showDescriptionCopied ? .green : .secondary)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
Text(desc)
|
||||
.font(.body)
|
||||
.foregroundColor(.primary)
|
||||
.textSelection(.enabled)
|
||||
}
|
||||
.padding(.leading, 4)
|
||||
}
|
||||
@@ -212,6 +234,29 @@ struct ModelInfoView: View {
|
||||
.padding(.vertical, 12)
|
||||
}
|
||||
.frame(minWidth: 550, idealWidth: 650, minHeight: 550, idealHeight: 750)
|
||||
// Nearly every value in this modal has .textSelection(.enabled) (infoRow's value text,
|
||||
// the description). Once one of those has text-selection focus, Escape can get
|
||||
// intercepted by AppKit's text-selection machinery instead of reaching the close
|
||||
// button's .keyboardShortcut(.escape) — no action is bound there, so it just beeps
|
||||
// instead of dismissing. onExitCommand is macOS's dedicated hook for the "Escape/Cancel"
|
||||
// user command and fires regardless of which child currently holds focus, so it's a more
|
||||
// reliable place to handle this than a single button's keyboardShortcut alone.
|
||||
.onExitCommand { dismiss() }
|
||||
}
|
||||
|
||||
private func copyDescription() {
|
||||
guard let desc = model.description else { return }
|
||||
let pasteboard = NSPasteboard.general
|
||||
pasteboard.clearContents()
|
||||
pasteboard.setString(desc, forType: .string)
|
||||
withAnimation {
|
||||
showDescriptionCopied = true
|
||||
}
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
|
||||
withAnimation {
|
||||
showDescriptionCopied = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Layout Helpers
|
||||
|
||||
Reference in New Issue
Block a user