1
+ /* eslint-disable jsx-a11y/no-static-element-interactions */
2
+ /* eslint-disable jsx-a11y/click-events-have-key-events */
1
3
/* eslint-disable react/jsx-pascal-case */
2
4
import * as React from 'react' ;
3
5
@@ -235,26 +237,7 @@ const Combobox: React.FC<RootProps> = (props) => {
235
237
onFilterValueChange = { setFilterValue }
236
238
onVisuallyFocussedItemChange = { setVisuallyFocussedItem }
237
239
>
238
- < FocusScope
239
- // we make sure we're not trapping once it's been closed
240
- // (closed !== unmounted when animating out)
241
- trapped = { open }
242
- onMountAutoFocus = { ( event ) => {
243
- // we prevent open autofocus because we manually focus the selected item
244
- event . preventDefault ( ) ;
245
- } }
246
- onUnmountAutoFocus = { ( event ) => {
247
- trigger ?. focus ( { preventScroll : true } ) ;
248
- /**
249
- * In firefox there's a some kind of selection happening after
250
- * unmounting all of this, so we make sure we clear that.
251
- */
252
- document . getSelection ( ) ?. empty ( ) ;
253
- event . preventDefault ( ) ;
254
- } }
255
- >
256
- { children }
257
- </ FocusScope >
240
+ { children }
258
241
</ ComboboxProvider >
259
242
</ ComboboxProviders >
260
243
) ;
@@ -273,9 +256,76 @@ const ComboboxTrigger = React.forwardRef<ComboboxTriggerElement, TriggerProps>((
273
256
const { ...triggerProps } = props ;
274
257
const context = useComboboxContext ( TRIGGER_NAME ) ;
275
258
259
+ const handleOpen = ( ) => {
260
+ if ( ! context . disabled ) {
261
+ context . onOpenChange ( true ) ;
262
+ }
263
+ } ;
264
+
276
265
return (
277
266
< PopperPrimitive . Anchor asChild >
278
- < div ref = { forwardedRef } data-disabled = { context . disabled ? '' : undefined } { ...triggerProps } />
267
+ < FocusScope
268
+ asChild
269
+ // we make sure we're not trapping once it's been closed
270
+ // (closed !== unmounted when animating out)
271
+ trapped = { context . open }
272
+ onMountAutoFocus = { ( event ) => {
273
+ // we prevent open autofocus because we manually focus the selected item
274
+ event . preventDefault ( ) ;
275
+ } }
276
+ onUnmountAutoFocus = { ( event ) => {
277
+ context . trigger ?. focus ( { preventScroll : true } ) ;
278
+ /**
279
+ * In firefox there's a some kind of selection happening after
280
+ * unmounting all of this, so we make sure we clear that.
281
+ */
282
+ document . getSelection ( ) ?. empty ( ) ;
283
+ event . preventDefault ( ) ;
284
+ } }
285
+ >
286
+ < div
287
+ ref = { forwardedRef }
288
+ data-disabled = { context . disabled ? '' : undefined }
289
+ { ...triggerProps } // Enable compatibility with native label or custom `Label` "click" for Safari:
290
+ onClick = { composeEventHandlers ( triggerProps . onClick , ( ) => {
291
+ // Whilst browsers generally have no issue focusing the trigger when clicking
292
+ // on a label, Safari seems to struggle with the fact that there's no `onClick`.
293
+ // We force `focus` in this case. Note: this doesn't create any other side-effect
294
+ // because we are preventing default in `onPointerDown` so effectively
295
+ // this only runs for a label "click"
296
+ context . trigger ?. focus ( ) ;
297
+ } ) }
298
+ onPointerDown = { composeEventHandlers ( triggerProps . onPointerDown , ( event ) => {
299
+ // prevent implicit pointer capture
300
+ // https://www.w3.org/TR/pointerevents3/#implicit-pointer-capture
301
+ const target = event . target as HTMLElement ;
302
+
303
+ if ( target . hasPointerCapture ( event . pointerId ) ) {
304
+ target . releasePointerCapture ( event . pointerId ) ;
305
+ }
306
+
307
+ /**
308
+ * This has been added to allow events inside the trigger to be easily fired
309
+ * e.g. the clear button or removing a tag
310
+ */
311
+ const buttonTarg = target . closest ( 'button' ) ?? target . closest ( 'div' ) ;
312
+
313
+ if ( buttonTarg !== event . currentTarget ) {
314
+ return ;
315
+ }
316
+
317
+ // only call handler if it's the left button (mousedown gets triggered by all mouse buttons)
318
+ // but not when the control key is pressed (avoiding MacOS right click)
319
+ if ( event . button === 0 && event . ctrlKey === false ) {
320
+ handleOpen ( ) ;
321
+ /**
322
+ * Firefox had issues focussing the input correctly.
323
+ */
324
+ context . trigger ?. focus ( ) ;
325
+ }
326
+ } ) }
327
+ />
328
+ </ FocusScope >
279
329
</ PopperPrimitive . Anchor >
280
330
) ;
281
331
} ) ;
@@ -291,7 +341,7 @@ type ComboboxInputElement = React.ElementRef<'input'>;
291
341
292
342
const ComboxboxTextInput = React . forwardRef < ComboboxInputElement , TextInputProps > ( ( props , forwardedRef ) => {
293
343
const context = useComboboxContext ( INPUT_NAME ) ;
294
- const inputRef = React . useRef < HTMLInputElement > ( null ! ) ;
344
+ const inputRef = React . useRef < HTMLInputElement > ( null ) ;
295
345
const { getItems } = useCollection ( undefined ) ;
296
346
297
347
const { startsWith } = useFilter ( context . locale , { sensitivity : 'base' } ) ;
@@ -330,7 +380,7 @@ const ComboxboxTextInput = React.forwardRef<ComboboxInputElement, TextInputProps
330
380
* If there's a match, we want to select the text after the match.
331
381
*/
332
382
if ( firstItem && ! context . visuallyFocussedItem && characterChangedAtIndex === context . filterValue . length ) {
333
- inputRef . current . setSelectionRange ( context . filterValue . length , context . textValue . length ) ;
383
+ inputRef . current ? .setSelectionRange ( context . filterValue . length , context . textValue . length ) ;
334
384
}
335
385
} ) ;
336
386
@@ -354,26 +404,6 @@ const ComboxboxTextInput = React.forwardRef<ComboboxInputElement, TextInputProps
354
404
value = { context . textValue ?? '' }
355
405
{ ...props }
356
406
ref = { composedRefs }
357
- // Enable compatibility with native label or custom `Label` "click" for Safari:
358
- onClick = { composeEventHandlers ( props . onClick , ( event ) => {
359
- // Whilst browsers generally have no issue focusing the trigger when clicking
360
- // on a label, Safari seems to struggle with the fact that there's no `onClick`.
361
- // We force `focus` in this case. Note: this doesn't create any other side-effect
362
- // because we are preventing default in `onPointerDown` so effectively
363
- // this only runs for a label "click"
364
- event . currentTarget . focus ( ) ;
365
- } ) }
366
- onPointerDown = { composeEventHandlers ( props . onPointerDown , ( event ) => {
367
- // only call handler if it's the left button (mousedown gets triggered by all mouse buttons)
368
- // but not when the control key is pressed (avoiding MacOS right click)
369
- if ( event . button === 0 && event . ctrlKey === false ) {
370
- handleOpen ( ) ;
371
- /**
372
- * Firefox had issues focussing the input correctly.
373
- */
374
- event . currentTarget . focus ( ) ;
375
- }
376
- } ) }
377
407
onKeyDown = { composeEventHandlers ( props . onKeyDown , ( event ) => {
378
408
if ( [ 'ArrowUp' , 'ArrowDown' , 'Home' , 'End' ] . includes ( event . key ) ) {
379
409
setTimeout ( ( ) => {
@@ -406,6 +436,8 @@ const ComboxboxTextInput = React.forwardRef<ComboboxInputElement, TextInputProps
406
436
context . focusFirst ( candidateNodes , getItems ( ) ) ;
407
437
} ) ;
408
438
event . preventDefault ( ) ;
439
+ } else if ( [ 'Tab' ] . includes ( event . key ) && context . open ) {
440
+ event . preventDefault ( ) ;
409
441
} else if ( [ 'Escape' ] . includes ( event . key ) ) {
410
442
if ( context . open ) {
411
443
context . onOpenChange ( false ) ;
0 commit comments