UNPKG

@carbon/react

Version:

React components for the Carbon Design System

490 lines (488 loc) 17.4 kB
/** * 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;