diff --git a/packages/combobox/src/index.tsx b/packages/combobox/src/index.tsx index 10b726041..3654d87c2 100644 --- a/packages/combobox/src/index.tsx +++ b/packages/combobox/src/index.tsx @@ -524,6 +524,22 @@ export const ComboboxInput = React.forwardRef( } }, [controlledValue, handleValueChange, isControlled, value]); + // If a form is reset, we'll need to manually clear the value since we are + // controlling it internally. + React.useEffect(() => { + let form = inputRef.current?.form; + if (!form) return; + + function handleReset(event: Event) { + transition(CLEAR, { isControlled }); + } + + form.addEventListener("reset", handleReset); + return () => { + form?.removeEventListener("reset", handleReset); + }; + }, [inputRef, isControlled, transition]); + // [*]... and when controlled, we don't trigger handleValueChange as the // user types, instead the developer controls it with the normal input // onChange prop @@ -1237,8 +1253,7 @@ function makeHash(str: string) { return hash; } for (let i = 0; i < str.length; i++) { - var char = str.charCodeAt(i); - hash = (hash << 5) - hash + char; + hash = (hash << 5) - hash + str.charCodeAt(i); hash = hash & hash; } return hash; diff --git a/playground/stories/combobox/in-form.example.tsx b/playground/stories/combobox/in-form.example.tsx new file mode 100644 index 000000000..e8c378d4a --- /dev/null +++ b/playground/stories/combobox/in-form.example.tsx @@ -0,0 +1,86 @@ +import * as React from "react"; +import { + Combobox, + ComboboxInput, + ComboboxList, + ComboboxOption, + ComboboxPopover, +} from "@reach/combobox"; +import { Alert } from "@reach/alert"; +import { useCityMatch } from "./utils"; +import "@reach/combobox/styles.css"; + +let name = "In a form"; + +function showOpts( + results: R[], + render: (props: { result: R; index: number }) => React.ReactNode +) { + return results.slice(0, 10).map((result, index) => render({ result, index })); +} + +function MyCombobox() { + let [term, setTerm] = React.useState(""); + let [showAlert, setShowAlert] = React.useState(false); + let results = useCityMatch(term); + let tid = React.useRef(-1); + React.useEffect(() => { + return () => { + // eslint-disable-next-line react-hooks/exhaustive-deps + window.clearTimeout(tid.current); + }; + }, []); + + return ( +
+
{ + event.preventDefault(); + window.clearTimeout(tid.current); + setShowAlert(true); + tid.current = window.setTimeout(() => { + setShowAlert(false); + }, 3000); + }} + > + + +
City
+ setTerm(event.target.value)} + autoComplete="off" + autoCorrect="off" + autoSave="off" + /> + {results ? ( + + + {showOpts(results, ({ result, index }) => ( + + ))} + + + ) : null} +
+
+ + +
+
+
+ {showAlert ? Submitted! : null} +
+ ); +} + +function Example() { + return ; +} + +Example.storyName = name; +export { Example }; + +//////////////////////////////////////////////////////////////////////////////// diff --git a/playground/stories/combobox/index.story.ts b/playground/stories/combobox/index.story.ts index c855b82a0..0b12343ad 100644 --- a/playground/stories/combobox/index.story.ts +++ b/playground/stories/combobox/index.story.ts @@ -2,6 +2,7 @@ export { Example as Basic } from "./basic.example"; export { Example as ControlledTs } from "./controlled.example"; export { Example as LotsOfElements } from "./lots-of-elements.example"; export { Example as NoPopover } from "./no-popover.example"; +export { Example as InForm } from "./in-form.example"; export { Example as OpenOnFocus } from "./open-on-focus.example"; // export { Example as SimulatedChange } from "./simulated-change.example.js"; export { Example as TokenInput } from "./token-input.example";