UNPKG

@mui/x-data-grid-pro

Version:

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

517 lines (514 loc) 19.4 kB
"use strict"; 'use client'; var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default; Object.defineProperty(exports, "__esModule", { value: true }); exports.GridEditMultiSelectCell = GridEditMultiSelectCell; exports.renderEditMultiSelectCell = void 0; var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); var React = _interopRequireWildcard(require("react")); var _propTypes = _interopRequireDefault(require("prop-types")); var _clsx = _interopRequireDefault(require("clsx")); var _composeClasses = _interopRequireDefault(require("@mui/utils/composeClasses")); var _styles = require("@mui/material/styles"); var _useEnhancedEffect = _interopRequireDefault(require("@mui/utils/useEnhancedEffect")); var _useEventCallback = _interopRequireDefault(require("@mui/utils/useEventCallback")); var _xDataGrid = require("@mui/x-data-grid"); var _internals = require("@mui/x-data-grid/internals"); var _Autocomplete = require("@mui/material/Autocomplete"); var _InputBase = require("@mui/material/InputBase"); var _useGridPrivateApiContext = require("../../hooks/utils/useGridPrivateApiContext"); var _GridMultiSelectChips = require("./GridMultiSelectChips"); var _jsxRuntime = require("react/jsx-runtime"); const useUtilityClasses = ownerState => { const { classes } = ownerState; const slots = { root: ['editMultiSelectCell'], chip: ['editMultiSelectCellChip'], chipHidden: ['editMultiSelectCellChip--hidden'], overflow: ['editMultiSelectCellOverflow'], popup: ['editMultiSelectCellPopup'], popperContent: ['editMultiSelectCellPopperContent'] }; return (0, _composeClasses.default)(slots, _xDataGrid.getDataGridUtilityClass, classes); }; const GridEditMultiSelectCellPopper = (0, _styles.styled)(_internals.NotRendered, { name: 'MuiDataGrid', slot: 'EditMultiSelectCellPopper' })(({ theme }) => ({ zIndex: _internals.vars.zIndex.modal, background: (theme.vars || theme).palette.background.paper, borderRadius: (theme.vars || theme).shape.borderRadius, '&[data-popper-reference-hidden]': { opacity: 0 } })); const GridEditMultiSelectCellPopperContent = (0, _styles.styled)('div', { name: 'MuiDataGrid', slot: 'EditMultiSelectCellPopperContent' })(({ theme }) => ({ width: 'var(--_width)', boxShadow: (theme.vars || theme).shadows[4], boxSizing: 'border-box' })); const GridEditMultiSelectCellAutocomplete = (0, _styles.styled)(_internals.NotRendered, { name: 'MuiDataGrid', slot: 'EditMultiSelectCellAutocomplete' })(({ theme }) => ({ [`& .${_InputBase.inputBaseClasses.root}.${_InputBase.inputBaseClasses.sizeSmall}`]: { minHeight: 'var(--_rowHeight, 52px)', paddingBlock: 4 }, [`& + .${_Autocomplete.autocompleteClasses.popper}`]: { [`& .${_Autocomplete.autocompleteClasses.option}`]: (0, _extends2.default)({}, theme.typography.body2) } })); const GridEditMultiSelectCellAutocompletePopper = (0, _styles.styled)('div', { slot: 'internal', shouldForwardProp: prop => prop !== 'ownerState' && prop !== 'anchorEl' && prop !== 'open' && prop !== 'disablePortal' })({}); const GridEditMultiSelectChips = (0, _styles.styled)(_GridMultiSelectChips.GridMultiSelectChips)({ padding: '0 10px', '&:focus-visible': { outline: 'none' // let the grid cell handle the focus ring } }); function GridEditMultiSelectCell(props) { const rootProps = (0, _xDataGrid.useGridRootProps)(); const { id, value: valueProp, field, row, colDef, cellMode, hasFocus, slotProps } = props; const apiRef = (0, _xDataGrid.useGridApiContext)(); const privateApiRef = (0, _useGridPrivateApiContext.useGridPrivateApiContext)(); const classes = useUtilityClasses(rootProps); const rowHeight = (0, _xDataGrid.useGridSelector)(apiRef, _internals.gridRowHeightSelector); const [anchorEl, setAnchorEl] = React.useState(null); const [open, setOpen] = React.useState(true); const isAutoHeight = privateApiRef.current.rowHasAutoHeight(id); const popupId = `${id}-${field}-multiselect-edit-popup`; const showPopup = hasFocus && Boolean(anchorEl) && open; const closePopup = reason => { if (rootProps.editMode === 'row') { setOpen(false); return; } const params = apiRef.current.getCellParams(id, field); apiRef.current.publishEvent('cellEditStop', (0, _extends2.default)({}, params, { reason })); }; const handleModalClose = () => closePopup(_xDataGrid.GridCellEditStopReasons.cellFocusOut); const handleKeyDownCapture = event => { if (event.key === 'Escape') { closePopup(_xDataGrid.GridCellEditStopReasons.escapeKeyDown); } }; const handleReopen = () => { if (rootProps.editMode === 'row' && !open) { setOpen(true); } }; const handleChipsKeyDown = event => { if (event.key === 'Enter' && rootProps.editMode === 'row' && !open) { // Consume Enter so the grid's row-edit handler doesn't exit the row. event.preventDefault(); event.stopPropagation(); setOpen(true); } }; // Reset `open` on focus-loss so re-entering (Shift+Tab back) reopens the autocomplete // by default — matches the "every focus opens the autocomplete" expectation. Also // scrolls the cell into view on focus-gain because the Modal locks body scroll and // the autocomplete input is body-portaled, so the browser default scroll-into-view // text-input cells rely on doesn't fire here. Kept as a single effect to avoid a // per-cell `cellFocusIn` listener (EventManager warns past 20 in row-edit). const prevHasFocusRef = React.useRef(false); (0, _useEnhancedEffect.default)(() => { if (rootProps.editMode !== 'row') { return; } if (!hasFocus) { setOpen(true); prevHasFocusRef.current = false; return; } if (!prevHasFocusRef.current) { apiRef.current.scrollToIndexes({ colIndex: apiRef.current.getColumnIndex(field, true), rowIndex: apiRef.current.getRowIndexRelativeToVisibleRows(id) }); prevHasFocusRef.current = true; } }, [hasFocus, rootProps.editMode, apiRef, field, id]); (0, _useEnhancedEffect.default)(() => { if (rootProps.editMode !== 'row') { return; } if (hasFocus && !open) { anchorEl?.focus(); } }, [hasFocus, open, rootProps.editMode, anchorEl]); const getOptionValue = colDef.getOptionValue; const getOptionLabel = colDef.getOptionLabel; if (!(0, _internals.isMultiSelectColDef)(colDef)) { return null; } const valueOptions = (0, _internals.getValueOptions)(colDef, { id, row }); if (!valueOptions) { return null; } const currentValue = Array.isArray(valueProp) ? valueProp : []; const optionByValue = new Map(); for (const opt of valueOptions) { optionByValue.set(getOptionValue(opt), opt); } // Convert values to options for Autocomplete const selectedOptions = currentValue.map(val => optionByValue.get(val)).filter(option => option !== undefined); return /*#__PURE__*/(0, _jsxRuntime.jsxs)(React.Fragment, { children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(GridEditMultiSelectChips, { ref: setAnchorEl, values: currentValue, field: field, columnWidth: colDef.computedWidth, autoWrap: isAutoHeight, optionByValue: optionByValue, getOptionLabel: getOptionLabel, classes: { root: classes.root, chip: classes.chip, chipHidden: classes.chipHidden, overflow: classes.overflow }, slotProps: { root: (0, _extends2.default)({ tabIndex: cellMode === 'edit' && rootProps.editMode === 'row' ? 0 : undefined, 'aria-controls': showPopup ? popupId : undefined, 'aria-expanded': showPopup, onClick: handleReopen, onKeyDown: handleChipsKeyDown }, slotProps?.root), chip: slotProps?.chip // No `overflow` slotProps → `+N` renders as a non-interactive indicator; // clicks on the cell focus the autocomplete which opens the editor. } }), /*#__PURE__*/(0, _jsxRuntime.jsx)(rootProps.slots.baseModal, { open: showPopup, disableAutoFocus: true, disableEnforceFocus: true, disableRestoreFocus: true, onClose: handleModalClose, material: { slotProps: { root: { style: { zIndex: 'auto' } }, backdrop: { invisible: true } } }, children: /*#__PURE__*/(0, _jsxRuntime.jsx)(GridEditMultiSelectCellPopper, (0, _extends2.default)({ as: rootProps.slots.basePopper, ownerState: rootProps, id: popupId, role: "dialog", "aria-label": colDef.headerName || field, open: showPopup, target: anchorEl, placement: "bottom-start", flip: true, material: { modifiers: [{ name: 'offset', options: { offset: [-1, -rowHeight + 1] } // 1px compensate for the editing cell padding. }] } }, slotProps?.popper, { className: (0, _clsx.default)(classes.popup, slotProps?.popper?.className), children: /*#__PURE__*/(0, _jsxRuntime.jsx)(GridEditMultiSelectCellPopperContent, (0, _extends2.default)({ onKeyDownCapture: handleKeyDownCapture }, slotProps?.popperContent, { className: (0, _clsx.default)(classes.popperContent, slotProps?.popperContent?.className), style: { '--_width': `${colDef.computedWidth}px`, '--_rowHeight': `${rowHeight}px` }, children: /*#__PURE__*/(0, _jsxRuntime.jsx)(GridEditMultiSelectAutocomplete, (0, _extends2.default)({}, props, { valueOptions: valueOptions, selectedOptions: selectedOptions, getOptionValue: getOptionValue, getOptionLabel: getOptionLabel, onDismiss: () => setOpen(false) })) })) })) })] }); } function GridEditMultiSelectAutocomplete(props) { const { id, field, hasFocus, onValueChange, slotProps, valueOptions, selectedOptions, getOptionValue, getOptionLabel, onDismiss } = props; const inputRef = React.useRef(null); const apiRef = (0, _xDataGrid.useGridApiContext)(); const rootProps = (0, _xDataGrid.useGridRootProps)(); (0, _useEnhancedEffect.default)(() => { if (hasFocus && inputRef.current) { inputRef.current.focus(); } }, [hasFocus]); const handleClose = (0, _useEventCallback.default)((_event, reason) => { if (rootProps.editMode === 'row') { onDismiss(); return; } const params = apiRef.current.getCellParams(id, field); apiRef.current.publishEvent('cellEditStop', (0, _extends2.default)({}, params, { reason: reason === 'escape' ? _xDataGrid.GridCellEditStopReasons.escapeKeyDown : _xDataGrid.GridCellEditStopReasons.cellFocusOut })); }); const isOptionEqualToValue = React.useCallback((option, val) => getOptionValue(option) === getOptionValue(val), [getOptionValue]); const handleChange = (0, _useEventCallback.default)(async (event, newValue) => { if (event.type === 'keydown') { const keyboardEvent = event.nativeEvent; if (keyboardEvent.key === 'Enter') { if (keyboardEvent.ctrlKey || keyboardEvent.metaKey) { // Ctrl/Cmd + Enter: stop propagation to prevent cell navigation event.stopPropagation(); } else if (rootProps.editMode === 'row') { // Row mode bare Enter: dismiss popup, keep cell focused, ignore this selection. onDismiss(); return; } else { // Cell mode bare Enter: exit edit mode, ignore this selection change. const params = apiRef.current.getCellParams(id, field); apiRef.current.publishEvent('cellEditStop', (0, _extends2.default)({}, params, { reason: _xDataGrid.GridCellEditStopReasons.enterKeyDown })); return; } } } const newValues = newValue.map(option => getOptionValue(option)); if (onValueChange) { await onValueChange(event, newValues); } await apiRef.current.setEditCellValue({ id, field, value: newValues }, event); }); return /*#__PURE__*/(0, _jsxRuntime.jsx)(GridEditMultiSelectCellAutocomplete, (0, _extends2.default)({ as: rootProps.slots.baseAutocomplete, multiple: true, open: true, options: valueOptions, value: selectedOptions, onClose: handleClose, onChange: handleChange, getOptionLabel: getOptionLabel, isOptionEqualToValue: isOptionEqualToValue, disableCloseOnSelect: true, openOnFocus: true, slotProps: { textField: { size: 'small', inputRef }, chip: slotProps?.chip } }, slotProps?.autocomplete, { material: (0, _extends2.default)({}, slotProps?.autocomplete?.material, { slots: (0, _extends2.default)({ // @ts-expect-error the types require import from Material UI package popper: GridEditMultiSelectCellAutocompletePopper }, slotProps?.autocomplete?.material?.slots) }) })); } process.env.NODE_ENV !== "production" ? GridEditMultiSelectAutocomplete.propTypes /* remove-proptypes */ = { // ----------------------------- Warning -------------------------------- // | These PropTypes are generated from the TypeScript type definitions | // | To update them edit the TypeScript types and run "pnpm proptypes" | // ---------------------------------------------------------------------- /** * GridApi that let you manipulate the grid. */ api: _propTypes.default.object.isRequired, /** * The mode of the cell. */ cellMode: _propTypes.default.oneOf(['edit', 'view']).isRequired, changeReason: _propTypes.default.oneOf(['debouncedSetEditCellValue', 'setEditCellValue']), /** * The column of the row that the current cell belongs to. */ colDef: _propTypes.default.object.isRequired, /** * The column field of the cell that triggered the event. */ field: _propTypes.default.string.isRequired, /** * The cell value formatted with the column valueFormatter. */ formattedValue: _propTypes.default.any, getOptionLabel: _propTypes.default.func.isRequired, getOptionValue: _propTypes.default.func.isRequired, /** * If true, the cell is the active element. */ hasFocus: _propTypes.default.bool.isRequired, /** * The grid row id. */ id: _propTypes.default.oneOfType([_propTypes.default.number, _propTypes.default.string]).isRequired, /** * If true, the cell is editable. */ isEditable: _propTypes.default.bool, isProcessingProps: _propTypes.default.bool, isValidating: _propTypes.default.bool, onDismiss: _propTypes.default.func.isRequired, /** * Callback called when the value is changed by the user. * @param {React.SyntheticEvent} event The event source of the callback. * @param {any[]} newValue The value that is going to be passed to `apiRef.current.setEditCellValue`. * @returns {Promise<void> | void} A promise to be awaited before calling `apiRef.current.setEditCellValue` */ onValueChange: _propTypes.default.func, /** * The row model of the row that the current cell belongs to. */ row: _propTypes.default.any.isRequired, /** * The node of the row that the current cell belongs to. */ rowNode: _propTypes.default.object.isRequired, selectedOptions: _propTypes.default.arrayOf(_propTypes.default.oneOfType([_propTypes.default.number, _propTypes.default.object, _propTypes.default.shape({ label: _propTypes.default.string.isRequired, value: _propTypes.default.any.isRequired }), _propTypes.default.string]).isRequired).isRequired, /** * Props passed to internal components. */ slotProps: _propTypes.default.object, /** * the tabIndex value. */ tabIndex: _propTypes.default.oneOf([-1, 0]).isRequired, /** * The cell value. * If the column has `valueGetter`, use `params.row` to directly access the fields. */ value: _propTypes.default.any, valueOptions: _propTypes.default.arrayOf(_propTypes.default.oneOfType([_propTypes.default.number, _propTypes.default.object, _propTypes.default.shape({ label: _propTypes.default.string.isRequired, value: _propTypes.default.any.isRequired }), _propTypes.default.string]).isRequired).isRequired } : void 0; process.env.NODE_ENV !== "production" ? GridEditMultiSelectCell.propTypes /* remove-proptypes */ = { // ----------------------------- Warning -------------------------------- // | These PropTypes are generated from the TypeScript type definitions | // | To update them edit the TypeScript types and run "pnpm proptypes" | // ---------------------------------------------------------------------- /** * GridApi that let you manipulate the grid. */ api: _propTypes.default.object.isRequired, /** * The mode of the cell. */ cellMode: _propTypes.default.oneOf(['edit', 'view']).isRequired, changeReason: _propTypes.default.oneOf(['debouncedSetEditCellValue', 'setEditCellValue']), /** * The column of the row that the current cell belongs to. */ colDef: _propTypes.default.object.isRequired, /** * The column field of the cell that triggered the event. */ field: _propTypes.default.string.isRequired, /** * The cell value formatted with the column valueFormatter. */ formattedValue: _propTypes.default.any, /** * If true, the cell is the active element. */ hasFocus: _propTypes.default.bool.isRequired, /** * The grid row id. */ id: _propTypes.default.oneOfType([_propTypes.default.number, _propTypes.default.string]).isRequired, /** * If true, the cell is editable. */ isEditable: _propTypes.default.bool, isProcessingProps: _propTypes.default.bool, isValidating: _propTypes.default.bool, /** * Callback called when the value is changed by the user. * @param {React.SyntheticEvent} event The event source of the callback. * @param {any[]} newValue The value that is going to be passed to `apiRef.current.setEditCellValue`. * @returns {Promise<void> | void} A promise to be awaited before calling `apiRef.current.setEditCellValue` */ onValueChange: _propTypes.default.func, /** * The row model of the row that the current cell belongs to. */ row: _propTypes.default.any.isRequired, /** * The node of the row that the current cell belongs to. */ rowNode: _propTypes.default.object.isRequired, /** * Props passed to internal components. */ slotProps: _propTypes.default.object, /** * the tabIndex value. */ tabIndex: _propTypes.default.oneOf([-1, 0]).isRequired, /** * The cell value. * If the column has `valueGetter`, use `params.row` to directly access the fields. */ value: _propTypes.default.any } : void 0; const renderEditMultiSelectCell = params => /*#__PURE__*/(0, _jsxRuntime.jsx)(GridEditMultiSelectCell, (0, _extends2.default)({}, params)); exports.renderEditMultiSelectCell = renderEditMultiSelectCell; if (process.env.NODE_ENV !== "production") renderEditMultiSelectCell.displayName = "renderEditMultiSelectCell";