@@ -2,11 +2,22 @@ import * as React from 'react';
22import { useStableCallback } from '@base-ui/utils/useStableCallback' ;
33import { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect' ;
44import { useTimeout } from '@base-ui/utils/useTimeout' ;
5- import { contains , stopEvent } from '../utils' ;
5+ import { contains } from '../utils' ;
66
77import type { ElementProps , FloatingContext , FloatingRootContext } from '../types' ;
88import { EMPTY_ARRAY } from '../../utils/constants' ;
99
10+ // Track handled native events without mutating them so ancestors can skip typeahead.
11+ const handledTypeaheadEvents = new WeakSet < Event > ( ) ;
12+
13+ function isTypeaheadEventHandled ( event : React . KeyboardEvent ) {
14+ return handledTypeaheadEvents . has ( event . nativeEvent ) ;
15+ }
16+
17+ function markTypeaheadEventHandled ( event : React . KeyboardEvent ) {
18+ handledTypeaheadEvents . add ( event . nativeEvent ) ;
19+ }
20+
1021export interface UseTypeaheadProps {
1122 /**
1223 * A ref which contains an array of strings whose indices match the HTML
@@ -45,6 +56,11 @@ export interface UseTypeaheadProps {
4556 * @default 750
4657 */
4758 resetMs ?: number | undefined ;
59+ /**
60+ * Whether to stop event propagation after typeahead handles the key.
61+ * @default true
62+ */
63+ stopPropagation ?: boolean | undefined ;
4864 /**
4965 * An array of keys to ignore when typing.
5066 * @default []
@@ -77,6 +93,7 @@ export function useTypeahead(
7793 enabled = true ,
7894 findMatch = null ,
7995 resetMs = 750 ,
96+ stopPropagation = true ,
8097 ignoreKeys = EMPTY_ARRAY ,
8198 selectedIndex = null ,
8299 } = props ;
@@ -118,7 +135,24 @@ export function useTypeahead(
118135 }
119136 } ) ;
120137
138+ function handleTypeaheadEvent ( event : React . KeyboardEvent ) {
139+ markTypeaheadEventHandled ( event ) ;
140+ event . preventDefault ( ) ;
141+
142+ if ( stopPropagation ) {
143+ event . stopPropagation ( ) ;
144+ }
145+ }
146+
121147 const onKeyDown = useStableCallback ( ( event : React . KeyboardEvent ) => {
148+ // Allow bubbling for group handlers, but avoid parent typeahead re-running.
149+ if ( isTypeaheadEventHandled ( event ) ) {
150+ if ( stopPropagation ) {
151+ event . stopPropagation ( ) ;
152+ }
153+ return ;
154+ }
155+
122156 function getMatchingIndex (
123157 list : Array < string | null > ,
124158 orderedList : Array < string | null > ,
@@ -139,7 +173,7 @@ export function useTypeahead(
139173 if ( getMatchingIndex ( listContent , listContent , stringRef . current ) === - 1 ) {
140174 setTypingChange ( false ) ;
141175 } else if ( event . key === ' ' ) {
142- stopEvent ( event ) ;
176+ handleTypeaheadEvent ( event ) ;
143177 }
144178 }
145179
@@ -157,7 +191,7 @@ export function useTypeahead(
157191 }
158192
159193 if ( open && event . key !== ' ' ) {
160- stopEvent ( event ) ;
194+ handleTypeaheadEvent ( event ) ;
161195 setTypingChange ( true ) ;
162196 }
163197
0 commit comments