@mui/x-data-grid-pro
Version:
The Pro plan edition of the MUI X Data Grid components.
170 lines (165 loc) • 7.26 kB
JavaScript
'use client';
import _extends from "@babel/runtime/helpers/esm/extends";
import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose";
const _excluded = ["values", "field", "columnWidth", "optionByValue", "getOptionLabel", "autoWrap", "classes", "slotProps"];
import * as React from 'react';
import clsx from 'clsx';
import { styled } from '@mui/material/styles';
import useForkRef from '@mui/utils/useForkRef';
import useEnhancedEffect from '@mui/utils/useEnhancedEffect';
import { gridClasses, gridResizingColumnFieldSelector, useGridRootProps, useGridSelector } from '@mui/x-data-grid';
import { NotRendered, useSyncExternalStore, useTimeout } from '@mui/x-data-grid/internals';
import { DEFAULT_GAP, DEFAULT_OVERFLOW_CHIP_WIDTHS, calculateVisibleCount } from "../../utils/multiSelectCellUtils.mjs";
import { useGridPrivateApiContext } from "../../hooks/utils/useGridPrivateApiContext.mjs";
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
export const MultiSelectChipsRoot = styled('div', {
name: 'MuiDataGrid',
slot: 'MultiSelectChips',
shouldForwardProp: prop => prop !== 'ownerState'
})({
display: 'flex',
alignItems: 'center',
width: '100%',
height: '100%',
gap: 4,
overflow: 'hidden',
position: 'relative'
});
const Chip = styled(NotRendered, {
name: 'MuiDataGrid',
slot: 'MultiSelectChip'
})({
[`&.${gridClasses['multiSelectCellChip--hidden']}`]: {
display: 'none'
}
});
// Delay before revealing all chips on resize start, so a quick double-click (autosize) — which
// toggles the resizing state in short bursts — doesn't flash every chip.
const RESIZE_REVEAL_DELAY_MS = 150;
function GridMultiSelectChipsImpl(props, ref) {
const {
values,
field,
columnWidth,
optionByValue,
getOptionLabel,
autoWrap,
classes,
slotProps
} = props,
other = _objectWithoutPropertiesLoose(props, _excluded);
const rootProps = useGridRootProps();
const privateApiRef = useGridPrivateApiContext();
const ownerState = rootProps;
const containerRef = React.useRef(null);
const handleRef = useForkRef(containerRef, ref);
const chipsRef = React.useRef(new Map());
const chipWidthsRef = React.useRef(new Map());
const [measuredCount, setMeasuredCount] = React.useState(0);
const [containerWidth, setContainerWidth] = React.useState(null);
const subscribeMetrics = React.useCallback(notify => {
const cache = privateApiRef.current.caches.multiSelect;
if (!cache) {
return () => {};
}
return cache.subscribeOverflowMetrics(notify);
}, [privateApiRef]);
const getMetricsSnapshot = React.useCallback(() => privateApiRef.current.caches.multiSelect?.getOverflowMetrics() ?? null, [privateApiRef]);
const overflowMetrics = useSyncExternalStore(subscribeMetrics, getMetricsSnapshot, getMetricsSnapshot);
const arrayKey = React.useMemo(() => values.join('\0'), [values]);
const [prevArrayKey, setPrevArrayKey] = React.useState(arrayKey);
if (!autoWrap && prevArrayKey !== arrayKey) {
setPrevArrayKey(arrayKey);
chipWidthsRef.current.clear();
setMeasuredCount(0);
setContainerWidth(null);
}
// Re-measure on committed column width changes (incl. when a resize finishes); we don't
// track live drag, matching how other cells reflow only once the resize is committed.
useEnhancedEffect(() => {
if (autoWrap || !containerRef.current) {
return;
}
setContainerWidth(containerRef.current.getBoundingClientRect().width);
}, [columnWidth, autoWrap]);
// Measure chip and container widths after render. `getBoundingClientRect` is sub-pixel,
// which keeps the chip-fit math aligned with the browser's actual layout.
useEnhancedEffect(() => {
if (autoWrap) {
return;
}
if (containerWidth === null && containerRef.current) {
setContainerWidth(containerRef.current.getBoundingClientRect().width);
}
let newMeasurements = 0;
chipsRef.current.forEach((chipEl, index) => {
if (chipEl && !chipWidthsRef.current.has(index)) {
chipWidthsRef.current.set(index, chipEl.getBoundingClientRect().width);
newMeasurements += 1;
}
});
if (newMeasurements > 0) {
setMeasuredCount(chipWidthsRef.current.size);
}
}, [containerWidth, autoWrap]);
const overflowWidths = overflowMetrics?.overflowChipWidths ?? DEFAULT_OVERFLOW_CHIP_WIDTHS;
const gap = overflowMetrics?.gap ?? DEFAULT_GAP;
// While this column is being drag-resized, render every chip and let the cell clip them.
// The `+N` overflow is recomputed once the resize is committed (see the `columnWidth` effect).
const isResizingThisColumn = useGridSelector(privateApiRef, gridResizingColumnFieldSelector) === field;
// Debounce the reveal so a quick double-click to autosize doesn't flash all chips.
const revealTimeout = useTimeout();
const [revealWhileResizing, setRevealWhileResizing] = React.useState(false);
React.useEffect(() => {
if (!isResizingThisColumn) {
revealTimeout.clear();
setRevealWhileResizing(false);
return;
}
revealTimeout.start(RESIZE_REVEAL_DELAY_MS, () => setRevealWhileResizing(true));
}, [isResizingThisColumn, revealTimeout]);
const visibleCount = React.useMemo(() => {
if (autoWrap || revealWhileResizing || values.length === 0) {
return values.length;
}
if (containerWidth === null || containerWidth === 0 || measuredCount < values.length) {
return values.length;
}
return calculateVisibleCount(values.length, containerWidth, chipWidthsRef.current, overflowWidths, gap);
}, [values.length, measuredCount, containerWidth, autoWrap, revealWhileResizing, overflowWidths, gap]);
const hiddenCount = values.length - visibleCount;
return /*#__PURE__*/_jsxs(MultiSelectChipsRoot, _extends({
ref: handleRef,
ownerState: ownerState
}, other, slotProps?.root, {
className: clsx(classes?.root, other?.className, slotProps?.root?.className),
children: [values.map((v, index) => {
const option = optionByValue.get(v) ?? v;
const chipProps = typeof slotProps?.chip === 'function' ? slotProps.chip(option, index) : slotProps?.chip;
return /*#__PURE__*/_jsx(Chip, _extends({
as: rootProps.slots.baseChip,
ownerState: ownerState,
ref: el => {
if (el) {
chipsRef.current.set(index, el);
} else {
chipsRef.current.delete(index);
}
},
label: getOptionLabel(option),
size: "small",
variant: "outlined"
}, chipProps, {
className: clsx(classes?.chip, chipProps?.className, index >= visibleCount && [gridClasses['multiSelectCellChip--hidden'], classes?.chipHidden])
}), index);
}), hiddenCount > 0 && /*#__PURE__*/_jsx(rootProps.slots.baseChip, _extends({
label: `+${hiddenCount}`,
size: "small",
variant: "outlined"
}, slotProps?.overflow, {
className: clsx(classes?.overflow, slotProps?.overflow?.className)
}))]
}));
}
export const GridMultiSelectChips = /*#__PURE__*/React.forwardRef(GridMultiSelectChipsImpl);
if (process.env.NODE_ENV !== "production") GridMultiSelectChips.displayName = "GridMultiSelectChips";