diff --git a/lib/focus/focusable_chip_mixin.dart b/lib/focus/focusable_chip_mixin.dart index 62df8864..bff3f91b 100644 --- a/lib/focus/focusable_chip_mixin.dart +++ b/lib/focus/focusable_chip_mixin.dart @@ -152,9 +152,8 @@ mixin FocusableChipStateMixin on State { _isSelectKeyDown = false; return KeyEventResult.handled; } - } else if (event.isActionable && callbacks.onSelect != null) { - callbacks.onSelect!(); - return KeyEventResult.handled; + } else if (callbacks.onSelect != null) { + return handleOneShotSelect(event, callbacks.onSelect!); } } diff --git a/lib/focus/focusable_wrapper.dart b/lib/focus/focusable_wrapper.dart index e9ed636f..bc78bb25 100644 --- a/lib/focus/focusable_wrapper.dart +++ b/lib/focus/focusable_wrapper.dart @@ -366,12 +366,8 @@ class _FocusableWrapperState extends State with SingleTickerPr _isSelectKeyDown = false; return KeyEventResult.handled; } - } else { - // Simple select handling without long-press - if (event is KeyDownEvent) { - widget.onSelect?.call(); - return KeyEventResult.handled; - } + } else if (widget.onSelect != null) { + return handleOneShotSelect(event, widget.onSelect!); } } diff --git a/lib/focus/key_event_utils.dart b/lib/focus/key_event_utils.dart index 156e70ac..1df5fb6a 100644 --- a/lib/focus/key_event_utils.dart +++ b/lib/focus/key_event_utils.dart @@ -87,6 +87,18 @@ KeyEventResult handleBackKeyNavigation(BuildContext context, KeyEvent event, return handleBackKeyAction(event, () => Navigator.pop(context, result)); } +/// Handles a select key as a one-shot button activation. +/// +/// Fires [onActivate] on the initial [KeyDownEvent] only. +/// Consumes all select key events (down, repeat, up) to prevent +/// unhandled events from reaching platform-level handling. +/// Returns [KeyEventResult.ignored] for non-select keys. +KeyEventResult handleOneShotSelect(KeyEvent event, VoidCallback onActivate) { + if (!event.logicalKey.isSelectKey) return KeyEventResult.ignored; + if (event is KeyDownEvent) onActivate(); + return KeyEventResult.handled; +} + /// Creates a [FocusOnKeyEventCallback] that dispatches d-pad / arrow keys to /// the provided directional callbacks. /// @@ -94,7 +106,8 @@ KeyEventResult handleBackKeyNavigation(BuildContext context, KeyEvent event, /// (passed through to the framework). Directions mapped to a callback /// automatically return [KeyEventResult.handled]. /// -/// Only [KeyDownEvent] and [KeyRepeatEvent] are handled (via [isActionable]). +/// Directional keys repeat on [KeyRepeatEvent] (via [isActionable]). +/// Select is one-shot: fires on [KeyDownEvent] only, consumes repeat and up. /// /// ```dart /// Focus( @@ -115,6 +128,13 @@ FocusOnKeyEventCallback dpadKeyHandler({ VoidCallback? onSelect, }) { return (FocusNode _, KeyEvent event) { + // Select: one-shot activation (no repeat), must run before isActionable + // filter so KeyUpEvent is also consumed. + if (onSelect != null) { + final result = handleOneShotSelect(event, onSelect); + if (result != KeyEventResult.ignored) return result; + } + if (!event.isActionable) return KeyEventResult.ignored; final key = event.logicalKey; @@ -134,10 +154,6 @@ FocusOnKeyEventCallback dpadKeyHandler({ onRight(); return KeyEventResult.handled; } - if (key.isSelectKey && onSelect != null) { - onSelect(); - return KeyEventResult.handled; - } return KeyEventResult.ignored; };