@primer/components
Version:
Primer react components
222 lines (194 loc) • 9.41 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _react = _interopRequireWildcard(require("react"));
var _ActionList = require("../ActionList");
var _useFocusZone = require("../hooks/useFocusZone");
var _ = require("../");
var _AutocompleteContext = require("./AutocompleteContext");
var _octiconsReact = require("@primer/octicons-react");
var _uniqueId = require("../utils/uniqueId");
var _scrollIntoViewingArea = require("../utils/scrollIntoViewingArea");
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
const getDefaultSortFn = isItemSelectedFn => (itemIdA, itemIdB) => isItemSelectedFn(itemIdA) === isItemSelectedFn(itemIdB) ? 0 : isItemSelectedFn(itemIdA) ? -1 : 1;
function getDefaultItemFilter(filterValue) {
return function (item, _i) {
var _item$text;
return Boolean((_item$text = item.text) === null || _item$text === void 0 ? void 0 : _item$text.toLowerCase().startsWith(filterValue.toLowerCase()));
};
}
function getDefaultOnSelectionChange(setInputValueFn) {
return function (itemOrItems) {
const {
text = ''
} = Array.isArray(itemOrItems) ? itemOrItems.slice(-1)[0] : itemOrItems;
setInputValueFn && setInputValueFn(text);
};
}
const isItemSelected = (itemId, selectedItemIds) => selectedItemIds.includes(itemId);
function getItemById(itemId, items) {
return items.find(item => item.id === itemId);
}
;
// TODO: consider making 'aria-labelledby' required
function AutocompleteMenu(props) {
const {
activeDescendantRef,
id,
inputRef,
inputValue = '',
scrollContainerRef,
setAutocompleteSuggestion,
setShowMenu,
setInputValue,
setIsMenuDirectlyActivated,
setSelectedItemLength,
showMenu
} = (0, _react.useContext)(_AutocompleteContext.AutocompleteContext);
const {
items,
selectedItemIds,
sortOnCloseFn,
emptyStateText,
addNewItem,
loading,
selectionVariant,
filterFn = getDefaultItemFilter(inputValue),
"aria-labelledby": ariaLabelledBy,
onOpenChange,
onSelectedChange = getDefaultOnSelectionChange(setInputValue),
customScrollContainerRef
} = props;
const listContainerRef = (0, _react.useRef)(null);
const [highlightedItem, setHighlightedItem] = (0, _react.useState)();
const [sortedItemIds, setSortedItemIds] = (0, _react.useState)(items.map(({
id
}) => id));
const selectableItems = (0, _react.useMemo)(() => items.map(selectableItem => {
return { ...selectableItem,
role: "option",
id: selectableItem.id,
selected: selectionVariant === 'multiple' ? selectedItemIds.includes(selectableItem.id) : undefined,
onAction: item => {
const otherSelectedItemIds = selectedItemIds.filter(selectedItemId => selectedItemId !== item.id);
const newSelectedItemIds = selectedItemIds.includes(item.id) ? otherSelectedItemIds : [...otherSelectedItemIds, item.id];
onSelectedChange && onSelectedChange(newSelectedItemIds.map(newSelectedItemId => getItemById(newSelectedItemId, items)));
if (selectionVariant === 'multiple') {
setInputValue && setInputValue('');
setAutocompleteSuggestion && setAutocompleteSuggestion('');
} else {
var _inputRef$current;
setShowMenu && setShowMenu(false);
inputRef === null || inputRef === void 0 ? void 0 : (_inputRef$current = inputRef.current) === null || _inputRef$current === void 0 ? void 0 : _inputRef$current.setSelectionRange(inputRef.current.value.length, inputRef.current.value.length);
}
}
};
}), [items, selectedItemIds]);
const itemSortOrderData = (0, _react.useMemo)(() => sortedItemIds.reduce((acc, curr, i) => {
acc[curr] = i;
return acc;
}, {}), [sortedItemIds]);
const sortedAndFilteredItemsToRender = (0, _react.useMemo)(() => selectableItems.filter((item, i) => filterFn(item, i)).sort((a, b) => itemSortOrderData[a.id] - itemSortOrderData[b.id]), [selectableItems]);
const allItemsToRender = (0, _react.useMemo)(() => [// sorted and filtered selectable items
...sortedAndFilteredItemsToRender, // menu item used for creating a token from whatever is in the text input
...(addNewItem ? [{ ...addNewItem,
leadingVisual: () => /*#__PURE__*/_react.default.createElement(_octiconsReact.PlusIcon, null),
onAction: (item, e) => {
// TODO: make it possible to pass a leadingVisual when using `addNewItem`
addNewItem.handleAddItem({ ...item,
id: item.id || (0, _uniqueId.uniqueId)(),
leadingVisual: undefined
});
if (selectionVariant === 'multiple') {
setInputValue && setInputValue('');
setAutocompleteSuggestion && setAutocompleteSuggestion('');
}
}
}] : [])], [sortedAndFilteredItemsToRender, addNewItem]);
(0, _useFocusZone.useFocusZone)({
containerRef: listContainerRef,
focusOutBehavior: 'wrap',
focusableElementFilter: element => {
return !(element instanceof HTMLInputElement);
},
activeDescendantFocus: inputRef,
onActiveDescendantChanged: (current, _previous, directlyActivated) => {
if (activeDescendantRef) {
activeDescendantRef.current = current || null;
}
if (current) {
const selectedItem = selectableItems.find(item => item.id.toString() === (current === null || current === void 0 ? void 0 : current.dataset.id));
setHighlightedItem(selectedItem);
setIsMenuDirectlyActivated && setIsMenuDirectlyActivated(directlyActivated);
}
if (current && customScrollContainerRef && customScrollContainerRef.current && directlyActivated) {
(0, _scrollIntoViewingArea.scrollIntoViewingArea)(current, customScrollContainerRef.current);
} else if (current && scrollContainerRef && scrollContainerRef.current && directlyActivated) {
(0, _scrollIntoViewingArea.scrollIntoViewingArea)(current, scrollContainerRef.current);
}
}
}, [loading]);
(0, _react.useEffect)(() => {
var _highlightedItem$text;
if (!setAutocompleteSuggestion) {
return;
}
if (highlightedItem !== null && highlightedItem !== void 0 && (_highlightedItem$text = highlightedItem.text) !== null && _highlightedItem$text !== void 0 && _highlightedItem$text.startsWith(inputValue) && !selectedItemIds.includes(highlightedItem.id)) {
setAutocompleteSuggestion(highlightedItem.text);
} else {
setAutocompleteSuggestion('');
}
}, [highlightedItem, inputValue]);
(0, _react.useEffect)(() => {
if (showMenu === false) {
setSortedItemIds([...sortedItemIds].sort(sortOnCloseFn ? sortOnCloseFn : getDefaultSortFn(itemId => isItemSelected(itemId, selectedItemIds))));
}
onOpenChange && onOpenChange(Boolean(showMenu));
}, [showMenu]);
(0, _react.useEffect)(() => {
if (selectedItemIds.length) {
setSelectedItemLength && setSelectedItemLength(selectedItemIds.length);
}
}, [selectedItemIds]);
return /*#__PURE__*/_react.default.createElement(_.Box, {
sx: !showMenu ? {
// visually hides this label for sighted users
position: 'absolute',
width: '1px',
height: '1px',
padding: '0',
margin: '-1px',
overflow: 'hidden',
clip: 'rect(0, 0, 0, 0)',
whiteSpace: 'nowrap',
borderWidth: '0'
} : {}
}, loading ? /*#__PURE__*/_react.default.createElement(_.Box, {
p: 3,
display: "flex",
justifyContent: "center"
}, /*#__PURE__*/_react.default.createElement(_.Spinner, null)) : /*#__PURE__*/_react.default.createElement("div", {
ref: listContainerRef
}, allItemsToRender.length ? /*#__PURE__*/_react.default.createElement(_ActionList.ActionList, {
selectionVariant: "multiple" // have to typecast to `ItemProps` because we have an extra property
// on `items` for Autocomplete: `metadata`
,
items: allItemsToRender,
role: "listbox",
id: `${id}-listbox`,
"aria-labelledby": ariaLabelledBy
}) : /*#__PURE__*/_react.default.createElement(_.Box, {
p: 3
}, emptyStateText)));
}
AutocompleteMenu.displayName = "AutocompleteMenu";
AutocompleteMenu.defaultProps = {
emptyStateText: 'No selectable options',
selectionVariant: 'single'
};
AutocompleteMenu.displayName = 'AutocompleteMenu';
var _default = AutocompleteMenu;
exports.default = _default;