diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b7ecf9fd..0b9ca2464 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- The cell inspector's Set NULL, Set DEFAULT, copy, and SQL-function actions are now in a right-click context menu on each field, so they're reachable by keyboard, Full Keyboard Access, and VoiceOver, not only on hover. (#1490) - VoiceOver reliability: grid selection changes are announced to all assistive technologies (not only when VoiceOver was already on), the drop and truncate dialog toggles describe their effect, and the favorite query editor is labeled. (#1490) - Tab moves keyboard focus between the window panes (sidebar, results, inspector) by rebuilding the key view loop when the window appears. (#1490) - The license activation sheet focuses the key field on open, the SQL review sheet closes with Escape even while the editor has focus, and the integration token sheet focuses its Done button. (#1490) diff --git a/TablePro/Views/RightSidebar/EditableFieldView.swift b/TablePro/Views/RightSidebar/EditableFieldView.swift index 72e12e174..c5e3c0b1d 100644 --- a/TablePro/Views/RightSidebar/EditableFieldView.swift +++ b/TablePro/Views/RightSidebar/EditableFieldView.swift @@ -73,6 +73,22 @@ internal struct FieldDetailView: View { } .labelsHidden() .onHover { isHovered = $0 } + .contextMenu { + if !context.isReadOnly { + FieldMenuContent( + value: context.value.wrappedValue, + columnType: context.columnType, + sqlFunctions: SQLFunctionProvider.functions(for: databaseType), + isPendingNull: isPendingNull, + isPendingDefault: isPendingDefault, + onSetNull: onSetNull, + onSetDefault: onSetDefault, + onSetEmpty: onSetEmpty, + onSetFunction: onSetFunction, + onClear: { context.value.wrappedValue = context.originalValue ?? "" } + ) + } + } } // MARK: - Header diff --git a/TablePro/Views/RightSidebar/FieldEditors/FieldMenuView.swift b/TablePro/Views/RightSidebar/FieldEditors/FieldMenuView.swift index c673be811..73723f76d 100644 --- a/TablePro/Views/RightSidebar/FieldEditors/FieldMenuView.swift +++ b/TablePro/Views/RightSidebar/FieldEditors/FieldMenuView.swift @@ -5,7 +5,9 @@ import SwiftUI -internal struct FieldMenuView: View { +/// The field actions (Set NULL/DEFAULT/EMPTY, copy, SQL functions). Shared by the +/// hover menu button and the field's context menu so both stay in sync. +internal struct FieldMenuContent: View { let value: String let columnType: ColumnType let sqlFunctions: [SQLFunctionProvider.SQLFunction] @@ -18,45 +20,73 @@ internal struct FieldMenuView: View { let onClear: () -> Void var body: some View { - Menu { - Button("Set NULL") { onSetNull() } - Button("Set DEFAULT") { onSetDefault() } - Button("Set EMPTY") { onSetEmpty() } + Button("Set NULL") { onSetNull() } + Button("Set DEFAULT") { onSetDefault() } + Button("Set EMPTY") { onSetEmpty() } - Divider() + Divider() - if columnType.isJsonType { - Button("Pretty Print") { - if let formatted = value.prettyPrintedAsJson() { - ClipboardService.shared.writeText(formatted) - } + if columnType.isJsonType { + Button("Pretty Print") { + if let formatted = value.prettyPrintedAsJson() { + ClipboardService.shared.writeText(formatted) } } + } - if BlobFormattingService.shared.requiresFormatting(columnType: columnType) { - Button("Copy as Hex") { - if let hex = BlobFormattingService.shared.format(value, for: .detail) { - ClipboardService.shared.writeText(hex) - } + if BlobFormattingService.shared.requiresFormatting(columnType: columnType) { + Button("Copy as Hex") { + if let hex = BlobFormattingService.shared.format(value, for: .detail) { + ClipboardService.shared.writeText(hex) } } + } + + Button("Copy Value") { + ClipboardService.shared.writeText(value) + } - Button("Copy Value") { - ClipboardService.shared.writeText(value) + Divider() + + Menu("SQL Functions") { + ForEach(sqlFunctions, id: \.expression) { function in + Button(function.label) { onSetFunction(function.expression) } } + } + if isPendingNull || isPendingDefault { Divider() + Button("Clear") { onClear() } + } + } +} - Menu("SQL Functions") { - ForEach(sqlFunctions, id: \.expression) { function in - Button(function.label) { onSetFunction(function.expression) } - } - } +internal struct FieldMenuView: View { + let value: String + let columnType: ColumnType + let sqlFunctions: [SQLFunctionProvider.SQLFunction] + let isPendingNull: Bool + let isPendingDefault: Bool + let onSetNull: () -> Void + let onSetDefault: () -> Void + let onSetEmpty: () -> Void + let onSetFunction: (String) -> Void + let onClear: () -> Void - if isPendingNull || isPendingDefault { - Divider() - Button("Clear") { onClear() } - } + var body: some View { + Menu { + FieldMenuContent( + value: value, + columnType: columnType, + sqlFunctions: sqlFunctions, + isPendingNull: isPendingNull, + isPendingDefault: isPendingDefault, + onSetNull: onSetNull, + onSetDefault: onSetDefault, + onSetEmpty: onSetEmpty, + onSetFunction: onSetFunction, + onClear: onClear + ) } label: { Image(systemName: "chevron.down") .font(.caption)