@@ -49,6 +49,7 @@ interface StateDefinition {
4949 labelRef : MutableRefObject < HTMLLabelElement | null >
5050 buttonRef : MutableRefObject < HTMLButtonElement | null >
5151 optionsRef : MutableRefObject < HTMLUListElement | null >
52+ disabled : boolean
5253 options : { id : string ; dataRef : ListboxOptionDataRef } [ ]
5354 searchQuery : string
5455 activeOptionIndex : number | null
@@ -58,6 +59,8 @@ enum ActionTypes {
5859 OpenListbox ,
5960 CloseListbox ,
6061
62+ SetDisabled ,
63+
6164 GoToOption ,
6265 Search ,
6366 ClearSearch ,
@@ -69,6 +72,7 @@ enum ActionTypes {
6972type Actions =
7073 | { type : ActionTypes . CloseListbox }
7174 | { type : ActionTypes . OpenListbox }
75+ | { type : ActionTypes . SetDisabled ; disabled : boolean }
7276 | { type : ActionTypes . GoToOption ; focus : Focus . Specific ; id : string }
7377 | { type : ActionTypes . GoToOption ; focus : Exclude < Focus , Focus . Specific > }
7478 | { type : ActionTypes . Search ; value : string }
@@ -82,13 +86,24 @@ let reducers: {
8286 action : Extract < Actions , { type : P } >
8387 ) => StateDefinition
8488} = {
85- [ ActionTypes . CloseListbox ] : state => ( {
86- ...state ,
87- activeOptionIndex : null ,
88- listboxState : ListboxStates . Closed ,
89- } ) ,
90- [ ActionTypes . OpenListbox ] : state => ( { ...state , listboxState : ListboxStates . Open } ) ,
91- [ ActionTypes . GoToOption ] : ( state , action ) => {
89+ [ ActionTypes . CloseListbox ] ( state ) {
90+ if ( state . disabled ) return state
91+ if ( state . listboxState === ListboxStates . Closed ) return state
92+ return { ...state , activeOptionIndex : null , listboxState : ListboxStates . Closed }
93+ } ,
94+ [ ActionTypes . OpenListbox ] ( state ) {
95+ if ( state . disabled ) return state
96+ if ( state . listboxState === ListboxStates . Open ) return state
97+ return { ...state , listboxState : ListboxStates . Open }
98+ } ,
99+ [ ActionTypes . SetDisabled ] ( state , action ) {
100+ if ( state . disabled === action . disabled ) return state
101+ return { ...state , disabled : action . disabled }
102+ } ,
103+ [ ActionTypes . GoToOption ] ( state , action ) {
104+ if ( state . disabled ) return state
105+ if ( state . listboxState === ListboxStates . Closed ) return state
106+
92107 let activeOptionIndex = calculateActiveIndex ( action , {
93108 resolveItems : ( ) => state . options ,
94109 resolveActiveIndex : ( ) => state . activeOptionIndex ,
@@ -100,6 +115,9 @@ let reducers: {
100115 return { ...state , searchQuery : '' , activeOptionIndex }
101116 } ,
102117 [ ActionTypes . Search ] : ( state , action ) => {
118+ if ( state . disabled ) return state
119+ if ( state . listboxState === ListboxStates . Closed ) return state
120+
103121 let searchQuery = state . searchQuery + action . value
104122 let match = state . options . findIndex (
105123 option =>
@@ -110,7 +128,12 @@ let reducers: {
110128 if ( match === - 1 || match === state . activeOptionIndex ) return { ...state , searchQuery }
111129 return { ...state , searchQuery, activeOptionIndex : match }
112130 } ,
113- [ ActionTypes . ClearSearch ] : state => ( { ...state , searchQuery : '' } ) ,
131+ [ ActionTypes . ClearSearch ] ( state ) {
132+ if ( state . disabled ) return state
133+ if ( state . listboxState === ListboxStates . Closed ) return state
134+ if ( state . searchQuery === '' ) return state
135+ return { ...state , searchQuery : '' }
136+ } ,
114137 [ ActionTypes . RegisterOption ] : ( state , action ) => ( {
115138 ...state ,
116139 options : [ ...state . options , { id : action . id , dataRef : action . dataRef } ] ,
@@ -161,22 +184,25 @@ function stateReducer(state: StateDefinition, action: Actions) {
161184let DEFAULT_LISTBOX_TAG = Fragment
162185interface ListboxRenderPropArg {
163186 open : boolean
187+ disabled : boolean
164188}
165189
166190export function Listbox < TTag extends ElementType = typeof DEFAULT_LISTBOX_TAG , TType = string > (
167191 props : Props < TTag , ListboxRenderPropArg , 'value' | 'onChange' > & {
168192 value : TType
169193 onChange ( value : TType ) : void
194+ disabled ?: boolean
170195 }
171196) {
172- let { value, onChange, ...passThroughProps } = props
197+ let { value, onChange, disabled = false , ...passThroughProps } = props
173198 let d = useDisposables ( )
174199 let reducerBag = useReducer ( stateReducer , {
175200 listboxState : ListboxStates . Closed ,
176201 propsRef : { current : { value, onChange } } ,
177202 labelRef : createRef ( ) ,
178203 buttonRef : createRef ( ) ,
179204 optionsRef : createRef ( ) ,
205+ disabled,
180206 options : [ ] ,
181207 searchQuery : '' ,
182208 activeOptionIndex : null ,
@@ -189,6 +215,7 @@ export function Listbox<TTag extends ElementType = typeof DEFAULT_LISTBOX_TAG, T
189215 useIsoMorphicEffect ( ( ) => {
190216 propsRef . current . onChange = onChange
191217 } , [ onChange , propsRef ] )
218+ useIsoMorphicEffect ( ( ) => dispatch ( { type : ActionTypes . SetDisabled , disabled } ) , [ disabled ] )
192219
193220 useEffect ( ( ) => {
194221 function handler ( event : MouseEvent ) {
@@ -208,8 +235,8 @@ export function Listbox<TTag extends ElementType = typeof DEFAULT_LISTBOX_TAG, T
208235 } , [ listboxState , optionsRef , buttonRef , d , dispatch ] )
209236
210237 let propsBag = useMemo < ListboxRenderPropArg > (
211- ( ) => ( { open : listboxState === ListboxStates . Open } ) ,
212- [ listboxState ]
238+ ( ) => ( { open : listboxState === ListboxStates . Open , disabled } ) ,
239+ [ listboxState , disabled ]
213240 )
214241
215242 return (
@@ -224,6 +251,7 @@ export function Listbox<TTag extends ElementType = typeof DEFAULT_LISTBOX_TAG, T
224251let DEFAULT_BUTTON_TAG = 'button' as const
225252interface ButtonRenderPropArg {
226253 open : boolean
254+ disabled : boolean
227255}
228256type ButtonPropsWeControl =
229257 | 'id'
@@ -232,6 +260,7 @@ type ButtonPropsWeControl =
232260 | 'aria-controls'
233261 | 'aria-expanded'
234262 | 'aria-labelledby'
263+ | 'disabled'
235264 | 'onKeyDown'
236265 | 'onClick'
237266
@@ -279,7 +308,6 @@ let Button = forwardRefWithAs(function Button<TTag extends ElementType = typeof
279308 let handleClick = useCallback (
280309 ( event : ReactMouseEvent ) => {
281310 if ( isDisabledReactIssue7711 ( event . currentTarget ) ) return event . preventDefault ( )
282- if ( props . disabled ) return
283311 if ( state . listboxState === ListboxStates . Open ) {
284312 dispatch ( { type : ActionTypes . CloseListbox } )
285313 d . nextFrame ( ( ) => state . buttonRef . current ?. focus ( { preventScroll : true } ) )
@@ -289,7 +317,7 @@ let Button = forwardRefWithAs(function Button<TTag extends ElementType = typeof
289317 d . nextFrame ( ( ) => state . optionsRef . current ?. focus ( { preventScroll : true } ) )
290318 }
291319 } ,
292- [ dispatch , d , state , props . disabled ]
320+ [ dispatch , d , state ]
293321 )
294322
295323 let labelledby = useComputed ( ( ) => {
@@ -298,7 +326,7 @@ let Button = forwardRefWithAs(function Button<TTag extends ElementType = typeof
298326 } , [ state . labelRef . current , id ] )
299327
300328 let propsBag = useMemo < ButtonRenderPropArg > (
301- ( ) => ( { open : state . listboxState === ListboxStates . Open } ) ,
329+ ( ) => ( { open : state . listboxState === ListboxStates . Open , disabled : state . disabled } ) ,
302330 [ state ]
303331 )
304332 let passthroughProps = props
@@ -310,6 +338,7 @@ let Button = forwardRefWithAs(function Button<TTag extends ElementType = typeof
310338 'aria-controls' : state . optionsRef . current ?. id ,
311339 'aria-expanded' : state . listboxState === ListboxStates . Open ? true : undefined ,
312340 'aria-labelledby' : labelledby ,
341+ disabled : state . disabled ,
313342 onKeyDown : handleKeyDown ,
314343 onClick : handleClick ,
315344 }
@@ -322,6 +351,7 @@ let Button = forwardRefWithAs(function Button<TTag extends ElementType = typeof
322351let DEFAULT_LABEL_TAG = 'label' as const
323352interface LabelRenderPropArg {
324353 open : boolean
354+ disabled : boolean
325355}
326356type LabelPropsWeControl = 'id' | 'ref' | 'onClick'
327357
@@ -336,7 +366,7 @@ function Label<TTag extends ElementType = typeof DEFAULT_LABEL_TAG>(
336366 ] )
337367
338368 let propsBag = useMemo < OptionsRenderPropArg > (
339- ( ) => ( { open : state . listboxState === ListboxStates . Open } ) ,
369+ ( ) => ( { open : state . listboxState === ListboxStates . Open , disabled : state . disabled } ) ,
340370 [ state ]
341371 )
342372 let propsWeControl = { ref : state . labelRef , id, onClick : handleClick }
0 commit comments