@primer/react
Version:
An implementation of GitHub's Primer Design System using React
457 lines (452 loc) • 15.5 kB
JavaScript
import { c } from 'react-compiler-runtime';
import React, { useContext, useRef, useState, useEffect } from 'react';
import { debounce } from '@github/mini-throttle';
import { announce } from '@primer/live-region-element';
import { scrollIntoView } from '@primer/behaviors';
import { ActionList } from '../ActionList/index.js';
import { useFocusZone } from '../hooks/useFocusZone.js';
import { useId } from '../hooks/useId.js';
import { AutocompleteContext } from './AutocompleteContext.js';
import { PlusIcon } from '@primer/octicons-react';
import VisuallyHidden from '../_VisuallyHidden.js';
import { isElement } from 'react-is';
import classes from './AutocompleteMenu.module.css.js';
import { jsx, jsxs } from 'react/jsx-runtime';
import Spinner from '../Spinner/Spinner.js';
const getDefaultSortFn = isItemSelectedFn => (itemIdA, itemIdB) => isItemSelectedFn(itemIdA) === isItemSelectedFn(itemIdB) ? 0 : isItemSelectedFn(itemIdA) ? -1 : 1;
const menuScrollMargins = {
startMargin: 0,
endMargin: 8
};
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 getdefaultCheckedSelectionChange(setInputValueFn) {
return function (itemOrItems) {
const {
text = ''
} = Array.isArray(itemOrItems) ? itemOrItems.slice(-1)[0] : itemOrItems;
setInputValueFn(text);
};
}
const isItemSelected = (itemId, selectedItemIds) => selectedItemIds.includes(itemId);
function getItemById(itemId, items) {
return items.find(item => item.id === itemId);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
/**
* Announces a message to screen readers at a slowed-down rate. This is useful when you want to announce don't want to
* overwhelm the user with too many announcements in rapid succession.
*/
const debounceAnnouncement = debounce(announcement => {
announce(announcement);
}, 250);
function AutocompleteMenu(props) {
const $ = c(78);
const autocompleteContext = useContext(AutocompleteContext);
if (autocompleteContext === null) {
throw new Error("AutocompleteContext returned null values");
}
const {
activeDescendantRef,
id,
inputRef,
inputValue: t0,
scrollContainerRef,
setAutocompleteSuggestion,
setShowMenu,
setInputValue,
setIsMenuDirectlyActivated,
setSelectedItemLength,
showMenu
} = autocompleteContext;
const inputValue = t0 === undefined ? "" : t0;
const {
items,
selectedItemIds,
sortOnCloseFn,
emptyStateText: t1,
addNewItem,
loading,
selectionVariant: t2,
filterFn,
"aria-labelledby": ariaLabelledBy,
onOpenChange,
onSelectedChange,
customScrollContainerRef
} = props;
const emptyStateText = t1 === undefined ? "No selectable options" : t1;
const selectionVariant = t2 === undefined ? "single" : t2;
const listContainerRef = useRef(null);
let t3;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t3 = [];
$[0] = t3;
} else {
t3 = $[0];
}
const allItemsToRenderRef = useRef(t3);
const [highlightedItem, setHighlightedItem] = useState();
let t4;
if ($[1] !== items) {
t4 = items.map(_temp);
$[1] = items;
$[2] = t4;
} else {
t4 = $[2];
}
const [sortedItemIds, setSortedItemIds] = useState(t4);
const generatedUniqueId = useId(id);
let t5;
if ($[3] !== (highlightedItem === null || highlightedItem === void 0 ? void 0 : highlightedItem.id) || $[4] !== inputRef || $[5] !== items || $[6] !== onSelectedChange || $[7] !== selectedItemIds || $[8] !== selectionVariant || $[9] !== setAutocompleteSuggestion || $[10] !== setInputValue || $[11] !== setShowMenu) {
t5 = items.map(selectableItem => ({
...selectableItem,
role: "option",
id: selectableItem.id,
active: (highlightedItem === null || highlightedItem === void 0 ? void 0 : highlightedItem.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];
const onSelectedChangeFn = onSelectedChange ? onSelectedChange : getdefaultCheckedSelectionChange(setInputValue);
onSelectedChangeFn(newSelectedItemIds.map(newSelectedItemId => getItemById(newSelectedItemId, items)));
if (selectionVariant === "multiple") {
setInputValue("");
setAutocompleteSuggestion("");
} else {
var _inputRef$current;
setShowMenu(false);
(_inputRef$current = inputRef.current) === null || _inputRef$current === void 0 ? void 0 : _inputRef$current.setSelectionRange(inputRef.current.value.length, inputRef.current.value.length);
}
}
}));
$[3] = highlightedItem === null || highlightedItem === void 0 ? void 0 : highlightedItem.id;
$[4] = inputRef;
$[5] = items;
$[6] = onSelectedChange;
$[7] = selectedItemIds;
$[8] = selectionVariant;
$[9] = setAutocompleteSuggestion;
$[10] = setInputValue;
$[11] = setShowMenu;
$[12] = t5;
} else {
t5 = $[12];
}
const selectableItems = t5;
let t6;
if ($[13] !== sortedItemIds) {
t6 = sortedItemIds.reduce(_temp2, {});
$[13] = sortedItemIds;
$[14] = t6;
} else {
t6 = $[14];
}
const itemSortOrderData = t6;
let t7;
if ($[15] !== filterFn || $[16] !== inputValue || $[17] !== itemSortOrderData || $[18] !== selectableItems) {
let t8;
if ($[20] !== itemSortOrderData) {
t8 = (a, b) => itemSortOrderData[a.id] - itemSortOrderData[b.id];
$[20] = itemSortOrderData;
$[21] = t8;
} else {
t8 = $[21];
}
t7 = selectableItems.filter(filterFn ? filterFn : getDefaultItemFilter(inputValue)).sort(t8);
$[15] = filterFn;
$[16] = inputValue;
$[17] = itemSortOrderData;
$[18] = selectableItems;
$[19] = t7;
} else {
t7 = $[19];
}
const sortedAndFilteredItemsToRender = t7;
let t8;
if ($[22] !== addNewItem || $[23] !== generatedUniqueId || $[24] !== (highlightedItem === null || highlightedItem === void 0 ? void 0 : highlightedItem.id) || $[25] !== selectedItemIds || $[26] !== selectionVariant || $[27] !== setAutocompleteSuggestion || $[28] !== setInputValue) {
t8 = addNewItem ? [{
...addNewItem,
role: "option",
key: addNewItem.id,
active: (highlightedItem === null || highlightedItem === void 0 ? void 0 : highlightedItem.id) === addNewItem.id,
selected: selectionVariant === "multiple" ? selectedItemIds.includes(addNewItem.id) : undefined,
leadingVisual: _temp3,
onAction: item_0 => {
addNewItem.handleAddItem({
...item_0,
id: item_0.id || generatedUniqueId,
leadingVisual: undefined
});
if (selectionVariant === "multiple") {
setInputValue("");
setAutocompleteSuggestion("");
}
}
}] : [];
$[22] = addNewItem;
$[23] = generatedUniqueId;
$[24] = highlightedItem === null || highlightedItem === void 0 ? void 0 : highlightedItem.id;
$[25] = selectedItemIds;
$[26] = selectionVariant;
$[27] = setAutocompleteSuggestion;
$[28] = setInputValue;
$[29] = t8;
} else {
t8 = $[29];
}
let t9;
if ($[30] !== sortedAndFilteredItemsToRender || $[31] !== t8) {
t9 = [...sortedAndFilteredItemsToRender, ...t8];
$[30] = sortedAndFilteredItemsToRender;
$[31] = t8;
$[32] = t9;
} else {
t9 = $[32];
}
const allItemsToRender = t9;
let t10;
if ($[33] !== allItemsToRender) {
t10 = () => {
allItemsToRenderRef.current = allItemsToRender;
};
$[33] = allItemsToRender;
$[34] = t10;
} else {
t10 = $[34];
}
React.useEffect(t10);
let t11;
let t12;
if ($[35] !== allItemsToRender || $[36] !== emptyStateText) {
t11 = () => {
if (allItemsToRender.length === 0) {
debounceAnnouncement(emptyStateText);
}
};
t12 = [allItemsToRender, emptyStateText];
$[35] = allItemsToRender;
$[36] = emptyStateText;
$[37] = t11;
$[38] = t12;
} else {
t11 = $[37];
t12 = $[38];
}
React.useEffect(t11, t12);
let t13;
if ($[39] !== activeDescendantRef || $[40] !== customScrollContainerRef || $[41] !== scrollContainerRef || $[42] !== setIsMenuDirectlyActivated) {
t13 = (current, _previous, directlyActivated) => {
activeDescendantRef.current = current || null;
if (current) {
const selectedItem = allItemsToRenderRef.current.find(item_1 => {
var _current$closest;
return item_1.id === ((_current$closest = current.closest("li")) === null || _current$closest === void 0 ? void 0 : _current$closest.getAttribute("data-id"));
});
setHighlightedItem(selectedItem);
setIsMenuDirectlyActivated(directlyActivated);
}
if (current && customScrollContainerRef && customScrollContainerRef.current && directlyActivated) {
scrollIntoView(current, customScrollContainerRef.current, menuScrollMargins);
} else {
if (current && scrollContainerRef.current && directlyActivated) {
scrollIntoView(current, scrollContainerRef.current, menuScrollMargins);
}
}
};
$[39] = activeDescendantRef;
$[40] = customScrollContainerRef;
$[41] = scrollContainerRef;
$[42] = setIsMenuDirectlyActivated;
$[43] = t13;
} else {
t13 = $[43];
}
let t14;
if ($[44] !== inputRef || $[45] !== t13) {
t14 = {
containerRef: listContainerRef,
focusOutBehavior: "wrap",
focusableElementFilter: _temp4,
activeDescendantFocus: inputRef,
onActiveDescendantChanged: t13
};
$[44] = inputRef;
$[45] = t13;
$[46] = t14;
} else {
t14 = $[46];
}
let t15;
if ($[47] !== loading) {
t15 = [loading];
$[47] = loading;
$[48] = t15;
} else {
t15 = $[48];
}
useFocusZone(t14, t15);
let t16;
let t17;
if ($[49] !== highlightedItem || $[50] !== inputValue || $[51] !== selectedItemIds || $[52] !== setAutocompleteSuggestion) {
t16 = () => {
var _highlightedItem$text;
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("");
}
};
t17 = [highlightedItem, inputValue, selectedItemIds, setAutocompleteSuggestion];
$[49] = highlightedItem;
$[50] = inputValue;
$[51] = selectedItemIds;
$[52] = setAutocompleteSuggestion;
$[53] = t16;
$[54] = t17;
} else {
t16 = $[53];
t17 = $[54];
}
useEffect(t16, t17);
let t18;
let t19;
if ($[55] !== onOpenChange || $[56] !== selectedItemIds || $[57] !== showMenu || $[58] !== sortOnCloseFn || $[59] !== sortedItemIds) {
t18 = () => {
const itemIdSortResult = [...sortedItemIds].sort(sortOnCloseFn ? sortOnCloseFn : getDefaultSortFn(itemId_0 => isItemSelected(itemId_0, selectedItemIds)));
const sortResultMatchesState = itemIdSortResult.length === sortedItemIds.length && itemIdSortResult.every((element_0, index) => element_0 === sortedItemIds[index]);
if (showMenu === false && !sortResultMatchesState) {
setSortedItemIds(itemIdSortResult);
}
onOpenChange && onOpenChange(Boolean(showMenu));
};
t19 = [showMenu, onOpenChange, selectedItemIds, sortOnCloseFn, sortedItemIds];
$[55] = onOpenChange;
$[56] = selectedItemIds;
$[57] = showMenu;
$[58] = sortOnCloseFn;
$[59] = sortedItemIds;
$[60] = t18;
$[61] = t19;
} else {
t18 = $[60];
t19 = $[61];
}
useEffect(t18, t19);
let t20;
if ($[62] !== selectedItemIds.length || $[63] !== setSelectedItemLength) {
t20 = () => {
if (selectedItemIds.length) {
setSelectedItemLength(selectedItemIds.length);
}
};
$[62] = selectedItemIds.length;
$[63] = setSelectedItemLength;
$[64] = t20;
} else {
t20 = $[64];
}
let t21;
if ($[65] !== selectedItemIds || $[66] !== setSelectedItemLength) {
t21 = [selectedItemIds, setSelectedItemLength];
$[65] = selectedItemIds;
$[66] = setSelectedItemLength;
$[67] = t21;
} else {
t21 = $[67];
}
useEffect(t20, t21);
if (selectionVariant === "single" && selectedItemIds.length > 1) {
throw new Error("Autocomplete: selectionVariant \"single\" cannot be used with multiple selected items");
}
let t22;
if ($[68] !== allItemsToRender || $[69] !== ariaLabelledBy || $[70] !== emptyStateText || $[71] !== id || $[72] !== loading || $[73] !== selectionVariant) {
t22 = loading ? /*#__PURE__*/jsx("div", {
className: classes.SpinnerWrapper,
children: /*#__PURE__*/jsx(Spinner, {})
}) : /*#__PURE__*/jsx("div", {
ref: listContainerRef,
children: allItemsToRender.length ? /*#__PURE__*/jsx(ActionList, {
selectionVariant: selectionVariant,
role: "listbox",
id: `${id}-listbox`,
"aria-labelledby": ariaLabelledBy,
children: allItemsToRender.map(_temp5)
}) : emptyStateText !== false && emptyStateText !== null ? /*#__PURE__*/jsx("div", {
className: classes.EmptyStateWrapper,
children: emptyStateText
}) : null
});
$[68] = allItemsToRender;
$[69] = ariaLabelledBy;
$[70] = emptyStateText;
$[71] = id;
$[72] = loading;
$[73] = selectionVariant;
$[74] = t22;
} else {
t22 = $[74];
}
let t23;
if ($[75] !== showMenu || $[76] !== t22) {
t23 = /*#__PURE__*/jsx(VisuallyHidden, {
isVisible: showMenu,
children: t22
});
$[75] = showMenu;
$[76] = t22;
$[77] = t23;
} else {
t23 = $[77];
}
return t23;
}
function _temp5(item_2) {
const {
id: id_0,
onAction,
children,
text,
leadingVisual: LeadingVisual,
trailingVisual: TrailingVisual,
key,
role,
...itemProps
} = item_2;
return /*#__PURE__*/jsxs(ActionList.Item, {
onSelect: () => onAction(item_2),
...itemProps,
id: id_0,
"data-id": id_0,
role: role,
children: [LeadingVisual && /*#__PURE__*/jsx(ActionList.LeadingVisual, {
children: isElement(LeadingVisual) ? LeadingVisual : /*#__PURE__*/jsx(LeadingVisual, {})
}), children !== null && children !== void 0 ? children : text, TrailingVisual && /*#__PURE__*/jsx(ActionList.TrailingVisual, {
children: isElement(TrailingVisual) ? TrailingVisual : /*#__PURE__*/jsx(TrailingVisual, {})
})]
}, key !== null && key !== void 0 ? key : id_0);
}
_temp5.displayName = "_temp5";
function _temp4(element) {
return !(element instanceof HTMLInputElement);
}
function _temp3() {
return /*#__PURE__*/jsx(PlusIcon, {});
}
_temp3.displayName = "_temp3";
function _temp2(acc, curr, i) {
acc[curr] = i;
return acc;
}
function _temp(t0) {
const {
id: itemId
} = t0;
return itemId;
}
AutocompleteMenu.displayName = 'AutocompleteMenu';
AutocompleteMenu.__SLOT__ = Symbol('Autocomplete.Menu');
export { AutocompleteMenu as default };