@primer/react
Version:
An implementation of GitHub's Primer Design System using React
117 lines (116 loc) • 4.66 kB
JavaScript
import { useMergedRefs } from "../hooks/useMergedRefs.js";
import useSafeTimeout from "../hooks/useSafeTimeout.js";
import { AutocompleteContext, AutocompleteInputContext } from "./AutocompleteContext.js";
import TextInput_default from "../TextInput/TextInput.js";
import { jsx } from "react/jsx-runtime";
import React, { useCallback, useContext, useEffect, useState } from "react";
//#region src/Autocomplete/AutocompleteInput.tsx
const ARROW_KEYS_NAV = /* @__PURE__ */ new Set(["ArrowUp", "ArrowDown"]);
const AutocompleteInput = /*#__PURE__*/ React.forwardRef(({ as: Component = TextInput_default, onFocus, onBlur, onChange, onKeyDown, onKeyUp, onKeyPress, value, openOnFocus = false, ...props }, forwardedRef) => {
const autocompleteContext = useContext(AutocompleteContext);
const inputContext = useContext(AutocompleteInputContext);
if (autocompleteContext === null || inputContext === null) throw new Error("AutocompleteContext returned null values");
const { activeDescendantRef, id, inputRef, setInputValue, setShowMenu, showMenu } = autocompleteContext;
const { autocompleteSuggestion = "", inputValue = "", isMenuDirectlyActivated } = inputContext;
const mergedRef = useMergedRefs(forwardedRef, inputRef);
const [highlightRemainingText, setHighlightRemainingText] = useState(true);
const { safeSetTimeout } = useSafeTimeout();
const handleInputFocus = (event) => {
onFocus === null || onFocus === void 0 || onFocus(event);
if (openOnFocus) setShowMenu(true);
};
const handleInputBlur = useCallback((event) => {
onBlur && onBlur(event);
safeSetTimeout(() => {
if (document.activeElement !== inputRef.current) {
setShowMenu(false);
if (inputRef.current && autocompleteSuggestion && inputRef.current.value !== inputValue) inputRef.current.value = inputValue;
}
}, 0);
}, [
onBlur,
setShowMenu,
inputRef,
safeSetTimeout,
autocompleteSuggestion,
inputValue
]);
const handleInputChange = (event) => {
onChange && onChange(event);
setInputValue(event.currentTarget.value);
if (!showMenu) setShowMenu(true);
};
const handleInputKeyDown = useCallback((event) => {
var _inputRef$current;
onKeyDown && onKeyDown(event);
if (event.key === "Backspace") setHighlightRemainingText(false);
if (event.key === "Escape" && (_inputRef$current = inputRef.current) !== null && _inputRef$current !== void 0 && _inputRef$current.value) {
setInputValue("");
inputRef.current.value = "";
}
if (!showMenu && ARROW_KEYS_NAV.has(event.key) && !event.altKey) setShowMenu(true);
}, [
inputRef,
setInputValue,
setHighlightRemainingText,
onKeyDown,
showMenu,
setShowMenu
]);
const handleInputKeyUp = useCallback((event) => {
onKeyUp && onKeyUp(event);
if (event.key === "Backspace") setHighlightRemainingText(true);
}, [setHighlightRemainingText, onKeyUp]);
const onInputKeyPress = useCallback((event) => {
onKeyPress && onKeyPress(event);
if (showMenu && event.key === "Enter" && activeDescendantRef.current) {
event.preventDefault();
event.nativeEvent.stopImmediatePropagation();
const activeDescendantEvent = new KeyboardEvent(event.type, event.nativeEvent);
activeDescendantRef.current.dispatchEvent(activeDescendantEvent);
}
}, [
activeDescendantRef,
showMenu,
onKeyPress
]);
useEffect(() => {
if (!inputRef.current) return;
if (!autocompleteSuggestion) inputRef.current.value = inputValue;
if (document.activeElement === inputRef.current && highlightRemainingText && autocompleteSuggestion && (inputValue || isMenuDirectlyActivated)) {
inputRef.current.value = autocompleteSuggestion;
if (autocompleteSuggestion.toLowerCase().indexOf(inputValue.toLowerCase()) === 0) inputRef.current.setSelectionRange(inputValue.length, autocompleteSuggestion.length);
}
}, [
autocompleteSuggestion,
inputValue,
inputRef,
isMenuDirectlyActivated
]);
useEffect(() => {
setInputValue(typeof value !== "undefined" ? value.toString() : "");
}, [value, setInputValue]);
return /*#__PURE__*/ jsx(Component, {
onFocus: handleInputFocus,
onBlur: handleInputBlur,
onChange: handleInputChange,
onKeyDown: handleInputKeyDown,
onKeyPress: onInputKeyPress,
onKeyUp: handleInputKeyUp,
ref: mergedRef,
"aria-controls": `${id}-listbox`,
"aria-autocomplete": "both",
role: "combobox",
"aria-expanded": showMenu,
"aria-haspopup": "listbox",
"aria-owns": `${id}-listbox`,
autoComplete: "off",
id,
...props,
"data-component": "Autocomplete.Input"
});
});
AutocompleteInput.displayName = "AutocompleteInput";
AutocompleteInput.__SLOT__ = Symbol("Autocomplete.Input");
//#endregion
export { AutocompleteInput as default };