From c203bcb84a44bee0847eb845f439db2cdce5f008 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sun, 30 Jun 2024 21:07:48 +0800 Subject: [PATCH 01/26] feat(autocomplete): exclude name in input and add getHiddenInputProps --- .../autocomplete/src/use-autocomplete.ts | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/packages/components/autocomplete/src/use-autocomplete.ts b/packages/components/autocomplete/src/use-autocomplete.ts index cf8482948e..df2af8ea3e 100644 --- a/packages/components/autocomplete/src/use-autocomplete.ts +++ b/packages/components/autocomplete/src/use-autocomplete.ts @@ -415,8 +415,11 @@ export function useAutocomplete(originalProps: UseAutocomplete }), } as ButtonProps); - const getInputProps = () => - ({ + const getInputProps = () => { + // TODO(wkw): add comments + delete inputProps["name"]; + + return { ...otherProps, ...inputProps, ...slotsProps.inputProps, @@ -427,7 +430,8 @@ export function useAutocomplete(originalProps: UseAutocomplete ? errorMessage({isInvalid, validationErrors, validationDetails}) : errorMessage || validationErrors?.join(" "), onClick: chain(slotsProps.inputProps.onClick, otherProps.onClick), - } as unknown as InputProps); + } as unknown as InputProps; + }; const getListBoxProps = () => ({ @@ -495,6 +499,24 @@ export function useAutocomplete(originalProps: UseAutocomplete }, }); + const getHiddenInputProps = useCallback( + (props = {}) => ({ + state, + name: originalProps?.name, + isRequired: originalProps?.isRequired, + autoComplete: originalProps?.autoComplete, + isDisabled: originalProps?.isDisabled, + ...props, + }), + [ + state, + originalProps?.name, + originalProps?.autoComplete, + originalProps?.autoComplete, + originalProps?.isDisabled, + ], + ); + return { Component, inputRef, @@ -519,6 +541,7 @@ export function useAutocomplete(originalProps: UseAutocomplete getSelectorButtonProps, getListBoxWrapperProps, getEndContentWrapperProps, + getHiddenInputProps, }; } From 049c1bdd94de92ab3de6cf5e60ef8488c613c08f Mon Sep 17 00:00:00 2001 From: WK Wong Date: Thu, 4 Jul 2024 18:57:27 +0800 Subject: [PATCH 02/26] fix(autocomplete): revise getInputProps logic --- .../components/autocomplete/src/use-autocomplete.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/components/autocomplete/src/use-autocomplete.ts b/packages/components/autocomplete/src/use-autocomplete.ts index df2af8ea3e..a64b45db76 100644 --- a/packages/components/autocomplete/src/use-autocomplete.ts +++ b/packages/components/autocomplete/src/use-autocomplete.ts @@ -416,13 +416,14 @@ export function useAutocomplete(originalProps: UseAutocomplete } as ButtonProps); const getInputProps = () => { - // TODO(wkw): add comments - delete inputProps["name"]; + const props = mergeProps(otherProps, inputProps, slotsProps.inputProps); + + // `name` will be in the hidden input + // so that users can get the value of the input instead of label in form + delete props["name"]; return { - ...otherProps, - ...inputProps, - ...slotsProps.inputProps, + ...props, isInvalid, validationBehavior, errorMessage: @@ -430,7 +431,7 @@ export function useAutocomplete(originalProps: UseAutocomplete ? errorMessage({isInvalid, validationErrors, validationDetails}) : errorMessage || validationErrors?.join(" "), onClick: chain(slotsProps.inputProps.onClick, otherProps.onClick), - } as unknown as InputProps; + } as InputProps; }; const getListBoxProps = () => From cde7cad669f046bda5214bae389aa370fd8cfe2a Mon Sep 17 00:00:00 2001 From: WK Wong Date: Thu, 4 Jul 2024 18:58:21 +0800 Subject: [PATCH 03/26] feat(autocomplete): hidden input --- .../autocomplete/src/hidden-input.tsx | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 packages/components/autocomplete/src/hidden-input.tsx diff --git a/packages/components/autocomplete/src/hidden-input.tsx b/packages/components/autocomplete/src/hidden-input.tsx new file mode 100644 index 0000000000..66ec1c3478 --- /dev/null +++ b/packages/components/autocomplete/src/hidden-input.tsx @@ -0,0 +1,27 @@ +import type {ComboBoxState} from "@react-stately/combobox"; + +import React, {ReactNode} from "react"; + +export interface AriaHiddenInputProps { + /** + * Describes the type of autocomplete functionality the input should provide if any. + * See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#htmlattrdefautocomplete). + */ + autoComplete?: string; + /** The text label for the select. */ + label?: ReactNode; + /** HTML form input name. */ + name?: string; + /** Sets the disabled state of the select and input. */ + isDisabled?: boolean; + /** Whether the select is required. */ + isRequired?: boolean; + /** State for the input. */ + state: ComboBoxState; +} + +export function HiddenInput(props: AriaHiddenInputProps) { + const {state, ...otherProps} = props; + + return ; +} From db335062e031cffed33e478013d88a0c297957eb Mon Sep 17 00:00:00 2001 From: WK Wong Date: Thu, 4 Jul 2024 18:58:39 +0800 Subject: [PATCH 04/26] feat(autocomplete): include HiddenInput in autocomplete --- packages/components/autocomplete/src/autocomplete.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/components/autocomplete/src/autocomplete.tsx b/packages/components/autocomplete/src/autocomplete.tsx index 2eadca4ebd..0961773c64 100644 --- a/packages/components/autocomplete/src/autocomplete.tsx +++ b/packages/components/autocomplete/src/autocomplete.tsx @@ -9,6 +9,7 @@ import {ForwardedRef, ReactElement, Ref} from "react"; import {AnimatePresence} from "framer-motion"; import {UseAutocompleteProps, useAutocomplete} from "./use-autocomplete"; +import {HiddenInput} from "./hidden-input"; interface Props extends UseAutocompleteProps {} @@ -29,6 +30,7 @@ function Autocomplete(props: Props, ref: ForwardedRef({...props, ref}); const popoverContent = isOpen ? ( @@ -43,6 +45,7 @@ function Autocomplete(props: Props, ref: ForwardedRef + Date: Thu, 4 Jul 2024 19:00:20 +0800 Subject: [PATCH 05/26] feat(changeset): add changeset --- .changeset/purple-pillows-beg.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/purple-pillows-beg.md diff --git a/.changeset/purple-pillows-beg.md b/.changeset/purple-pillows-beg.md new file mode 100644 index 0000000000..c322850ca5 --- /dev/null +++ b/.changeset/purple-pillows-beg.md @@ -0,0 +1,5 @@ +--- +"@nextui-org/autocomplete": minor +--- + +return autocomplete value instead of label in form submission (#3353, #3343) From 4144d440b30cc5334267b49c7d70c2880a583d41 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Fri, 5 Jul 2024 21:28:20 +0800 Subject: [PATCH 06/26] fix(autocomplete): set empty string by default for value --- packages/components/autocomplete/src/hidden-input.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/autocomplete/src/hidden-input.tsx b/packages/components/autocomplete/src/hidden-input.tsx index 66ec1c3478..74b14f313c 100644 --- a/packages/components/autocomplete/src/hidden-input.tsx +++ b/packages/components/autocomplete/src/hidden-input.tsx @@ -23,5 +23,5 @@ export interface AriaHiddenInputProps { export function HiddenInput(props: AriaHiddenInputProps) { const {state, ...otherProps} = props; - return ; + return ; } From e95d52868939fe37b963777c0e105e34ed2f4f5e Mon Sep 17 00:00:00 2001 From: WK Wong Date: Fri, 5 Jul 2024 21:29:09 +0800 Subject: [PATCH 07/26] fix(autocomplete): avoid passing custom attribute to DOM --- .../components/autocomplete/src/hidden-input.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/components/autocomplete/src/hidden-input.tsx b/packages/components/autocomplete/src/hidden-input.tsx index 74b14f313c..cafdc9c1e6 100644 --- a/packages/components/autocomplete/src/hidden-input.tsx +++ b/packages/components/autocomplete/src/hidden-input.tsx @@ -21,7 +21,15 @@ export interface AriaHiddenInputProps { } export function HiddenInput(props: AriaHiddenInputProps) { - const {state, ...otherProps} = props; + const {state, isDisabled, isRequired, ...otherProps} = props; - return ; + return ( + + ); } From e137507bb6ee2a4258a90c4835fded981c8d6ad3 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Fri, 5 Jul 2024 21:29:44 +0800 Subject: [PATCH 08/26] fix(autocomplete): props comments --- packages/components/autocomplete/src/hidden-input.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/components/autocomplete/src/hidden-input.tsx b/packages/components/autocomplete/src/hidden-input.tsx index cafdc9c1e6..d302dee29f 100644 --- a/packages/components/autocomplete/src/hidden-input.tsx +++ b/packages/components/autocomplete/src/hidden-input.tsx @@ -8,13 +8,13 @@ export interface AriaHiddenInputProps { * See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#htmlattrdefautocomplete). */ autoComplete?: string; - /** The text label for the select. */ + /** The text label for the input. */ label?: ReactNode; /** HTML form input name. */ name?: string; - /** Sets the disabled state of the select and input. */ + /** Sets the disabled state of the input. */ isDisabled?: boolean; - /** Whether the select is required. */ + /** Whether the input is required. */ isRequired?: boolean; /** State for the input. */ state: ComboBoxState; From e8c414912def24a4381856a6622df6cb7f581056 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Fri, 5 Jul 2024 22:59:09 +0800 Subject: [PATCH 09/26] feat(autocomplete): include @react-aria/form --- packages/components/autocomplete/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/components/autocomplete/package.json b/packages/components/autocomplete/package.json index 67d769067b..070d64d0bc 100644 --- a/packages/components/autocomplete/package.json +++ b/packages/components/autocomplete/package.json @@ -55,6 +55,7 @@ "@nextui-org/use-safe-layout-effect": "workspace:*", "@react-aria/combobox": "3.9.1", "@react-aria/focus": "3.17.1", + "@react-aria/form": "3.0.5", "@react-aria/i18n": "3.11.1", "@react-aria/interactions": "3.21.3", "@react-aria/utils": "3.24.1", From 64599e707efc76f1cbada74a0e0ee248f07b3e73 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Fri, 5 Jul 2024 22:59:38 +0800 Subject: [PATCH 10/26] feat(autocomplete): add inputData map for hidden input --- .../autocomplete/src/use-autocomplete.ts | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/packages/components/autocomplete/src/use-autocomplete.ts b/packages/components/autocomplete/src/use-autocomplete.ts index a64b45db76..15aab57188 100644 --- a/packages/components/autocomplete/src/use-autocomplete.ts +++ b/packages/components/autocomplete/src/use-autocomplete.ts @@ -5,7 +5,7 @@ import {mapPropsVariants, useProviderContext} from "@nextui-org/system"; import {useSafeLayoutEffect} from "@nextui-org/use-safe-layout-effect"; import {autocomplete} from "@nextui-org/theme"; import {useFilter} from "@react-aria/i18n"; -import {FilterFn, useComboBoxState} from "@react-stately/combobox"; +import {ComboBoxState, FilterFn, useComboBoxState} from "@react-stately/combobox"; import {ReactRef, useDOMRef} from "@nextui-org/react-utils"; import {ReactNode, useCallback, useEffect, useMemo, useRef} from "react"; import {ComboBoxProps} from "@react-types/combobox"; @@ -111,6 +111,14 @@ interface Props extends Omit, keyof ComboBoxProps */ onClose?: () => void; } +interface InputData { + isDisabled?: boolean; + isRequired?: boolean; + name?: string; + validationBehavior?: "aria" | "native"; +} + +export const inputData = new WeakMap, InputData>(); export type UseAutocompleteProps = Props & Omit & @@ -234,7 +242,7 @@ export function useAutocomplete(originalProps: UseAutocomplete inputProps: mergeProps( { label, - ref: inputRef, + // ref: inputRef, wrapperRef: inputWrapperRef, onClick: () => { if (!state.isOpen && !!state.selectedItem) { @@ -503,6 +511,7 @@ export function useAutocomplete(originalProps: UseAutocomplete const getHiddenInputProps = useCallback( (props = {}) => ({ state, + inputRef, name: originalProps?.name, isRequired: originalProps?.isRequired, autoComplete: originalProps?.autoComplete, @@ -515,9 +524,19 @@ export function useAutocomplete(originalProps: UseAutocomplete originalProps?.autoComplete, originalProps?.autoComplete, originalProps?.isDisabled, + inputRef, ], ); + // store the data to be used in useHiddenInput + inputData.set(state, { + isDisabled: originalProps?.isDisabled, + isRequired: originalProps?.isRequired, + name: originalProps?.name, + // TODO: Future enhancement to support "aria" validation behavior. + validationBehavior: "native", + }); + return { Component, inputRef, From 91f4c5aee4f379d0ae3209465443223deaa26441 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Fri, 5 Jul 2024 22:59:59 +0800 Subject: [PATCH 11/26] feat(autocomplete): useHiddenInput --- .../autocomplete/src/hidden-input.tsx | 73 +++++++++++++++---- 1 file changed, 60 insertions(+), 13 deletions(-) diff --git a/packages/components/autocomplete/src/hidden-input.tsx b/packages/components/autocomplete/src/hidden-input.tsx index d302dee29f..226c3a5185 100644 --- a/packages/components/autocomplete/src/hidden-input.tsx +++ b/packages/components/autocomplete/src/hidden-input.tsx @@ -1,8 +1,12 @@ import type {ComboBoxState} from "@react-stately/combobox"; -import React, {ReactNode} from "react"; +import React, {ReactNode, RefObject} from "react"; +import {useFormReset} from "@react-aria/utils"; +import {useFormValidation} from "@react-aria/form"; -export interface AriaHiddenInputProps { +import {inputData} from "./use-autocomplete"; + +export interface AriaHiddenInputProps { /** * Describes the type of autocomplete functionality the input should provide if any. * See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#htmlattrdefautocomplete). @@ -16,20 +20,63 @@ export interface AriaHiddenInputProps { isDisabled?: boolean; /** Whether the input is required. */ isRequired?: boolean; +} + +type NativeHTMLInputProps = Omit< + React.InputHTMLAttributes, + keyof AriaHiddenInputProps +>; + +type CombinedAriaInputProps = NativeHTMLInputProps & AriaHiddenInputProps; + +export interface HiddenInputProps extends CombinedAriaInputProps { /** State for the input. */ state: ComboBoxState; + /** A ref to the hidden `` element. */ + inputRef?: RefObject; } -export function HiddenInput(props: AriaHiddenInputProps) { - const {state, isDisabled, isRequired, ...otherProps} = props; - - return ( - +export function useHiddenInput(props: HiddenInputProps) { + const data = inputData.get(props.state) || {}; + + const { + state, + autoComplete, + name = data.name, + isDisabled = data.isDisabled, + inputRef, + onChange, + } = props; + + const {validationBehavior, isRequired} = data; + + useFormReset(props.inputRef!, state.selectedKey, state.setSelectedKey); + useFormValidation( + { + validationBehavior, + focus: () => inputRef?.current?.focus(), + }, + state, + inputRef, ); + + return { + name, + ref: inputRef, + type: "hidden", + disabled: isDisabled, + required: isRequired, + autoComplete, + value: state.selectedKey ?? "", + onChange: (e: React.ChangeEvent) => { + state.setSelectedKey(e.target.value); + onChange?.(e); + }, + }; +} + +export function HiddenInput(props: HiddenInputProps) { + const inputProps = useHiddenInput(props); + + return ; } From 455f9856eaa6da9c8f4083c2d4b26036c27744dd Mon Sep 17 00:00:00 2001 From: WK Wong Date: Fri, 5 Jul 2024 23:06:20 +0800 Subject: [PATCH 12/26] chore(deps): pnpm-lock.yaml --- pnpm-lock.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 351f4e8104..dab5f8fc41 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -728,6 +728,9 @@ importers: '@react-aria/focus': specifier: 3.17.1 version: 3.17.1(react@18.2.0) + '@react-aria/form': + specifier: 3.0.5 + version: 3.0.5(react@18.2.0) '@react-aria/i18n': specifier: 3.11.1 version: 3.11.1(react@18.2.0) From 018cb537abdaf9b6d0615529cbf71d87ca835506 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Thu, 19 Sep 2024 23:11:13 +0800 Subject: [PATCH 13/26] fix(autocomplete): add missing import --- packages/components/autocomplete/src/use-autocomplete.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/autocomplete/src/use-autocomplete.ts b/packages/components/autocomplete/src/use-autocomplete.ts index 062901c070..929b15ec87 100644 --- a/packages/components/autocomplete/src/use-autocomplete.ts +++ b/packages/components/autocomplete/src/use-autocomplete.ts @@ -7,7 +7,7 @@ import {autocomplete} from "@nextui-org/theme"; import {useFilter} from "@react-aria/i18n"; import {ComboBoxState, FilterFn, useComboBoxState} from "@react-stately/combobox"; import {ReactRef, useDOMRef} from "@nextui-org/react-utils"; -import {ReactNode, useEffect, useMemo, useRef} from "react"; +import {ReactNode, useCallback, useEffect, useMemo, useRef} from "react"; import {ComboBoxProps} from "@react-types/combobox"; import {PopoverProps} from "@nextui-org/popover"; import {ListboxProps} from "@nextui-org/listbox"; From e02d9c7237348bb26995decf40835483bd210edb Mon Sep 17 00:00:00 2001 From: WK Wong Date: Fri, 20 Sep 2024 00:22:11 +0800 Subject: [PATCH 14/26] feat(autocomplete): add WithFormTemplate --- .../stories/autocomplete.stories.tsx | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/packages/components/autocomplete/stories/autocomplete.stories.tsx b/packages/components/autocomplete/stories/autocomplete.stories.tsx index ca4db45df2..d2d66f76b6 100644 --- a/packages/components/autocomplete/stories/autocomplete.stories.tsx +++ b/packages/components/autocomplete/stories/autocomplete.stories.tsx @@ -764,6 +764,28 @@ const CustomStylesWithCustomItemsTemplate = ({color, ...args}: AutocompleteProps ); }; +const WithFormTemplate = (args: AutocompleteProps) => { + const handleSubmit = (e) => { + e.preventDefault(); + const name = e.target.animal.value; + + // eslint-disable-next-line no-console + console.log(name); + alert("Submitted value: " + name); + }; + + return ( +
+ + Big Cat + Big Dog + + + +
+ ); +}; + const WithReactHookFormTemplate = (args: AutocompleteProps) => { const { register, @@ -1004,6 +1026,14 @@ export const WithAriaLabel = { }, }; +export const WithForm = { + render: WithFormTemplate, + + args: { + ...defaultProps, + }, +}; + export const WithReactHookForm = { render: WithReactHookFormTemplate, From caa14e8924ae3e97bdf73db710b58a9d6cac0a4f Mon Sep 17 00:00:00 2001 From: WK Wong Date: Fri, 4 Oct 2024 01:26:23 +0800 Subject: [PATCH 15/26] fix(autocomplete): include onSelectionChange logic and add missing onChange --- .../autocomplete/src/use-autocomplete.ts | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/packages/components/autocomplete/src/use-autocomplete.ts b/packages/components/autocomplete/src/use-autocomplete.ts index 36381a1ed2..b47c84aff7 100644 --- a/packages/components/autocomplete/src/use-autocomplete.ts +++ b/packages/components/autocomplete/src/use-autocomplete.ts @@ -1,7 +1,7 @@ import type {AutocompleteVariantProps, SlotsToClasses, AutocompleteSlots} from "@nextui-org/theme"; import type {DOMAttributes, HTMLNextUIProps, PropGetter} from "@nextui-org/system"; -import {mapPropsVariants, useProviderContext} from "@nextui-org/system"; +import {mapPropsVariants, useProviderContext, SharedSelection} from "@nextui-org/system"; import {useSafeLayoutEffect} from "@nextui-org/use-safe-layout-effect"; import {autocomplete} from "@nextui-org/theme"; import {useFilter} from "@react-aria/i18n"; @@ -110,6 +110,10 @@ interface Props extends Omit, keyof ComboBoxProps * Callback fired when the select menu is closed. */ onClose?: () => void; + /** + * Handler that is called when the selection changes. + */ + onSelectionChange?: (keys: SharedSelection) => void; } interface InputData { isDisabled?: boolean; @@ -121,7 +125,10 @@ interface InputData { export const inputData = new WeakMap, InputData>(); export type UseAutocompleteProps = Props & - Omit & + Omit< + InputProps, + "children" | "value" | "isClearable" | "defaultValue" | "classNames" | "onSelectionChange" + > & ComboBoxProps & AsyncLoadable & AutocompleteVariantProps; @@ -171,7 +178,9 @@ export function useAutocomplete(originalProps: UseAutocomplete classNames, errorMessage, onOpenChange, + onChange, onClose, + onSelectionChange, isReadOnly = false, ...otherProps } = props; @@ -193,6 +202,17 @@ export function useAutocomplete(originalProps: UseAutocomplete onClose?.(); } }, + onSelectionChange: (keys) => { + onSelectionChange?.(keys); + if (onChange && typeof onChange === "function") { + onChange({ + target: { + name: inputRef?.current?.name, + value: keys, + }, + } as React.ChangeEvent); + } + }, }); state = { @@ -521,6 +541,7 @@ export function useAutocomplete(originalProps: UseAutocomplete isRequired: originalProps?.isRequired, autoComplete: originalProps?.autoComplete, isDisabled: originalProps?.isDisabled, + onChange, ...props, }), [ @@ -529,6 +550,7 @@ export function useAutocomplete(originalProps: UseAutocomplete originalProps?.autoComplete, originalProps?.autoComplete, originalProps?.isDisabled, + originalProps?.isRequired, inputRef, ], ); From 737ad50a6519f0d5c89942cf16dcdc2e556218b2 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Fri, 4 Oct 2024 01:26:44 +0800 Subject: [PATCH 16/26] chore(autocomplete): remove duplicate dependency --- packages/components/autocomplete/src/use-autocomplete.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/components/autocomplete/src/use-autocomplete.ts b/packages/components/autocomplete/src/use-autocomplete.ts index b47c84aff7..c93924f29f 100644 --- a/packages/components/autocomplete/src/use-autocomplete.ts +++ b/packages/components/autocomplete/src/use-autocomplete.ts @@ -548,7 +548,6 @@ export function useAutocomplete(originalProps: UseAutocomplete state, originalProps?.name, originalProps?.autoComplete, - originalProps?.autoComplete, originalProps?.isDisabled, originalProps?.isRequired, inputRef, From ae5940c9ba9769cec65d9ab8103b7343121cbba4 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Fri, 4 Oct 2024 20:21:49 +0800 Subject: [PATCH 17/26] refactor(autocomplete): remove props --- packages/components/autocomplete/src/hidden-input.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/autocomplete/src/hidden-input.tsx b/packages/components/autocomplete/src/hidden-input.tsx index 226c3a5185..59166846e3 100644 --- a/packages/components/autocomplete/src/hidden-input.tsx +++ b/packages/components/autocomplete/src/hidden-input.tsx @@ -50,7 +50,7 @@ export function useHiddenInput(props: HiddenInputProps) { const {validationBehavior, isRequired} = data; - useFormReset(props.inputRef!, state.selectedKey, state.setSelectedKey); + useFormReset(inputRef!, state.selectedKey, state.setSelectedKey); useFormValidation( { validationBehavior, From fe8dc2f5737fa8e349d6efa7f9648c5501628b80 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Fri, 4 Oct 2024 21:34:28 +0800 Subject: [PATCH 18/26] feat(autocomplete): include hiddenInputRef --- packages/components/autocomplete/src/hidden-input.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/components/autocomplete/src/hidden-input.tsx b/packages/components/autocomplete/src/hidden-input.tsx index 59166846e3..5c763c2645 100644 --- a/packages/components/autocomplete/src/hidden-input.tsx +++ b/packages/components/autocomplete/src/hidden-input.tsx @@ -33,6 +33,8 @@ export interface HiddenInputProps extends CombinedAriaInputProps { /** State for the input. */ state: ComboBoxState; /** A ref to the hidden `` element. */ + hiddenInputRef?: RefObject; + /** A ref to the `` element. */ inputRef?: RefObject; } @@ -44,6 +46,7 @@ export function useHiddenInput(props: HiddenInputProps) { autoComplete, name = data.name, isDisabled = data.isDisabled, + hiddenInputRef, inputRef, onChange, } = props; @@ -62,7 +65,7 @@ export function useHiddenInput(props: HiddenInputProps) { return { name, - ref: inputRef, + ref: hiddenInputRef, type: "hidden", disabled: isDisabled, required: isRequired, From 853e3dd98b628751bcf9b44bdcb63784b4f9e3fb Mon Sep 17 00:00:00 2001 From: WK Wong Date: Fri, 4 Oct 2024 21:35:49 +0800 Subject: [PATCH 19/26] feat(autocomplete): include hiddenInputRef logic --- .../autocomplete/src/use-autocomplete.ts | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/packages/components/autocomplete/src/use-autocomplete.ts b/packages/components/autocomplete/src/use-autocomplete.ts index c93924f29f..e71fd421f4 100644 --- a/packages/components/autocomplete/src/use-autocomplete.ts +++ b/packages/components/autocomplete/src/use-autocomplete.ts @@ -188,6 +188,15 @@ export function useAutocomplete(originalProps: UseAutocomplete // Setup filter function and state. const {contains} = useFilter(filterOptions); + // Setup refs and get props for child elements. + const buttonRef = useRef(null); + const inputWrapperRef = useRef(null); + const listBoxRef = useRef(null); + const popoverRef = useRef(null); + const hiddenInputRef = useDOMRef(ref); + const inputRef = useRef(null); + const scrollShadowRef = useDOMRef(scrollRefProp); + let state = useComboBoxState({ ...originalProps, children, @@ -207,7 +216,7 @@ export function useAutocomplete(originalProps: UseAutocomplete if (onChange && typeof onChange === "function") { onChange({ target: { - name: inputRef?.current?.name, + name: hiddenInputRef?.current?.name, value: keys, }, } as React.ChangeEvent); @@ -222,14 +231,6 @@ export function useAutocomplete(originalProps: UseAutocomplete }), }; - // Setup refs and get props for child elements. - const buttonRef = useRef(null); - const inputWrapperRef = useRef(null); - const listBoxRef = useRef(null); - const popoverRef = useRef(null); - const inputRef = useDOMRef(ref); - const scrollShadowRef = useDOMRef(scrollRefProp); - const { buttonProps, inputProps, @@ -262,7 +263,7 @@ export function useAutocomplete(originalProps: UseAutocomplete inputProps: mergeProps( { label, - // ref: inputRef, + ref: inputRef, wrapperRef: inputWrapperRef, onClick: () => { if (!state.isOpen && !!state.selectedItem) { @@ -334,16 +335,21 @@ export function useAutocomplete(originalProps: UseAutocomplete // i.e. setting ref.current.value to something which is uncontrolled // hence, sync the state with `ref.current.value` useSafeLayoutEffect(() => { - if (!inputRef.current) return; + if (!hiddenInputRef.current) return; - const key = inputRef.current.value; + const key = hiddenInputRef.current.value; const item = state.collection.getItem(key); if (item && state.inputValue !== item.textValue) { state.setSelectedKey(key); state.setInputValue(item.textValue); } - }, [inputRef.current]); + + if (inputRef?.current) { + // sync the value from ref to inputRef for initial display + inputRef.current.value = hiddenInputRef.current.value; + } + }, [hiddenInputRef.current]); // focus first non-disabled item useEffect(() => { @@ -537,6 +543,7 @@ export function useAutocomplete(originalProps: UseAutocomplete (props = {}) => ({ state, inputRef, + hiddenInputRef, name: originalProps?.name, isRequired: originalProps?.isRequired, autoComplete: originalProps?.autoComplete, @@ -551,6 +558,7 @@ export function useAutocomplete(originalProps: UseAutocomplete originalProps?.isDisabled, originalProps?.isRequired, inputRef, + hiddenInputRef, ], ); From ea8894a5e5b211d59e326343ccc5914b5449441d Mon Sep 17 00:00:00 2001 From: WK Wong Date: Fri, 4 Oct 2024 21:36:53 +0800 Subject: [PATCH 20/26] feat(autocomplete): add test case --- .../__tests__/autocomplete.test.tsx | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/packages/components/autocomplete/__tests__/autocomplete.test.tsx b/packages/components/autocomplete/__tests__/autocomplete.test.tsx index fb6162007c..cc65ef7ccf 100644 --- a/packages/components/autocomplete/__tests__/autocomplete.test.tsx +++ b/packages/components/autocomplete/__tests__/autocomplete.test.tsx @@ -767,6 +767,7 @@ describe("Autocomplete with React Hook Form", () => { let submitButton: HTMLButtonElement; let wrapper: any; let onSubmit: () => void; + let getReactHookFormValues: (key: string) => string; beforeEach(() => { const {result} = renderHook(() => @@ -783,10 +784,13 @@ describe("Autocomplete with React Hook Form", () => { handleSubmit, register, formState: {errors}, + getValues, } = result.current; onSubmit = jest.fn(); + getReactHookFormValues = getValues; + wrapper = render(
{ expect(onSubmit).toHaveBeenCalledTimes(1); }); + + it("should have correct form values", async () => { + const user = userEvent.setup(); + + await user.click(autocomplete3); + + expect(autocomplete3).toHaveAttribute("aria-expanded", "true"); + + let listboxItems = wrapper.getAllByRole("option"); + + await user.click(listboxItems[1]); + + expect(autocomplete3).toHaveValue("Dog"); + + await user.click(submitButton); + + expect(onSubmit).toHaveBeenCalledTimes(1); + + expect(getReactHookFormValues("withDefaultValue")).toEqual("cat"); + expect(getReactHookFormValues("withoutDefaultValue")).toEqual(""); + expect(getReactHookFormValues("requiredField")).toEqual("dog"); + }); }); From a0a7042f8663b70bebb8aebac1511409a916b1d1 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sat, 5 Oct 2024 00:47:52 +0800 Subject: [PATCH 21/26] fix(autocomplete): typing on onSelectionChange --- packages/components/autocomplete/src/use-autocomplete.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/components/autocomplete/src/use-autocomplete.ts b/packages/components/autocomplete/src/use-autocomplete.ts index e71fd421f4..1fc9292e2e 100644 --- a/packages/components/autocomplete/src/use-autocomplete.ts +++ b/packages/components/autocomplete/src/use-autocomplete.ts @@ -1,7 +1,7 @@ import type {AutocompleteVariantProps, SlotsToClasses, AutocompleteSlots} from "@nextui-org/theme"; import type {DOMAttributes, HTMLNextUIProps, PropGetter} from "@nextui-org/system"; -import {mapPropsVariants, useProviderContext, SharedSelection} from "@nextui-org/system"; +import {mapPropsVariants, useProviderContext} from "@nextui-org/system"; import {useSafeLayoutEffect} from "@nextui-org/use-safe-layout-effect"; import {autocomplete} from "@nextui-org/theme"; import {useFilter} from "@react-aria/i18n"; @@ -113,7 +113,7 @@ interface Props extends Omit, keyof ComboBoxProps /** * Handler that is called when the selection changes. */ - onSelectionChange?: (keys: SharedSelection) => void; + onSelectionChange?: (keys: React.Key | null) => void; } interface InputData { isDisabled?: boolean; From f8cce314b96be732a0ad8c693dbc1c38bd267989 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sun, 6 Oct 2024 16:06:11 +0800 Subject: [PATCH 22/26] fix(autocomplete): handle allowsCustomValue --- packages/components/autocomplete/src/hidden-input.tsx | 2 +- .../components/autocomplete/src/use-autocomplete.ts | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/components/autocomplete/src/hidden-input.tsx b/packages/components/autocomplete/src/hidden-input.tsx index 5c763c2645..886562695e 100644 --- a/packages/components/autocomplete/src/hidden-input.tsx +++ b/packages/components/autocomplete/src/hidden-input.tsx @@ -70,7 +70,7 @@ export function useHiddenInput(props: HiddenInputProps) { disabled: isDisabled, required: isRequired, autoComplete, - value: state.selectedKey ?? "", + value: state.selectedKey ?? inputRef?.current?.value ?? "", onChange: (e: React.ChangeEvent) => { state.setSelectedKey(e.target.value); onChange?.(e); diff --git a/packages/components/autocomplete/src/use-autocomplete.ts b/packages/components/autocomplete/src/use-autocomplete.ts index 1fc9292e2e..7622463b80 100644 --- a/packages/components/autocomplete/src/use-autocomplete.ts +++ b/packages/components/autocomplete/src/use-autocomplete.ts @@ -374,6 +374,17 @@ export function useAutocomplete(originalProps: UseAutocomplete } }, [isOpen]); + useEffect(() => { + if (allowsCustomValue) { + onChange?.({ + target: { + name: hiddenInputRef?.current?.name, + value: hiddenInputRef?.current?.value, + }, + } as React.ChangeEvent); + } + }, [state, allowsCustomValue, inputRef?.current?.value, hiddenInputRef?.current?.value]); + // to prevent the error message: // stopPropagation is now the default behavior for events in React Spectrum. // You can use continuePropagation() to revert this behavior. From 1a84b7b29550ff6b1123ad1eba8ba63dc86f4de6 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sun, 6 Oct 2024 20:03:33 +0800 Subject: [PATCH 23/26] chore(autocomplete): add default values --- .../components/autocomplete/src/use-autocomplete.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/components/autocomplete/src/use-autocomplete.ts b/packages/components/autocomplete/src/use-autocomplete.ts index 7622463b80..3ca1357cd5 100644 --- a/packages/components/autocomplete/src/use-autocomplete.ts +++ b/packages/components/autocomplete/src/use-autocomplete.ts @@ -556,9 +556,9 @@ export function useAutocomplete(originalProps: UseAutocomplete inputRef, hiddenInputRef, name: originalProps?.name, - isRequired: originalProps?.isRequired, - autoComplete: originalProps?.autoComplete, - isDisabled: originalProps?.isDisabled, + isRequired: originalProps?.isRequired ?? false, + autoComplete: originalProps?.autoComplete ?? "on", + isDisabled: originalProps?.isDisabled ?? false, onChange, ...props, }), @@ -575,8 +575,8 @@ export function useAutocomplete(originalProps: UseAutocomplete // store the data to be used in useHiddenInput inputData.set(state, { - isDisabled: originalProps?.isDisabled, - isRequired: originalProps?.isRequired, + isDisabled: originalProps?.isDisabled ?? false, + isRequired: originalProps?.isRequired ?? false, name: originalProps?.name, // TODO: Future enhancement to support "aria" validation behavior. validationBehavior: "native", From 8ee75deee5cb09c3432b35da79f863a07f613da7 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sun, 6 Oct 2024 20:07:06 +0800 Subject: [PATCH 24/26] fix(autocomplete): pr comments --- .../autocomplete/__tests__/autocomplete.test.tsx | 2 +- packages/components/autocomplete/src/use-autocomplete.ts | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/components/autocomplete/__tests__/autocomplete.test.tsx b/packages/components/autocomplete/__tests__/autocomplete.test.tsx index cc65ef7ccf..a1b5e4e232 100644 --- a/packages/components/autocomplete/__tests__/autocomplete.test.tsx +++ b/packages/components/autocomplete/__tests__/autocomplete.test.tsx @@ -767,7 +767,7 @@ describe("Autocomplete with React Hook Form", () => { let submitButton: HTMLButtonElement; let wrapper: any; let onSubmit: () => void; - let getReactHookFormValues: (key: string) => string; + let getReactHookFormValues: (key: string) => any; beforeEach(() => { const {result} = renderHook(() => diff --git a/packages/components/autocomplete/src/use-autocomplete.ts b/packages/components/autocomplete/src/use-autocomplete.ts index 3ca1357cd5..3d3b1425f3 100644 --- a/packages/components/autocomplete/src/use-autocomplete.ts +++ b/packages/components/autocomplete/src/use-autocomplete.ts @@ -345,7 +345,7 @@ export function useAutocomplete(originalProps: UseAutocomplete state.setInputValue(item.textValue); } - if (inputRef?.current) { + if (inputRef.current && hiddenInputRef.current) { // sync the value from ref to inputRef for initial display inputRef.current.value = hiddenInputRef.current.value; } @@ -461,10 +461,11 @@ export function useAutocomplete(originalProps: UseAutocomplete // `name` will be in the hidden input // so that users can get the value of the input instead of label in form - delete props["name"]; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const {name, ...restProps} = props; return { - ...props, + ...restProps, isInvalid, validationBehavior, errorMessage: From 5eaf1749cc276249bdf43c20d269f7bb9cc3aed1 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Sun, 6 Oct 2024 20:11:51 +0800 Subject: [PATCH 25/26] refactor(autocomplete): add hiddenInputRef.current check --- packages/components/autocomplete/src/use-autocomplete.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/autocomplete/src/use-autocomplete.ts b/packages/components/autocomplete/src/use-autocomplete.ts index 3d3b1425f3..04d8a9aeb1 100644 --- a/packages/components/autocomplete/src/use-autocomplete.ts +++ b/packages/components/autocomplete/src/use-autocomplete.ts @@ -375,7 +375,7 @@ export function useAutocomplete(originalProps: UseAutocomplete }, [isOpen]); useEffect(() => { - if (allowsCustomValue) { + if (allowsCustomValue && hiddenInputRef.current) { onChange?.({ target: { name: hiddenInputRef?.current?.name, From 0f71b970c6a47d7f8f7e052060dcb7c0d740148b Mon Sep 17 00:00:00 2001 From: Junior Garcia Date: Fri, 15 Nov 2024 11:40:10 -0300 Subject: [PATCH 26/26] Update .changeset/purple-pillows-beg.md --- .changeset/purple-pillows-beg.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/purple-pillows-beg.md b/.changeset/purple-pillows-beg.md index c322850ca5..8aba493186 100644 --- a/.changeset/purple-pillows-beg.md +++ b/.changeset/purple-pillows-beg.md @@ -1,5 +1,5 @@ --- -"@nextui-org/autocomplete": minor +"@nextui-org/autocomplete": patch --- return autocomplete value instead of label in form submission (#3353, #3343)