@pagopa/mui-italia
Version:
[Material-UI](https://mui.com/core/) theme inspired by [Bootstrap Italia](https://italia.github.io/bootstrap-italia/).
291 lines (290 loc) • 16.6 kB
JavaScript
"use strict";
'use client';
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const jsx_runtime_1 = require("react/jsx-runtime");
const icons_material_1 = require("@mui/icons-material");
const material_1 = require("@mui/material");
const utils_1 = require("@mui/utils");
const react_1 = require("react");
const device_1 = require("../../utils/device");
const autocomplete_1 = require("../../utils/autocomplete");
const AutocompleteContent_1 = tslib_1.__importDefault(require("./AutocompleteContent"));
const MultiSelectChips_1 = tslib_1.__importDefault(require("./MultiSelectChips"));
const DefaultEmptyState_1 = tslib_1.__importDefault(require("./DefaultEmptyState"));
const Autocomplete = (_a) => {
var _b;
var { id, options, getOptionLabel = (option) => { var _a; return (_a = option.label) !== null && _a !== void 0 ? _a : option; }, isOptionEqualToValue = (option, value) => option === value, label, placeholder, multiple = false, handleFiltering = autocomplete_1.filterOptionsInternal, disabled = false, required = false, error = false, helperText, loading = false, noResultsText = 'There are no matches to show', slots = {}, slotProps = {}, value, inputValue, renderOption, onChange, onInputChange, sx } = _a, other = tslib_1.__rest(_a, ["id", "options", "getOptionLabel", "isOptionEqualToValue", "label", "placeholder", "multiple", "handleFiltering", "disabled", "required", "error", "helperText", "loading", "noResultsText", "slots", "slotProps", "value", "inputValue", "renderOption", "onChange", "onInputChange", "sx"]) // all the HTML default properties (i.e. data-testid)
;
const [inputInternalValue, setInputInternalValue] = (0, react_1.useState)('');
const [internalValue, setInternalValue] = (0, react_1.useState)((multiple ? [] : null));
const [isOpen, setIsOpen] = (0, react_1.useState)(false);
const [activeIndex, setActiveIndex] = (0, react_1.useState)(-1);
const containerRef = (0, react_1.useRef)(null);
const popperRef = (0, react_1.useRef)(null);
const inputRef = (0, react_1.useRef)(null);
const listboxRef = (0, react_1.useRef)(null);
const generatedId = (0, react_1.useId)();
const inputId = id !== null && id !== void 0 ? id : generatedId;
const listboxId = `${inputId}-listbox`;
const currentInputValue = inputValue !== null && inputValue !== void 0 ? inputValue : inputInternalValue;
const currentValue = value !== null && value !== void 0 ? value : internalValue;
const { startIcon: StartIcon, loadingSkeleton: LoadingSkeleton, emptyState: EmptyState = DefaultEmptyState_1.default, } = slots;
const { clearButton: clearButtonProps = { 'aria-label': 'Clear the entered text' }, toggleButton: toggleButtonProps = {
hidden: false,
'open-aria-label': 'Open the dropdown menu',
'close-aria-label': 'Close the dropdown menu',
}, announcementBox: announcementBoxProps = {
loadingText: 'Loading',
selectionText: '%s selected',
}, selectionBox: selectionBoxProps = { 'aria-label': 'Selected options' }, selectionChip: selectionChipProps = { 'aria-label': 'Delete %s' }, textField: textFieldProps = {}, } = slotProps;
const filteredOptions = handleFiltering(options, {
inputValue: currentInputValue,
getOptionLabel,
});
const setInputValue = (v, reason) => {
// non controlled input
if (inputValue === undefined) {
setInputInternalValue(v);
}
if (v !== currentInputValue) {
setActiveIndex(-1);
}
onInputChange === null || onInputChange === void 0 ? void 0 : onInputChange(v, reason);
};
const setAutocompleteValue = (v) => {
// non controlled autocomplete
if (value === undefined) {
setInternalValue(v);
}
onChange === null || onChange === void 0 ? void 0 : onChange(v);
};
const handleInputChange = (e) => {
if (disabled) {
return;
}
setInputValue(e.target.value, 'input');
setIsOpen(true);
};
const handleOptionSelect = (option) => {
if (disabled) {
return;
}
if (multiple && Array.isArray(currentValue)) {
const isAlreadySelected = currentValue.some((selected) => isOptionEqualToValue(selected, option));
let newSelectedOptions;
if (isAlreadySelected) {
newSelectedOptions = currentValue.filter((selected) => !isOptionEqualToValue(selected, option));
}
else {
newSelectedOptions = [...currentValue, option];
}
setInputValue('', 'selectOption');
setAutocompleteValue(newSelectedOptions);
}
else {
setInputFocus(false);
setInputValue(getOptionLabel(option), 'selectOption');
setAutocompleteValue(option);
}
};
const handleChipDelete = (optionToRemove) => {
var _a;
if (disabled) {
return;
}
if (multiple && Array.isArray(currentValue)) {
const newSelectedOptions = currentValue.filter((option) => !isOptionEqualToValue(option, optionToRemove));
setAutocompleteValue(newSelectedOptions);
(_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
}
};
const setInputFocus = (open = true) => {
var _a;
if (disabled) {
return;
}
(_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
setIsOpen(open);
};
const handleKeyDown = (e) => {
if (disabled) {
return;
}
if (filteredOptions.length === 0) {
if (e.key === 'Escape') {
setIsOpen(false);
}
return;
}
switch (e.key) {
case 'ArrowDown':
e.preventDefault();
setIsOpen(true);
setActiveIndex((prev) => (prev < filteredOptions.length - 1 ? prev + 1 : 0));
break;
case 'ArrowUp':
e.preventDefault();
setActiveIndex((prev) => (prev > 0 ? prev - 1 : filteredOptions.length - 1));
break;
case 'Enter':
e.preventDefault();
if (activeIndex >= 0 && filteredOptions[activeIndex]) {
handleOptionSelect(filteredOptions[activeIndex]);
}
break;
case 'Escape':
e.preventDefault();
setIsOpen(false);
setActiveIndex(-1);
break;
default:
break;
}
};
const handleClearValue = () => {
if (disabled) {
return;
}
setInputValue('', 'clear');
setAutocompleteValue(multiple ? [] : null);
setIsOpen(false);
};
const handleToggleOpen = (e) => {
if (disabled) {
return;
}
e.preventDefault();
e.stopPropagation();
setIsOpen((prev) => !prev);
};
const handleBlur = (event) => {
var _a, _b;
if (disabled) {
return;
}
const focusingAnOption = activeIndex !== -1;
const keepMenuOpen = isOpen && (0, device_1.isMobileDevice)();
if (focusingAnOption && keepMenuOpen) {
return;
}
// If the newly focused element isn't in the autocomplete component, we can close the dropdown
// and deselect the option
if (!((_a = containerRef.current) === null || _a === void 0 ? void 0 : _a.contains(event.relatedTarget)) &&
!((_b = popperRef.current) === null || _b === void 0 ? void 0 : _b.contains(event.relatedTarget))) {
setIsOpen(false);
setActiveIndex(-1);
}
};
const getStartInputAdornment = () => {
if (!StartIcon && (!multiple || (Array.isArray(currentValue) && currentValue.length === 0))) {
return undefined;
}
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [StartIcon && ((0, jsx_runtime_1.jsx)(StartIcon, { sx: {
color: disabled ? 'text.disabled' : 'text.secondary',
} })), multiple && Array.isArray(currentValue) && currentValue.length > 0 && ((0, jsx_runtime_1.jsx)(MultiSelectChips_1.default, { selectedOptions: currentValue, handleChipDelete: handleChipDelete, disabled: disabled, getOptionLabel: getOptionLabel, slotProps: {
list: { 'aria-label': selectionBoxProps['aria-label'] },
chip: { 'aria-label': selectionChipProps['aria-label'] },
} }))] }));
};
const getEndInputAdornment = () => {
const showClearIcon = currentInputValue || (Array.isArray(currentValue) ? currentValue.length > 0 : currentValue);
const showArrowIcon = !toggleButtonProps.hidden;
if ((!showClearIcon && !showArrowIcon) || disabled) {
return null;
}
return ((0, jsx_runtime_1.jsxs)(material_1.Box, Object.assign({ sx: {
display: 'flex',
alignItems: 'center',
gap: 0.5,
} }, { children: [showClearIcon && ((0, jsx_runtime_1.jsx)(material_1.IconButton, Object.assign({ size: "small", onClick: handleClearValue, onMouseDown: (e) => e.preventDefault(), "aria-label": clearButtonProps['aria-label'], disabled: disabled, sx: {
padding: 0,
color: 'text.secondary',
} }, { children: (0, jsx_runtime_1.jsx)(icons_material_1.Close, {}) }))), showArrowIcon && ((0, jsx_runtime_1.jsx)(material_1.IconButton, Object.assign({ size: "small", onClick: handleToggleOpen, "aria-label": isOpen ? toggleButtonProps['close-aria-label'] : toggleButtonProps['open-aria-label'], disabled: disabled, sx: {
padding: 0,
color: disabled ? 'text.disabled' : 'text.secondary',
cursor: disabled ? 'default' : 'pointer',
} }, { children: isOpen ? (0, jsx_runtime_1.jsx)(icons_material_1.KeyboardArrowUp, {}) : (0, jsx_runtime_1.jsx)(icons_material_1.KeyboardArrowDown, {}) })))] })));
};
(0, react_1.useEffect)(() => {
if (disabled) {
setIsOpen(false);
setActiveIndex(-1);
return;
}
if (isOpen && activeIndex >= 0 && listboxRef.current) {
const optionElement = listboxRef.current.querySelector(CSS.escape(`#${listboxId}-option-${activeIndex}`));
optionElement === null || optionElement === void 0 ? void 0 : optionElement.scrollIntoView({ block: 'nearest' });
}
}, [activeIndex, isOpen, disabled]);
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)(material_1.Box, Object.assign({ position: "relative", ref: containerRef, onBlur: handleBlur, sx: sx }, other, { children: [(0, jsx_runtime_1.jsx)(material_1.TextField, Object.assign({ id: inputId, fullWidth: true, inputRef: inputRef, value: currentInputValue, onChange: handleInputChange, onClick: () => setInputFocus(true), onKeyDown: handleKeyDown, label: label, placeholder: multiple && Array.isArray(currentValue) && currentValue.length > 0 ? '' : placeholder, variant: "outlined", autoComplete: "off", disabled: disabled, required: required, error: error, helperText: helperText, inputProps: {
role: 'combobox',
'aria-expanded': isOpen,
'aria-controls': listboxId,
'aria-autocomplete': 'list',
'aria-activedescendant': activeIndex >= 0 ? `${listboxId}-option-${activeIndex}` : undefined,
'aria-haspopup': 'listbox',
'aria-disabled': disabled,
}, InputProps: {
startAdornment: getStartInputAdornment(),
endAdornment: getEndInputAdornment(),
}, sx: {
'& .MuiInputBase-root': {
display: 'flex',
flexWrap: 'wrap',
alignItems: 'center',
gap: 1,
padding: '0.625rem 0.75rem',
borderRadius: '0.5rem',
borderWidth: '0.125rem',
backgroundColor: disabled ? '#F4F5F8' : 'transparent',
minHeight: '3rem',
},
'& .MuiInputBase-input': {
flex: '1 1 3.75rem',
minWidth: '3.75rem',
padding: '0',
boxSizing: 'border-box',
},
'& .MuiInputLabel-root': {
transform: 'translate(0.875rem, 0.663rem) scale(1)',
'&.MuiInputLabel-shrink': {
transform: 'translate(0.875rem, -0.663rem) scale(0.75)',
},
},
} }, textFieldProps)), (0, jsx_runtime_1.jsx)(material_1.Popper, Object.assign({ id: `${inputId}-popper`, ref: popperRef, open: isOpen && !disabled, anchorEl: containerRef.current, keepMounted: true, placement: "bottom-start", modifiers: [
{
name: 'flip',
enabled: true,
options: {
altBoundary: true,
rootBoundary: 'viewport',
padding: 8,
},
},
{
name: 'sameWidth',
enabled: true,
phase: 'beforeWrite',
requires: ['computeStyles'],
fn: ({ state }) => {
state.styles.popper.width = `${state.rects.reference.width}px`;
},
},
{
name: 'offset',
options: {
// [skidding, distance]
// skidding: lateral movement (0)
// distance: distance from the input (es. 8px)
offset: [0, 8],
},
},
], style: { zIndex: 1300 }, role: "presentation" }, { children: (0, jsx_runtime_1.jsxs)(material_1.Paper, Object.assign({ elevation: 4, variant: "elevation", sx: {
maxHeight: '15rem',
overflowY: 'auto',
} }, { children: [filteredOptions.length > 0 && ((0, jsx_runtime_1.jsx)(AutocompleteContent_1.default, { multiple: multiple, filteredOptions: filteredOptions, selectedOptions: currentValue, handleOptionSelect: handleOptionSelect, listboxId: listboxId, listboxRef: listboxRef, inputId: inputId, activeIndex: activeIndex, setActiveIndex: setActiveIndex, renderOption: renderOption, loading: loading, slots: { loadingSkeleton: LoadingSkeleton }, getOptionLabel: getOptionLabel, isOptionEqualToValue: isOptionEqualToValue })), (0, jsx_runtime_1.jsx)(material_1.Box, Object.assign({ "aria-live": "polite", role: "status" }, { children: (0, jsx_runtime_1.jsx)(EmptyState, { noResultsText: noResultsText, filteredOptions: filteredOptions }) }))] })) }))] })), (0, jsx_runtime_1.jsxs)(material_1.Box, Object.assign({ "aria-live": "polite", role: "status", sx: Object.assign({}, utils_1.visuallyHidden), "aria-atomic": "true" }, { children: [loading && announcementBoxProps.loadingText, Array.isArray(currentValue) &&
currentValue.length > 0 &&
`${(_b = announcementBoxProps.selectionText) === null || _b === void 0 ? void 0 : _b.replace('%s', currentValue.map((opt) => getOptionLabel(opt)).join(', '))}`] }))] }));
};
exports.default = Autocomplete;