@navikt/ds-react
Version:
React components from the Norwegian Labour and Welfare Administration.
217 lines • 9.76 kB
JavaScript
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
import React, { forwardRef, useCallback, useRef, } from "react";
import { useRenameCSS } from "../../../theme/Theme.js";
import { omit } from "../../../util/index.js";
import { composeEventHandlers } from "../../../util/composeEventHandlers.js";
import { useMergeRefs } from "../../../util/hooks/index.js";
import filteredOptionsUtil from "../FilteredOptions/filtered-options-util.js";
import { useFilteredOptionsContext } from "../FilteredOptions/filteredOptionsContext.js";
import { useSelectedOptionsContext } from "../SelectedOptions/selectedOptionsContext.js";
import { useInputContext } from "./Input.context.js";
const Input = forwardRef((_a, ref) => {
var { inputClassName, shouldShowSelectedOptions, placeholder, onBlur } = _a, rest = __rest(_a, ["inputClassName", "shouldShowSelectedOptions", "placeholder", "onBlur"]);
const { cn } = useRenameCSS();
const internalRef = useRef(null);
const mergedRefs = useMergeRefs(ref, internalRef);
const { clearInput, inputProps, onChange, size, value, searchTerm, setValue, hideCaret, setHideCaret, readOnly, } = useInputContext();
const { selectedOptions, removeSelectedOption, toggleOption, isMultiSelect, maxSelected, } = useSelectedOptionsContext();
const { activeDecendantId, allowNewValues, currentOption, filteredOptions, isValueNew, toggleIsListOpen, isListOpen, ariaDescribedBy, setIsMouseLastUsedInputDevice, shouldAutocomplete, virtualFocus, } = useFilteredOptionsContext();
const onEnter = useCallback((event) => {
var _a;
const isSelected = (text) => selectedOptions.some((option) => option.label.toLocaleLowerCase() === text.toLocaleLowerCase());
if (currentOption) {
event.preventDefault();
// Selecting a value from the dropdown / FilteredOptions
toggleOption(currentOption, event);
if (!isMultiSelect && !isSelected(currentOption.label)) {
toggleIsListOpen(false);
}
}
else if (isSelected(value)) {
event.preventDefault();
// Trying to set the same value that is already set, so just clearing the input
clearInput(event);
}
else if ((allowNewValues || shouldAutocomplete) && value !== "") {
event.preventDefault();
const autoCompletedOption = filteredOptionsUtil.getFirstValueStartingWith(value, filteredOptions);
/*
* User can have matching results, while not using the autocomplete result
* E.g. User types "Oslo", list has is "Oslo kommune", but user hits backspace, canceling autocomplete.
*/
const autoCompleteMatchesValue = filteredOptionsUtil.normalizeText(value) ===
filteredOptionsUtil.normalizeText((_a = autoCompletedOption === null || autoCompletedOption === void 0 ? void 0 : autoCompletedOption.label) !== null && _a !== void 0 ? _a : "");
let optionToToggle;
if (shouldAutocomplete &&
autoCompletedOption &&
autoCompleteMatchesValue) {
optionToToggle = autoCompletedOption;
}
else if (allowNewValues && isValueNew) {
optionToToggle = { label: value, value };
}
if (!optionToToggle) {
return;
}
toggleOption(optionToToggle, event);
if (!isMultiSelect && !isSelected(optionToToggle.label)) {
toggleIsListOpen(false);
}
}
}, [
allowNewValues,
clearInput,
currentOption,
filteredOptions,
isMultiSelect,
isValueNew,
selectedOptions,
shouldAutocomplete,
toggleIsListOpen,
toggleOption,
value,
]);
const handleKeyUp = (e) => {
e.preventDefault();
switch (e.key) {
case "Enter":
case "Accept":
onEnter(e);
break;
default:
break;
}
};
const handleKeyDown = useCallback((e) => {
setIsMouseLastUsedInputDevice(false);
if (readOnly) {
return;
}
if (e.key === "Backspace") {
if (value === "" && shouldShowSelectedOptions) {
const lastSelectedOption = selectedOptions[selectedOptions.length - 1];
if (lastSelectedOption) {
removeSelectedOption(lastSelectedOption);
}
}
}
else if (e.key === "Enter" || e.key === "Accept") {
if (activeDecendantId || value) {
e.preventDefault();
}
}
else if (e.key === "Escape") {
if (isListOpen || value) {
e.preventDefault(); // Prevents closing an encasing Modal, as Combobox reacts on keyup.
clearInput(e);
toggleIsListOpen(false);
}
}
else if (["ArrowLeft", "ArrowRight"].includes(e.key)) {
/**
* In case user has an active selection and 'completes' the selection with ArrowLeft or ArrowRight
* we need to make sure to update the filter.
*/
if (value !== "" && value !== searchTerm) {
onChange(value);
}
}
else if (e.key === "ArrowDown") {
// Reset the value to the search term to cancel autocomplete
// if the user moves focus down to the FilteredOptions
if (value !== searchTerm) {
setValue(searchTerm);
}
if (!isListOpen) {
toggleIsListOpen(true);
setTimeout(virtualFocus.moveFocusDown, 0); // Wait until list is visible so that scrollIntoView works
}
else {
virtualFocus.moveFocusDown();
}
}
else if (e.key === "ArrowUp") {
if (value !== "" && value !== searchTerm) {
onChange(value);
}
// Check that the FilteredOptions list is open and has virtual focus.
// Otherwise ignore keystrokes, so it doesn't interfere with text editing
if (isListOpen && activeDecendantId) {
e.preventDefault();
if (virtualFocus.isFocusOnTheTop()) {
toggleIsListOpen(false);
}
virtualFocus.moveFocusUp();
}
}
else if (e.key === "Home") {
e.preventDefault();
virtualFocus.moveFocusToTop();
}
else if (e.key === "End") {
e.preventDefault();
if (!isListOpen) {
toggleIsListOpen(true);
setTimeout(virtualFocus.moveFocusToBottom, 0); // Wait until list is visible so that scrollIntoView works
}
else {
virtualFocus.moveFocusToBottom();
}
}
else if (e.key === "PageUp") {
e.preventDefault();
virtualFocus.moveFocusUpBy(6);
}
else if (e.key === "PageDown") {
e.preventDefault();
if (!isListOpen) {
toggleIsListOpen(true);
setTimeout(() => virtualFocus.moveFocusDownBy(6), 0); // Wait until list is visible so that scrollIntoView works
}
else {
virtualFocus.moveFocusDownBy(6);
}
}
}, [
value,
selectedOptions,
removeSelectedOption,
isListOpen,
activeDecendantId,
setIsMouseLastUsedInputDevice,
clearInput,
toggleIsListOpen,
onChange,
virtualFocus,
setValue,
searchTerm,
shouldShowSelectedOptions,
readOnly,
]);
const onChangeHandler = useCallback((event) => {
const newValue = event.target.value;
if (newValue && newValue !== "") {
toggleIsListOpen(true);
}
else if (filteredOptions.length === 0) {
toggleIsListOpen(false);
}
onChange(newValue);
}, [filteredOptions.length, onChange, toggleIsListOpen]);
return (React.createElement("input", Object.assign({}, rest, omit(inputProps, ["aria-invalid"]), { ref: mergedRefs, type: "text", role: "combobox", value: value, onBlur: composeEventHandlers(onBlur, virtualFocus.resetFocus), onClick: () => {
setHideCaret(maxSelected.isLimitReached);
value !== searchTerm && onChange(value);
}, onInput: onChangeHandler, onKeyUp: handleKeyUp, onKeyDown: handleKeyDown, autoComplete: "off", placeholder: selectedOptions.length ? undefined : placeholder, className: cn(inputClassName, "navds-combobox__input", "navds-body-short", `navds-body-short--${size}`, { "navds-combobox__input--hide-caret": hideCaret }), "aria-controls": filteredOptionsUtil.getFilteredOptionsId(inputProps.id), "aria-expanded": !!isListOpen, "aria-autocomplete": shouldAutocomplete ? "both" : "list", "aria-activedescendant": activeDecendantId, "aria-describedby": ariaDescribedBy, "aria-invalid": inputProps["aria-invalid"], readOnly: readOnly })));
});
export default Input;
//# sourceMappingURL=Input.js.map