UNPKG

@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
"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;