mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-08-29 07:27:16 +00:00
Fix dead key monitor: gate on live state, not a value-captured Bool
A synthetic-event harness against the extracted modifier showed the realistic path broken: the panel is hidden when the composer appears, so onChange(of: isActive) ran on the previous render's modifier value and installed a monitor whose closure had captured isActive == false — it passed every key through forever. Arrows/Tab/Escape never worked on a real Mac. Install the monitor once for the view's lifetime and gate each event on an isActive closure that reads the reference-typed model live. Same fix applies to the iOS onKeyPress guards for consistency. Harness now passes all paths including deactivate/reactivate. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
b98df88f1a
commit
90ac3a5b23
@ -82,8 +82,8 @@ struct ContentComposerView: View {
|
||||
#endif
|
||||
.submitLabel(.send)
|
||||
.modifier(AutocompleteKeyboardNavigationModifier(
|
||||
isActive: conversationUIModel.showAutocomplete
|
||||
&& !conversationUIModel.autocompleteSuggestions.isEmpty,
|
||||
isActive: { conversationUIModel.showAutocomplete
|
||||
&& !conversationUIModel.autocompleteSuggestions.isEmpty },
|
||||
onMove: { delta in
|
||||
conversationUIModel.moveAutocompleteSelection(by: delta)
|
||||
},
|
||||
@ -413,7 +413,13 @@ private extension ContentComposerView {
|
||||
/// same reason command suggestions (#1504) use an `NSEvent` local monitor.
|
||||
/// Mentions follow that mechanism on macOS and keep `.onKeyPress` for iOS 17+.
|
||||
private struct AutocompleteKeyboardNavigationModifier: ViewModifier {
|
||||
let isActive: Bool
|
||||
/// Live activity check, not a captured Bool. The macOS monitor closure is
|
||||
/// registered once for the view's lifetime; a plain `Bool` would freeze
|
||||
/// the value captured at install time (this is a value type), so a panel
|
||||
/// that opens after the monitor installs would never intercept a key.
|
||||
/// The provider closes over the reference-typed model and reads current
|
||||
/// state on every event.
|
||||
let isActive: () -> Bool
|
||||
let onMove: (Int) -> Void
|
||||
let onAccept: () -> Bool
|
||||
let onDismiss: () -> Void
|
||||
@ -425,36 +431,27 @@ private struct AutocompleteKeyboardNavigationModifier: ViewModifier {
|
||||
func body(content: Content) -> some View {
|
||||
#if os(macOS)
|
||||
content
|
||||
.onChange(of: isActive) { active in
|
||||
if active {
|
||||
installKeyMonitor()
|
||||
} else {
|
||||
removeKeyMonitor()
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
if isActive { installKeyMonitor() }
|
||||
}
|
||||
.onAppear { installKeyMonitor() }
|
||||
.onDisappear { removeKeyMonitor() }
|
||||
#else
|
||||
if #available(iOS 17.0, *) {
|
||||
content
|
||||
.onKeyPress(.upArrow) {
|
||||
guard isActive else { return .ignored }
|
||||
guard isActive() else { return .ignored }
|
||||
onMove(-1)
|
||||
return .handled
|
||||
}
|
||||
.onKeyPress(.downArrow) {
|
||||
guard isActive else { return .ignored }
|
||||
guard isActive() else { return .ignored }
|
||||
onMove(1)
|
||||
return .handled
|
||||
}
|
||||
.onKeyPress(.tab) {
|
||||
guard isActive else { return .ignored }
|
||||
guard isActive() else { return .ignored }
|
||||
return onAccept() ? .handled : .ignored
|
||||
}
|
||||
.onKeyPress(.escape) {
|
||||
guard isActive else { return .ignored }
|
||||
guard isActive() else { return .ignored }
|
||||
onDismiss()
|
||||
return .handled
|
||||
}
|
||||
@ -484,7 +481,7 @@ private struct AutocompleteKeyboardNavigationModifier: ViewModifier {
|
||||
/// consumes the event so return completes instead of sending while the
|
||||
/// list is up. Inactive monitors pass everything through.
|
||||
private func handleKeyDown(_ event: NSEvent) -> NSEvent? {
|
||||
guard isActive,
|
||||
guard isActive(),
|
||||
event.modifierFlags.intersection([.command, .option, .control]).isEmpty else {
|
||||
return event
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user