Skip to content

Commit e3e601c

Browse files
fix(SelectPanel): do not bubble up keyboard events (#6900)
Co-authored-by: Tyler Jones <[email protected]>
1 parent e5753f0 commit e3e601c

File tree

3 files changed

+33
-5
lines changed

3 files changed

+33
-5
lines changed

.changeset/itchy-readers-yell.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@primer/react": patch
3+
---
4+
5+
fix(SelectPanel): do not bubble up keyboard events

packages/react/src/SelectPanel/SelectPanel.tsx

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {SearchIcon, TriangleDownIcon, XIcon, type IconProps} from '@primer/octicons-react'
2-
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'
2+
import React, {useCallback, useEffect, useMemo, useRef, useState, type KeyboardEventHandler} from 'react'
33
import type {AnchoredOverlayProps} from '../AnchoredOverlay'
44
import {AnchoredOverlay} from '../AnchoredOverlay'
55
import type {AnchoredOverlayWrapperAnchorProps} from '../AnchoredOverlay/AnchoredOverlay'
@@ -27,6 +27,7 @@ import {debounce} from '@github/mini-throttle'
2727
import {useResponsiveValue} from '../hooks/useResponsiveValue'
2828
import type {ButtonProps, LinkButtonProps} from '../Button/types'
2929
import {Banner} from '../Banner'
30+
import {isAlphabetKey} from '../hooks/useMnemonics'
3031

3132
// we add a delay so that it does not interrupt default screen reader announcement and queues after it
3233
const SHORT_DELAY_MS = 500
@@ -215,6 +216,7 @@ function Panel({
215216
const usingFullScreenOnNarrow = disableFullscreenOnNarrow ? false : featureFlagFullScreenOnNarrow
216217
const shouldOrderSelectedFirst =
217218
useFeatureFlag('primer_react_select_panel_order_selected_at_top') && showSelectedOptionsFirst
219+
const usingRemoveActiveDescendant = useFeatureFlag('primer_react_select_panel_remove_active_descendant')
218220

219221
// Single select modals work differently, they have an intermediate state where the user has selected an item but
220222
// has not yet confirmed the selection. This is the only time the user can cancel the selection.
@@ -741,6 +743,26 @@ function Panel({
741743
'anchored',
742744
)
743745

746+
const preventBubbling =
747+
(customOnKeyDown: KeyboardEventHandler<HTMLDivElement> | undefined) =>
748+
(event: React.KeyboardEvent<HTMLDivElement>) => {
749+
// skip if a TextInput has focus
750+
customOnKeyDown?.(event)
751+
752+
const activeElement = document.activeElement as HTMLElement
753+
if (activeElement.tagName === 'INPUT' || activeElement.tagName === 'TEXTAREA') return
754+
755+
// skip if used with modifier to preserve shortcuts like ⌘ + F
756+
const hasModifier = event.ctrlKey || event.altKey || event.metaKey
757+
if (hasModifier) return
758+
759+
// skip if it's not a alphabet key
760+
if (!isAlphabetKey(event.nativeEvent as KeyboardEvent)) return
761+
762+
// if this is a typeahead event, don't propagate outside of menu
763+
event.stopPropagation()
764+
}
765+
744766
return (
745767
<>
746768
<AnchoredOverlay
@@ -773,6 +795,7 @@ function Panel({
773795
}
774796
: {}),
775797
} as React.CSSProperties,
798+
onKeyDown: usingRemoveActiveDescendant ? preventBubbling(overlayProps?.onKeyDown) : overlayProps?.onKeyDown,
776799
}}
777800
focusTrapSettings={focusTrapSettings}
778801
focusZoneSettings={focusZoneSettings}

packages/react/src/hooks/useMnemonics.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,9 @@ export const useMnemonics = (open: boolean, providedRef?: React.RefObject<HTMLEl
8282
[open, containerRef],
8383
)
8484

85-
const isAlphabetKey = (event: KeyboardEvent) => {
86-
return event.key.length === 1 && /[a-z\d]/i.test(event.key)
87-
}
88-
8985
return {containerRef}
9086
}
87+
88+
export const isAlphabetKey = (event: KeyboardEvent) => {
89+
return event.key.length === 1 && /[a-z\d]/i.test(event.key)
90+
}

0 commit comments

Comments
 (0)