Skip to content

Commit dadb8c0

Browse files
authored
fix(datagrid): Esc in the filter suggestions or a search field no longer exits fullscreen (#1403) (#1406)
1 parent 115eafa commit dadb8c0

4 files changed

Lines changed: 55 additions & 18 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1515
### Fixed
1616

1717
- Installing or updating a plugin right after updating TablePro now refetches the current plugin list first, so it no longer fails against a stale cached list (the error a restart used to clear). (#1380)
18+
- Pressing Esc to close the Raw SQL filter suggestions, or to clear a search field, no longer also exits fullscreen. (#1403)
1819

1920
## [0.44.0] - 2026-05-23
2021

TablePro/Views/Filter/FilterValueTextField.swift

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,24 @@ struct FilterValueTextField: NSViewRepresentable {
3838
return (ns.replacingCharacters(in: range, with: insertText), caret)
3939
}
4040

41+
enum SuggestionKeyOutcome: Equatable {
42+
case moveSelection(Int)
43+
case accept(submitting: Bool)
44+
case dismiss
45+
case passThrough
46+
}
47+
48+
static func suggestionKeyOutcome(for key: KeyCode?, submitsOnAccept: Bool) -> SuggestionKeyOutcome {
49+
switch key {
50+
case .downArrow: return .moveSelection(1)
51+
case .upArrow: return .moveSelection(-1)
52+
case .return: return .accept(submitting: submitsOnAccept)
53+
case .tab: return .accept(submitting: false)
54+
case .escape: return .dismiss
55+
default: return .passThrough
56+
}
57+
}
58+
4159
func makeNSView(context: Context) -> NSTextField {
4260
let textField = SubstitutionDisabledTextField()
4361
textField.bezelStyle = .roundedBezel
@@ -176,6 +194,10 @@ struct FilterValueTextField: NSViewRepresentable {
176194
text.wrappedValue = textView.string
177195
return true
178196
}
197+
if commandSelector == #selector(NSResponder.cancelOperation(_:)) {
198+
dismissSuggestions()
199+
return true
200+
}
179201
return false
180202
}
181203

@@ -291,25 +313,20 @@ struct FilterValueTextField: NSViewRepresentable {
291313
nsEvent.window?.firstResponder === textField.currentEditor()
292314
else { return nsEvent }
293315

294-
switch nsEvent.semanticKeyCode {
295-
case .downArrow:
296-
self.moveSelection(by: 1)
297-
return nil
298-
case .upArrow:
299-
self.moveSelection(by: -1)
300-
return nil
301-
case .return:
302-
self.acceptCurrentSelection(submitting: self.submitsOnAccept)
303-
return nil
304-
case .tab:
305-
self.acceptCurrentSelection(submitting: false)
306-
return nil
307-
case .escape:
316+
switch FilterValueTextField.suggestionKeyOutcome(
317+
for: nsEvent.semanticKeyCode,
318+
submitsOnAccept: self.submitsOnAccept
319+
) {
320+
case .moveSelection(let delta):
321+
self.moveSelection(by: delta)
322+
case .accept(let submitting):
323+
self.acceptCurrentSelection(submitting: submitting)
324+
case .dismiss:
308325
self.dismissSuggestions()
309-
return nsEvent
310-
default:
326+
case .passThrough:
311327
return nsEvent
312328
}
329+
return nil
313330
}
314331
}
315332
}

TablePro/Views/Sidebar/NativeSearchField.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,8 @@ struct NativeSearchField: NSViewRepresentable {
9898
if !field.stringValue.isEmpty {
9999
field.stringValue = ""
100100
text.wrappedValue = ""
101-
return true
102101
}
103-
return false
102+
return true
104103
}
105104
if commandSelector == #selector(NSResponder.moveUp(_:)), let onMoveUp {
106105
onMoveUp()

TableProTests/Views/Filter/FilterValueTextFieldTests.swift

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,4 +113,24 @@ struct FilterValueTextFieldTests {
113113
)
114114
#expect(result == nil)
115115
}
116+
117+
@Test("Escape dismisses the suggestions and is consumed, not passed through")
118+
func testKeyOutcome_escapeDismisses() {
119+
#expect(FilterValueTextField.suggestionKeyOutcome(for: .escape, submitsOnAccept: true) == .dismiss)
120+
#expect(FilterValueTextField.suggestionKeyOutcome(for: .escape, submitsOnAccept: false) == .dismiss)
121+
}
122+
123+
@Test("Arrow and accept keys map to consuming outcomes")
124+
func testKeyOutcome_navigationAndAccept() {
125+
#expect(FilterValueTextField.suggestionKeyOutcome(for: .downArrow, submitsOnAccept: false) == .moveSelection(1))
126+
#expect(FilterValueTextField.suggestionKeyOutcome(for: .upArrow, submitsOnAccept: false) == .moveSelection(-1))
127+
#expect(FilterValueTextField.suggestionKeyOutcome(for: .return, submitsOnAccept: true) == .accept(submitting: true))
128+
#expect(FilterValueTextField.suggestionKeyOutcome(for: .tab, submitsOnAccept: true) == .accept(submitting: false))
129+
}
130+
131+
@Test("Unhandled keys pass through unchanged")
132+
func testKeyOutcome_passThrough() {
133+
#expect(FilterValueTextField.suggestionKeyOutcome(for: .space, submitsOnAccept: true) == .passThrough)
134+
#expect(FilterValueTextField.suggestionKeyOutcome(for: nil, submitsOnAccept: true) == .passThrough)
135+
}
116136
}

0 commit comments

Comments
 (0)