@carbon/react
Version:
React components for the Carbon Design System
490 lines (488 loc) • 17.4 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.
*/
const require_runtime = require("../../_virtual/_rolldown/runtime.js");
const require_setupGetInstanceId = require("../../tools/setupGetInstanceId.js");
const require_deprecate = require("../../prop-types/deprecate.js");
const require_events = require("../../tools/events.js");
const require_cells = require("./tools/cells.js");
const require_sorting = require("./state/sorting.js");
const require_getDerivedStateFromProps = require("./state/getDerivedStateFromProps.js");
const require_denormalize = require("./tools/denormalize.js");
const require_filter = require("./tools/filter.js");
const require_Table = require("./Table.js");
const require_TableActionList = require("./TableActionList.js");
const require_TableBatchAction = require("./TableBatchAction.js");
const require_TableBatchActions = require("./TableBatchActions.js");
const require_TableBody = require("./TableBody.js");
const require_TableCell = require("./TableCell.js");
const require_TableContainer = require("./TableContainer.js");
const require_TableDecoratorRow = require("./TableDecoratorRow.js");
const require_TableExpandHeader = require("./TableExpandHeader.js");
const require_TableSlugRow = require("./TableSlugRow.js");
const require_TableExpandRow = require("./TableExpandRow.js");
const require_TableExpandedRow = require("./TableExpandedRow.js");
const require_TableHead = require("./TableHead.js");
const require_TableHeader = require("./TableHeader.js");
const require_TableRow = require("./TableRow.js");
const require_TableSelectAll = require("./TableSelectAll.js");
const require_TableSelectRow = require("./TableSelectRow.js");
const require_TableToolbar = require("./TableToolbar.js");
const require_TableToolbarAction = require("./TableToolbarAction.js");
const require_TableToolbarContent = require("./TableToolbarContent.js");
const require_TableToolbarSearch = require("./TableToolbarSearch.js");
const require_TableToolbarMenu = require("./TableToolbarMenu.js");
let react = require("react");
let prop_types = require("prop-types");
prop_types = require_runtime.__toESM(prop_types);
let react_fast_compare = require("react-fast-compare");
react_fast_compare = require_runtime.__toESM(react_fast_compare);
//#region src/components/DataTable/DataTable.tsx
/**
* 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.
*/
const getInstanceId = require_setupGetInstanceId.setupGetInstanceId();
const translationIds = {
"carbon.table.row.expand": "carbon.table.row.expand",
"carbon.table.row.collapse": "carbon.table.row.collapse",
"carbon.table.all.expand": "carbon.table.all.expand",
"carbon.table.all.collapse": "carbon.table.all.collapse",
"carbon.table.all.select": "carbon.table.all.select",
"carbon.table.all.unselect": "carbon.table.all.unselect",
"carbon.table.row.select": "carbon.table.row.select",
"carbon.table.row.unselect": "carbon.table.row.unselect"
};
const defaultTranslations = {
[translationIds["carbon.table.all.expand"]]: "Expand all rows",
[translationIds["carbon.table.all.collapse"]]: "Collapse all rows",
[translationIds["carbon.table.row.expand"]]: "Expand current row",
[translationIds["carbon.table.row.collapse"]]: "Collapse current row",
[translationIds["carbon.table.all.select"]]: "Select all rows",
[translationIds["carbon.table.all.unselect"]]: "Unselect all rows",
[translationIds["carbon.table.row.select"]]: "Select row",
[translationIds["carbon.table.row.unselect"]]: "Unselect row"
};
const defaultTranslateWithId = (messageId) => {
return defaultTranslations[messageId];
};
/**
* DataTable components are used to represent a collection of resources,
* displaying a subset of their fields in columns, or headers. We prioritize
* direct updates to the state of what we're rendering, so internally we
* normalize the given data and then denormalize it at render time. Each part of
* the DataTable is accessible through look-up by ID, and updating the state of
* a single entity cascades updates to the consumer.
*/
const DataTable = (props) => {
const { children, filterRows = require_filter.defaultFilterRows, headers, render, translateWithId: t = defaultTranslateWithId, size, isSortable, useZebraStyles, useStaticWidth, stickyHeader, overflowMenuOnHover, experimentalAutoAlign, radio, rows } = props;
const instanceId = (0, react.useMemo)(() => getInstanceId(), []);
const [state, setState] = (0, react.useState)(() => ({
...require_getDerivedStateFromProps.default(props, {}),
isExpandedAll: false
}));
(0, react.useEffect)(() => {
const nextRowIds = rows.map((row) => row.id);
const nextHeaders = headers.map((header) => header.key);
const hasRowIdsChanged = !(0, react_fast_compare.default)(nextRowIds, state.rowIds);
const hasHeadersChanged = !(0, react_fast_compare.default)(nextHeaders, Array.from(new Set(Object.keys(state.cellsById).map((id) => id.split(":")[1]))));
const hasRowsChanged = !(0, react_fast_compare.default)(rows, state.rowIds.map((id) => {
const row = state.rowsById[id];
return {
id: row.id,
disabled: row.disabled,
isExpanded: row.isExpanded,
isSelected: row.isSelected
};
}));
if (hasRowIdsChanged || hasHeadersChanged || hasRowsChanged) setState((prev) => require_getDerivedStateFromProps.default(props, prev));
}, [headers, rows]);
const getHeaderProps = ({ header, onClick, isSortable: headerIsSortable, ...rest }) => {
const { sortDirection, sortHeaderKey } = state;
const { key, slug, decorator } = header;
return {
...rest,
key,
sortDirection,
isSortable: headerIsSortable ?? header.isSortable ?? isSortable,
isSortHeader: sortHeaderKey === key,
slug,
decorator,
onClick: (event) => {
const nextSortState = require_sorting.getNextSortState(props, state, { key });
setState((prev) => ({
...prev,
...nextSortState
}));
if (onClick) handleOnHeaderClick(onClick, {
sortHeaderKey: key,
sortDirection: nextSortState.sortDirection
})(event);
}
};
};
const getExpandHeaderProps = ({ onClick, onExpand, ...rest } = {}) => {
const { isExpandedAll, rowIds, rowsById } = state;
const isExpanded = isExpandedAll || rowIds.every((id) => rowsById[id].isExpanded);
const translationKey = isExpanded ? translationIds["carbon.table.all.collapse"] : translationIds["carbon.table.all.expand"];
const handlers = [handleOnExpandAll, onExpand];
if (onClick) handlers.push(handleOnExpandHeaderClick(onClick, { isExpanded }));
return {
...rest,
"aria-label": t(translationKey),
"aria-controls": rowIds.map((id) => `${getTablePrefix()}-expanded-row-${id}`).join(" "),
isExpanded,
onExpand: require_events.composeEventHandlers(handlers),
id: `${getTablePrefix()}-expand`
};
};
/**
* Wraps the consumer's `onClick` with sorting metadata.
*/
const handleOnHeaderClick = (onClick, sortParams) => {
return (event) => onClick(event, sortParams);
};
/**
* Wraps the consumer's `onClick` with sorting metadata.
*/
const handleOnExpandHeaderClick = (onClick, expandParams) => {
return (event) => onClick(event, expandParams);
};
const getRowProps = ({ row, onClick, ...rest }) => {
const translationKey = row.isExpanded ? translationIds["carbon.table.row.collapse"] : translationIds["carbon.table.row.expand"];
return {
...rest,
key: row.id,
onClick,
onExpand: require_events.composeEventHandlers([handleOnExpandRow(row.id), onClick]),
isExpanded: row.isExpanded,
"aria-label": t(translationKey),
"aria-controls": `${getTablePrefix()}-expanded-row-${row.id}`,
isSelected: row.isSelected,
disabled: row.disabled,
expandHeader: `${getTablePrefix()}-expand`
};
};
const getExpandedRowProps = ({ row, ...rest }) => {
return {
...rest,
id: `${getTablePrefix()}-expanded-row-${row.id}`
};
};
/**
* Gets the props associated with selection for a header or a row.
*/
const getSelectionProps = ({ onClick, row, ...rest } = {}) => {
if (row) {
const translationKey = row.isSelected ? translationIds["carbon.table.row.unselect"] : translationIds["carbon.table.row.select"];
return {
...rest,
checked: row.isSelected,
onSelect: require_events.composeEventHandlers([handleOnSelectRow(row.id), onClick]),
id: `${getTablePrefix()}__select-row-${row.id}`,
name: `select-row-${instanceId}`,
"aria-label": t(translationKey),
disabled: row.disabled,
radio
};
}
const rowCount = state.rowIds.length;
const selectedRowCount = selectedRows.length;
const checked = rowCount > 0 && selectedRowCount === rowCount;
const indeterminate = rowCount > 0 && selectedRowCount > 0 && selectedRowCount !== rowCount;
const translationKey = checked || indeterminate ? translationIds["carbon.table.all.unselect"] : translationIds["carbon.table.all.select"];
return {
...rest,
"aria-label": t(translationKey),
checked,
id: `${getTablePrefix()}__select-all`,
indeterminate,
name: `select-all-${instanceId}`,
onSelect: require_events.composeEventHandlers([handleSelectAll, onClick])
};
};
const getToolbarProps = (props) => {
const isSmall = size === "xs" || size === "sm";
return {
...props,
size: isSmall ? "sm" : void 0
};
};
const getBatchActionProps = (props) => {
const { shouldShowBatchActions } = state;
const selectedRowCount = selectedRows.length;
return {
onSelectAll: void 0,
totalCount: state.rowIds.length,
...props,
shouldShowBatchActions: shouldShowBatchActions && selectedRowCount > 0,
totalSelected: selectedRowCount,
onCancel: handleOnCancel
};
};
const getTableProps = () => {
return {
useZebraStyles,
size: size ?? "lg",
isSortable,
useStaticWidth,
stickyHeader,
overflowMenuOnHover: overflowMenuOnHover ?? false,
experimentalAutoAlign
};
};
const getTableContainerProps = () => {
return {
stickyHeader,
useStaticWidth
};
};
const getCellProps = ({ cell: { hasAILabelHeader, id }, ...rest }) => {
return {
...rest,
hasAILabelHeader,
key: id
};
};
/**
* Selected row IDs, excluding disabled rows.
*/
const selectedRows = state.rowIds.filter((id) => {
const row = state.rowsById[id];
return row.isSelected && !row.disabled;
});
const filteredRowIds = typeof state.filterInputValue === "string" ? filterRows({
cellsById: state.cellsById,
getCellId: require_cells.getCellId,
headers,
inputValue: state.filterInputValue,
rowIds: state.rowIds
}) : state.rowIds;
/**
* Generates a prefix for table related IDs.
*/
const getTablePrefix = () => `data-table-${instanceId}`;
/**
* Generates a new `rowsById` object with updated selection state.
*/
const getUpdatedSelectionState = (initialState, isSelected) => {
const { rowIds } = initialState;
const isFiltered = rowIds.length !== filteredRowIds.length;
return { rowsById: rowIds.reduce((acc, id) => {
const row = { ...initialState.rowsById[id] };
if (!row.disabled && (!isFiltered || filteredRowIds.includes(id))) row.isSelected = isSelected;
acc[id] = row;
return acc;
}, {}) };
};
/**
* Handler for `onCancel` to hide the batch action toolbar and deselect all
* rows.
*/
const handleOnCancel = () => {
setState((prev) => {
return {
...prev,
shouldShowBatchActions: false,
...getUpdatedSelectionState(prev, false)
};
});
};
/**
* Handler for toggling the selection state of all rows.
*/
const handleSelectAll = () => {
setState((prev) => {
const { rowsById } = prev;
const isSelected = !Object.values(rowsById).filter((row) => row.isSelected && !row.disabled).length;
return {
...prev,
shouldShowBatchActions: isSelected,
...getUpdatedSelectionState(prev, isSelected)
};
});
};
/**
* Handler for toggling selection state of a given row.
*/
const handleOnSelectRow = (rowId) => () => {
setState((prev) => {
const row = prev.rowsById[rowId];
if (radio) {
const rowsById = Object.entries(prev.rowsById).reduce((acc, [id, row]) => {
acc[id] = {
...row,
isSelected: false
};
return acc;
}, {});
return {
...prev,
shouldShowBatchActions: false,
rowsById: {
...rowsById,
[rowId]: {
...rowsById[rowId],
isSelected: !rowsById[rowId].isSelected
}
}
};
}
const selectedRows = prev.rowIds.filter((id) => prev.rowsById[id].isSelected).length;
const selectedRowsCount = !row.isSelected ? selectedRows + 1 : selectedRows - 1;
return {
...prev,
shouldShowBatchActions: !row.isSelected || selectedRowsCount > 0,
rowsById: {
...prev.rowsById,
[rowId]: {
...row,
isSelected: !row.isSelected
}
}
};
});
};
const handleOnExpandRow = (rowId) => () => {
setState((prev) => {
const row = prev.rowsById[rowId];
const { isExpandedAll } = prev;
return {
...prev,
isExpandedAll: row.isExpanded ? false : isExpandedAll,
rowsById: {
...prev.rowsById,
[rowId]: {
...row,
isExpanded: !row.isExpanded
}
}
};
});
};
const handleOnExpandAll = () => {
setState((prev) => {
const { rowIds, isExpandedAll } = prev;
return {
...prev,
isExpandedAll: !isExpandedAll,
rowsById: rowIds.reduce((acc, id) => {
acc[id] = {
...prev.rowsById[id],
isExpanded: !isExpandedAll
};
return acc;
}, {})
};
});
};
/**
* Transitions to the next sort state of the table.
*/
const handleSortBy = (headerKey) => () => {
setState((prev) => {
const sortState = require_sorting.getNextSortState(props, prev, { key: headerKey });
return {
...prev,
...sortState
};
});
};
/**
* Event handler for table filter input changes.
*/
const handleOnInputValueChange = (event, defaultValue) => {
const value = defaultValue ?? (event === "" ? event : event.target.value);
setState((prev) => ({
...prev,
filterInputValue: value
}));
};
const renderProps = {
rows: require_denormalize.default(filteredRowIds, state.rowsById, state.cellsById),
headers,
selectedRows: require_denormalize.default(selectedRows, state.rowsById, state.cellsById),
getHeaderProps,
getExpandHeaderProps,
getRowProps,
getExpandedRowProps,
getSelectionProps,
getToolbarProps,
getBatchActionProps,
getTableProps,
getTableContainerProps,
getCellProps,
onInputChange: handleOnInputValueChange,
sortBy: (headerKey) => handleSortBy(headerKey)(),
selectAll: handleSelectAll,
selectRow: (rowId) => handleOnSelectRow(rowId)(),
expandRow: (rowId) => handleOnExpandRow(rowId)(),
expandAll: handleOnExpandAll,
radio
};
if (typeof render !== "undefined") return render(renderProps);
if (typeof children !== "undefined") return children(renderProps);
return null;
};
DataTable.Table = require_Table.default;
DataTable.TableActionList = require_TableActionList.default;
DataTable.TableBatchAction = require_TableBatchAction.default;
DataTable.TableBatchActions = require_TableBatchActions.default;
DataTable.TableBody = require_TableBody.default;
DataTable.TableCell = require_TableCell.default;
DataTable.TableContainer = require_TableContainer.default;
DataTable.TableDecoratorRow = require_TableDecoratorRow.default;
DataTable.TableExpandHeader = require_TableExpandHeader.default;
DataTable.TableExpandRow = require_TableExpandRow.default;
DataTable.TableExpandedRow = require_TableExpandedRow.default;
DataTable.TableHead = require_TableHead.default;
DataTable.TableHeader = require_TableHeader.default;
DataTable.TableRow = require_TableRow.default;
DataTable.TableSelectAll = require_TableSelectAll.default;
DataTable.TableSelectRow = require_TableSelectRow.default;
DataTable.TableSlugRow = require_TableSlugRow.default;
DataTable.TableToolbar = require_TableToolbar.default;
DataTable.TableToolbarAction = require_TableToolbarAction.default;
DataTable.TableToolbarContent = require_TableToolbarContent.default;
DataTable.TableToolbarSearch = require_TableToolbarSearch.default;
DataTable.TableToolbarMenu = require_TableToolbarMenu.default;
DataTable.propTypes = {
children: prop_types.default.func,
experimentalAutoAlign: prop_types.default.bool,
filterRows: prop_types.default.func,
headers: prop_types.default.arrayOf(prop_types.default.shape({
key: prop_types.default.string.isRequired,
header: prop_types.default.node.isRequired,
isSortable: prop_types.default.bool
})).isRequired,
isSortable: prop_types.default.bool,
locale: prop_types.default.string,
overflowMenuOnHover: prop_types.default.bool,
radio: prop_types.default.bool,
render: require_deprecate.deprecate(prop_types.default.func),
rows: prop_types.default.arrayOf(prop_types.default.shape({
id: prop_types.default.string.isRequired,
disabled: prop_types.default.bool,
isSelected: prop_types.default.bool,
isExpanded: prop_types.default.bool
})).isRequired,
size: prop_types.default.oneOf([
"xs",
"sm",
"md",
"lg",
"xl"
]),
sortRow: prop_types.default.func,
stickyHeader: prop_types.default.bool,
translateWithId: prop_types.default.func,
useStaticWidth: prop_types.default.bool,
useZebraStyles: prop_types.default.bool
};
//#endregion
exports.DataTable = DataTable;