UNPKG

@mui/x-data-grid-pro

Version:

The Pro plan edition of the MUI X Data Grid components.

170 lines (165 loc) 7.26 kB
'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";