fix(autocomplete): ignore pointer events when the clear button is hidden (#3000)

* fix(autocomplete): hide clear button with `visibility: hidden`

* fix(autocomplete): clear button pointer-events

* refactor(autocomplete): improve keyboard reopen issue on mobile

* chore: add changeset

* refactor(autocomplete): apply chain and add type to e

---------

Co-authored-by: WK Wong <wingkwong.code@gmail.com>
This commit is contained in:
chirokas 2024-09-05 17:24:15 +08:00 committed by GitHub
parent 19c331be75
commit 81da063d6a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 30 additions and 26 deletions

View File

@ -0,0 +1,6 @@
---
"@nextui-org/autocomplete": patch
"@nextui-org/theme": patch
---
Improve clear button pointer events, keyboard reopen issue on mobile

View File

@ -7,7 +7,7 @@ import {autocomplete} from "@nextui-org/theme";
import {useFilter} from "@react-aria/i18n";
import {FilterFn, useComboBoxState} from "@react-stately/combobox";
import {ReactRef, useDOMRef} from "@nextui-org/react-utils";
import {ReactNode, useCallback, useEffect, useMemo, useRef} from "react";
import {ReactNode, useEffect, useMemo, useRef} from "react";
import {ComboBoxProps} from "@react-types/combobox";
import {PopoverProps} from "@nextui-org/popover";
import {ListboxProps} from "@nextui-org/listbox";
@ -318,10 +318,7 @@ export function useAutocomplete<T extends object>(originalProps: UseAutocomplete
}, [inputRef.current]);
useEffect(() => {
// set input focus
if (isOpen) {
onFocus(true);
// apply the same with to the popover as the select
if (popoverRef.current && inputWrapperRef.current) {
let rect = inputWrapperRef.current.getBoundingClientRect();
@ -361,19 +358,6 @@ export function useAutocomplete<T extends object>(originalProps: UseAutocomplete
[objectToDeps(variantProps), isClearable, disableAnimation, className],
);
const onClear = useCallback(() => {
state.setInputValue("");
state.setSelectedKey(null);
}, [state]);
const onFocus = useCallback(
(isFocused: boolean) => {
inputRef.current?.focus();
state.setFocused(isFocused);
},
[state, inputRef],
);
const getBaseProps: PropGetter = () => ({
"data-invalid": dataAttr(isInvalid),
"data-open": dataAttr(state.isOpen),
@ -394,19 +378,23 @@ export function useAutocomplete<T extends object>(originalProps: UseAutocomplete
({
...mergeProps(buttonProps, slotsProps.clearButtonProps),
// disable original focus and state toggle from react aria
onPressStart: () => {},
onPressStart: () => {
// this is in PressStart for mobile so that touching the clear button doesn't remove focus from
// the input and close the keyboard
inputRef.current?.focus();
},
onPress: (e: PressEvent) => {
slotsProps.clearButtonProps?.onPress?.(e);
if (state.selectedItem) {
onClear();
state.setInputValue("");
state.setSelectedKey(null);
} else {
if (allowsCustomValue) {
state.setInputValue("");
state.close();
}
}
inputRef?.current?.focus();
},
"data-visible": !!state.selectedItem || state.inputValue?.length > 0,
className: slots.clearButton({
@ -488,13 +476,19 @@ export function useAutocomplete<T extends object>(originalProps: UseAutocomplete
className: slots.endContentWrapper({
class: clsx(classNames?.endContentWrapper, props?.className),
}),
onClick: (e) => {
const inputFocused = inputRef.current === document.activeElement;
if (!inputFocused && !state.isFocused && e.currentTarget === e.target) {
onFocus(true);
onPointerDown: chain(props.onPointerDown, (e: React.PointerEvent) => {
if (e.button === 0 && e.currentTarget === e.target) {
inputRef.current?.focus();
}
},
}),
onMouseDown: chain(props.onMouseDown, (e: React.MouseEvent) => {
if (e.button === 0 && e.currentTarget === e.target) {
// Chrome and Firefox on touch Windows devices require mouse down events
// to be canceled in addition to pointer events, or an extra asynchronous
// focus event will be fired.
e.preventDefault();
}
}),
});
return {

View File

@ -14,12 +14,16 @@ const autocomplete = tv({
"translate-x-1",
"cursor-text",
"opacity-0",
"pointer-events-none",
"text-default-500",
"group-data-[invalid=true]:text-danger",
"data-[visible=true]:opacity-100", // on mobile is always visible when there is a value
"data-[visible=true]:pointer-events-auto",
"data-[visible=true]:cursor-pointer",
"sm:data-[visible=true]:opacity-0", // only visible on hover
"sm:data-[visible=true]:pointer-events-none",
"sm:group-data-[hover=true]:data-[visible=true]:opacity-100",
"sm:group-data-[hover=true]:data-[visible=true]:pointer-events-auto",
],
selectorButton: "text-medium",
},