@carbon/react
Version:
React components for the Carbon Design System
111 lines (109 loc) • 3.79 kB
JavaScript
/**
* Copyright IBM Corp. 2016, 2026
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/
import { useCallback, useEffect, useRef, useState } from "react";
import "react/jsx-runtime";
import isEqual from "react-fast-compare";
//#region src/internal/Selection.tsx
/**
* Copyright IBM Corp. 2016, 2025
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/
const callOnChangeHandler = ({ isControlled, isMounted, onChangeHandlerControlled, onChangeHandlerUncontrolled, selectedItems }) => {
if (isControlled) {
if (isMounted && onChangeHandlerControlled) setTimeout(() => {
onChangeHandlerControlled({ selectedItems });
}, 0);
} else onChangeHandlerUncontrolled(selectedItems);
};
const useSelection = ({ disabled, onChange, initialSelectedItems = [], selectedItems: controlledItems, selectAll = false, filteredItems = [] }) => {
const isMounted = useRef(false);
const savedOnChange = useRef(onChange);
const [uncontrolledItems, setUncontrolledItems] = useState(initialSelectedItems);
const isControlled = !!controlledItems;
const selectedItems = isControlled ? controlledItems : uncontrolledItems;
const onItemChange = useCallback((item) => {
if (disabled) return;
const allSelectableItems = filteredItems.filter((item) => !item?.disabled && !item?.isSelectAll);
const disabledItemCount = filteredItems.filter((item) => item?.disabled).length;
let newSelectedItems;
if (item?.isSelectAll && selectedItems.length > 0) newSelectedItems = [];
else if (item?.isSelectAll && selectedItems.length === 0) newSelectedItems = allSelectableItems;
else {
const selectedIndex = selectedItems.findLastIndex((selectedItem) => isEqual(selectedItem, item));
if (selectedIndex === -1) {
newSelectedItems = selectedItems.concat(item);
if (selectAll && filteredItems.length - 1 === newSelectedItems.length + disabledItemCount) newSelectedItems = allSelectableItems;
} else {
newSelectedItems = removeAtIndex(selectedItems, selectedIndex);
newSelectedItems = newSelectedItems.filter((item) => !item?.isSelectAll);
}
}
callOnChangeHandler({
isControlled,
isMounted: isMounted.current,
onChangeHandlerControlled: savedOnChange.current,
onChangeHandlerUncontrolled: setUncontrolledItems,
selectedItems: newSelectedItems
});
}, [
disabled,
selectedItems,
filteredItems,
selectAll,
isControlled
]);
const clearSelection = useCallback(() => {
if (disabled) return;
callOnChangeHandler({
isControlled,
isMounted: isMounted.current,
onChangeHandlerControlled: savedOnChange.current,
onChangeHandlerUncontrolled: setUncontrolledItems,
selectedItems: []
});
}, [disabled, isControlled]);
const toggleAll = useCallback((items) => {
callOnChangeHandler({
isControlled,
isMounted: isMounted.current,
onChangeHandlerControlled: savedOnChange.current,
onChangeHandlerUncontrolled: setUncontrolledItems,
selectedItems: items
});
}, [isControlled]);
useEffect(() => {
savedOnChange.current = onChange;
}, [onChange]);
useEffect(() => {
if (isMounted.current && savedOnChange.current && !isControlled) savedOnChange.current({ selectedItems });
}, [isControlled, selectedItems]);
useEffect(() => {
isMounted.current = true;
return () => {
isMounted.current = false;
};
}, []);
return {
clearSelection,
onItemChange,
toggleAll,
selectedItems
};
};
/**
* Generic utility for safely removing an element at a given index from an
* array.
*/
const removeAtIndex = (array, index) => {
const result = array.slice();
result.splice(index, 1);
return result;
};
//#endregion
export { useSelection };