@@ -27,6 +27,8 @@ import {clsx} from 'clsx'
27
27
import { heightMap } from '../Overlay/Overlay'
28
28
import { debounce } from '@github/mini-throttle'
29
29
30
+ type Gesture = 'anchor-click' | 'anchor-key-press' | 'click-outside' | 'escape' | 'selection'
31
+
30
32
// we add a delay so that it does not interrupt default screen reader announcement and queues after it
31
33
const SHORT_DELAY_MS = 500
32
34
const LONG_DELAY_MS = 1000
@@ -72,10 +74,8 @@ interface SelectPanelBaseProps {
72
74
// TODO: Make `title` required in the next major version
73
75
title ?: string | React . ReactElement
74
76
subtitle ?: string | React . ReactElement
75
- onOpenChange : (
76
- open : boolean ,
77
- gesture : 'anchor-click' | 'anchor-key-press' | 'click-outside' | 'escape' | 'selection' ,
78
- ) => void
77
+ open ?: boolean
78
+ onOpenChange ?: ( open : boolean , gesture : Gesture ) => void
79
79
placeholder ?: string
80
80
// TODO: Make `inputLabel` required in next major version
81
81
inputLabel ?: string
@@ -97,7 +97,7 @@ interface SelectPanelBaseProps {
97
97
98
98
export type SelectPanelProps = SelectPanelBaseProps &
99
99
Omit < FilteredActionListProps , 'selectionVariant' > &
100
- Pick < AnchoredOverlayProps , 'open' | ' height' | 'width' > &
100
+ Pick < AnchoredOverlayProps , 'height' | 'width' > &
101
101
AnchoredOverlayWrapperAnchorProps &
102
102
( SelectPanelSingleSelection | SelectPanelMultiSelection )
103
103
@@ -122,9 +122,28 @@ const doesItemsIncludeItem = (items: ItemInput[], item: ItemInput) => {
122
122
return items . some ( i => areItemsEqual ( i , item ) )
123
123
}
124
124
125
+ // We can't use the `useProvidedStateOrCreate` hook here because we need to handle the gesture
126
+ // separately from the open state. So, we have to use a separate state and a callback to update
127
+ // the open state.
128
+ function useProvidedOnOpenOrCreate (
129
+ externalOpen : boolean | undefined ,
130
+ externalOnOpenChange : ( ( open : boolean , gesture : Gesture ) => void ) | undefined ,
131
+ ) {
132
+ const [ internalOpen , setInternalOpen ] = useState < boolean > ( false )
133
+ const open = externalOpen ?? internalOpen
134
+ const onOpenChange = useCallback (
135
+ ( newOpen : boolean , gesture : Gesture ) => {
136
+ setInternalOpen ( newOpen )
137
+ if ( externalOnOpenChange ) externalOnOpenChange ( open , gesture )
138
+ } ,
139
+ [ open , externalOnOpenChange ] ,
140
+ )
141
+ return [ open , onOpenChange ] as const
142
+ }
143
+
125
144
export function SelectPanel ( {
126
- open,
127
- onOpenChange,
145
+ open : externalOpen ,
146
+ onOpenChange : externalOnOpenChange ,
128
147
renderAnchor = props => {
129
148
const { children, ...rest } = props
130
149
return (
@@ -183,6 +202,8 @@ export function SelectPanel({
183
202
[ needsNoItemsAnnouncement ] ,
184
203
)
185
204
205
+ const [ open , onOpenChange ] = useProvidedOnOpenOrCreate ( externalOpen , externalOnOpenChange )
206
+
186
207
const onInputRefChanged = useCallback (
187
208
( ref : React . RefObject < HTMLInputElement > ) => {
188
209
setInputRef ( ref )
@@ -307,9 +328,8 @@ export function SelectPanel({
307
328
[ onOpenChange ] ,
308
329
)
309
330
const onClose = useCallback (
310
- ( gesture : Parameters < Exclude < AnchoredOverlayProps [ 'onClose' ] , undefined > > [ 0 ] | 'selection' | 'escape' ) => {
311
- onOpenChange ( false , gesture )
312
- } ,
331
+ ( gesture : Parameters < Exclude < AnchoredOverlayProps [ 'onClose' ] , undefined > > [ 0 ] | 'selection' | 'escape' ) =>
332
+ onOpenChange ( false , gesture ) ,
313
333
[ onOpenChange ] ,
314
334
)
315
335
0 commit comments