@primer/react
Version:
An implementation of GitHub's Primer Design System using React
376 lines (375 loc) • 14.7 kB
JavaScript
import { useId as useId$1 } from "../hooks/useId.js";
import Spinner from "../Spinner/Spinner.js";
import VisuallyHidden from "../_VisuallyHidden.js";
import { useFocusZone } from "../hooks/useFocusZone.js";
import { ActionList } from "../ActionList/index.js";
import { AutocompleteContext, AutocompleteDeferredInputContext } from "./AutocompleteContext.js";
import AutocompleteMenu_module_css_default from "./AutocompleteMenu.module.css.js";
import { c } from "react-compiler-runtime";
import { jsx, jsxs } from "react/jsx-runtime";
import React, { useContext, useEffect, useRef, useState } from "react";
import { scrollIntoView } from "@primer/behaviors";
import { announce } from "@primer/live-region-element";
import { isElement } from "react-is";
import { PlusIcon } from "@primer/octicons-react";
import { debounce } from "@github/mini-throttle";
//#region src/Autocomplete/AutocompleteMenu.tsx
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);
}
/**
* 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);
const deferredInputContext = useContext(AutocompleteDeferredInputContext);
if (autocompleteContext === null || deferredInputContext === null) throw new Error("AutocompleteContext returned null values");
const { activeDescendantRef, id, inputRef, scrollContainerRef, setAutocompleteSuggestion, setShowMenu, setInputValue, setIsMenuDirectlyActivated, setSelectedItemLength, showMenu } = autocompleteContext;
const { deferredInputValue } = deferredInputContext;
const { items, selectedItemIds, sortOnCloseFn, emptyStateText: t0, addNewItem, loading, selectionVariant: t1, filterFn, "aria-labelledby": ariaLabelledBy, onOpenChange, onSelectedChange, customScrollContainerRef } = props;
const emptyStateText = t0 === void 0 ? "No selectable options" : t0;
const selectionVariant = t1 === void 0 ? "single" : t1;
const listContainerRef = useRef(null);
let t2;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t2 = [];
$[0] = t2;
} else t2 = $[0];
const allItemsToRenderRef = useRef(t2);
const [highlightedItem, setHighlightedItem] = useState();
let t3;
if ($[1] !== items) {
t3 = items.map(_temp);
$[1] = items;
$[2] = t3;
} else t3 = $[2];
const [sortedItemIds, setSortedItemIds] = useState(t3);
const generatedUniqueId = useId$1(id);
let t4;
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) {
t4 = 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) : void 0,
onAction: (item) => {
const otherSelectedItemIds = selectedItemIds.filter((selectedItemId) => selectedItemId !== item.id);
const newSelectedItemIds = selectedItemIds.includes(item.id) ? otherSelectedItemIds : [...otherSelectedItemIds, item.id];
(onSelectedChange ? onSelectedChange : getdefaultCheckedSelectionChange(setInputValue))(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 || _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] = t4;
} else t4 = $[12];
const selectableItems = t4;
let t5;
if ($[13] !== sortedItemIds) {
t5 = sortedItemIds.reduce(_temp2, {});
$[13] = sortedItemIds;
$[14] = t5;
} else t5 = $[14];
const itemSortOrderData = t5;
let t6;
if ($[15] !== deferredInputValue || $[16] !== filterFn || $[17] !== itemSortOrderData || $[18] !== selectableItems) {
let t7;
if ($[20] !== itemSortOrderData) {
t7 = (a, b) => itemSortOrderData[a.id] - itemSortOrderData[b.id];
$[20] = itemSortOrderData;
$[21] = t7;
} else t7 = $[21];
t6 = selectableItems.filter(filterFn ? filterFn : getDefaultItemFilter(deferredInputValue)).sort(t7);
$[15] = deferredInputValue;
$[16] = filterFn;
$[17] = itemSortOrderData;
$[18] = selectableItems;
$[19] = t6;
} else t6 = $[19];
const sortedAndFilteredItemsToRender = t6;
let t7;
if ($[22] !== addNewItem || $[23] !== generatedUniqueId || $[24] !== (highlightedItem === null || highlightedItem === void 0 ? void 0 : highlightedItem.id) || $[25] !== selectedItemIds || $[26] !== selectionVariant || $[27] !== setAutocompleteSuggestion || $[28] !== setInputValue) {
t7 = 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) : void 0,
leadingVisual: _temp3,
onAction: (item_0) => {
addNewItem.handleAddItem({
...item_0,
id: item_0.id || generatedUniqueId,
leadingVisual: void 0
});
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] = t7;
} else t7 = $[29];
let t8;
if ($[30] !== sortedAndFilteredItemsToRender || $[31] !== t7) {
t8 = [...sortedAndFilteredItemsToRender, ...t7];
$[30] = sortedAndFilteredItemsToRender;
$[31] = t7;
$[32] = t8;
} else t8 = $[32];
const allItemsToRender = t8;
let t9;
if ($[33] !== allItemsToRender) {
t9 = () => {
allItemsToRenderRef.current = allItemsToRender;
};
$[33] = allItemsToRender;
$[34] = t9;
} else t9 = $[34];
React.useEffect(t9);
let t10;
let t11;
if ($[35] !== allItemsToRender || $[36] !== emptyStateText) {
t10 = () => {
if (allItemsToRender.length === 0) debounceAnnouncement(emptyStateText);
};
t11 = [allItemsToRender, emptyStateText];
$[35] = allItemsToRender;
$[36] = emptyStateText;
$[37] = t10;
$[38] = t11;
} else {
t10 = $[37];
t11 = $[38];
}
React.useEffect(t10, t11);
let t12;
if ($[39] !== activeDescendantRef || $[40] !== customScrollContainerRef || $[41] !== scrollContainerRef || $[42] !== setIsMenuDirectlyActivated) {
t12 = (current, _previous, directlyActivated) => {
activeDescendantRef.current = current || null;
if (current) {
setHighlightedItem(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"));
}));
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] = t12;
} else t12 = $[43];
let t13;
if ($[44] !== inputRef || $[45] !== t12) {
t13 = {
containerRef: listContainerRef,
focusOutBehavior: "wrap",
focusableElementFilter: _temp4,
activeDescendantFocus: inputRef,
onActiveDescendantChanged: t12
};
$[44] = inputRef;
$[45] = t12;
$[46] = t13;
} else t13 = $[46];
let t14;
if ($[47] !== loading) {
t14 = [loading];
$[47] = loading;
$[48] = t14;
} else t14 = $[48];
useFocusZone(t13, t14);
let t15;
let t16;
if ($[49] !== deferredInputValue || $[50] !== highlightedItem || $[51] !== selectedItemIds || $[52] !== setAutocompleteSuggestion) {
t15 = () => {
var _highlightedItem$text;
if (highlightedItem !== null && highlightedItem !== void 0 && (_highlightedItem$text = highlightedItem.text) !== null && _highlightedItem$text !== void 0 && _highlightedItem$text.startsWith(deferredInputValue) && !selectedItemIds.includes(highlightedItem.id)) setAutocompleteSuggestion(highlightedItem.text);
else setAutocompleteSuggestion("");
};
t16 = [
highlightedItem,
deferredInputValue,
selectedItemIds,
setAutocompleteSuggestion
];
$[49] = deferredInputValue;
$[50] = highlightedItem;
$[51] = selectedItemIds;
$[52] = setAutocompleteSuggestion;
$[53] = t15;
$[54] = t16;
} else {
t15 = $[53];
t16 = $[54];
}
useEffect(t15, t16);
let t17;
let t18;
if ($[55] !== onOpenChange || $[56] !== selectedItemIds || $[57] !== showMenu || $[58] !== sortOnCloseFn || $[59] !== sortedItemIds) {
t17 = () => {
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));
};
t18 = [
showMenu,
onOpenChange,
selectedItemIds,
sortOnCloseFn,
sortedItemIds
];
$[55] = onOpenChange;
$[56] = selectedItemIds;
$[57] = showMenu;
$[58] = sortOnCloseFn;
$[59] = sortedItemIds;
$[60] = t17;
$[61] = t18;
} else {
t17 = $[60];
t18 = $[61];
}
useEffect(t17, t18);
let t19;
if ($[62] !== selectedItemIds.length || $[63] !== setSelectedItemLength) {
t19 = () => {
if (selectedItemIds.length) setSelectedItemLength(selectedItemIds.length);
};
$[62] = selectedItemIds.length;
$[63] = setSelectedItemLength;
$[64] = t19;
} else t19 = $[64];
let t20;
if ($[65] !== selectedItemIds || $[66] !== setSelectedItemLength) {
t20 = [selectedItemIds, setSelectedItemLength];
$[65] = selectedItemIds;
$[66] = setSelectedItemLength;
$[67] = t20;
} else t20 = $[67];
useEffect(t19, t20);
if (selectionVariant === "single" && selectedItemIds.length > 1) throw new Error("Autocomplete: selectionVariant \"single\" cannot be used with multiple selected items");
let t21;
if ($[68] !== allItemsToRender || $[69] !== ariaLabelledBy || $[70] !== emptyStateText || $[71] !== id || $[72] !== loading || $[73] !== selectionVariant) {
t21 = loading ? /*#__PURE__*/ jsx("div", {
className: AutocompleteMenu_module_css_default.SpinnerWrapper,
children: /*#__PURE__*/ jsx(Spinner, {})
}) : /*#__PURE__*/ jsx("div", {
ref: listContainerRef,
children: allItemsToRender.length ? /*#__PURE__*/ jsx(ActionList, {
"data-component": "Autocomplete.Menu",
selectionVariant,
role: "listbox",
id: `${id}-listbox`,
"aria-labelledby": ariaLabelledBy,
children: allItemsToRender.map(_temp5)
}) : emptyStateText !== false && emptyStateText !== null ? /*#__PURE__*/ jsx("div", {
className: AutocompleteMenu_module_css_default.EmptyStateWrapper,
children: emptyStateText
}) : null
});
$[68] = allItemsToRender;
$[69] = ariaLabelledBy;
$[70] = emptyStateText;
$[71] = id;
$[72] = loading;
$[73] = selectionVariant;
$[74] = t21;
} else t21 = $[74];
let t22;
if ($[75] !== showMenu || $[76] !== t21) {
t22 = /*#__PURE__*/ jsx(VisuallyHidden, {
isVisible: showMenu,
children: t21
});
$[75] = showMenu;
$[76] = t21;
$[77] = t22;
} else t22 = $[77];
return t22;
}
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,
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");
//#endregion
export { AutocompleteMenu as default };