UNPKG

@mui/x-data-grid-pro

Version:

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

278 lines (277 loc) 12.1 kB
import _extends from "@babel/runtime/helpers/esm/extends"; import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose"; const _excluded = ["colIndex", "height", "hasFocus", "width", "headerClassName", "colDef", "item", "headerFilterMenuRef", "InputComponentProps", "showClearIcon", "pinnedPosition", "style", "indexInSection", "sectionLength", "gridHasFiller"]; import * as React from 'react'; import PropTypes from 'prop-types'; import clsx from 'clsx'; import { unstable_useForkRef as useForkRef, unstable_composeClasses as composeClasses, unstable_capitalize as capitalize } from '@mui/utils'; import { fastMemo } from '@mui/x-internals/fastMemo'; import { gridVisibleColumnFieldsSelector, getDataGridUtilityClass, useGridSelector, gridFilterModelSelector, gridFilterableColumnLookupSelector } from '@mui/x-data-grid'; import { useGridPrivateApiContext, gridHeaderFilteringEditFieldSelector, gridHeaderFilteringMenuSelector, isNavigationKey, shouldCellShowLeftBorder, shouldCellShowRightBorder } from '@mui/x-data-grid/internals'; import { useGridRootProps } from '../../hooks/utils/useGridRootProps'; import { GridHeaderFilterMenuContainer } from './GridHeaderFilterMenuContainer'; import { GridHeaderFilterClearButton } from './GridHeaderFilterClearButton'; import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; const useUtilityClasses = ownerState => { const { colDef, classes, showRightBorder, showLeftBorder, pinnedPosition } = ownerState; const slots = { root: ['columnHeader', colDef.headerAlign === 'left' && 'columnHeader--alignLeft', colDef.headerAlign === 'center' && 'columnHeader--alignCenter', colDef.headerAlign === 'right' && 'columnHeader--alignRight', 'withBorderColor', showRightBorder && 'columnHeader--withRightBorder', showLeftBorder && 'columnHeader--withLeftBorder', pinnedPosition === 'left' && 'columnHeader--pinnedLeft', pinnedPosition === 'right' && 'columnHeader--pinnedRight'] }; return composeClasses(slots, getDataGridUtilityClass, classes); }; const dateSx = { [`& input[value=""]:not(:focus)`]: { color: 'transparent' } }; const GridHeaderFilterCell = /*#__PURE__*/React.forwardRef((props, ref) => { const { colIndex, height, hasFocus, width, headerClassName, colDef, item, headerFilterMenuRef, InputComponentProps, showClearIcon = true, pinnedPosition, style: styleProp, indexInSection, sectionLength, gridHasFiller } = props, other = _objectWithoutPropertiesLoose(props, _excluded); const apiRef = useGridPrivateApiContext(); const columnFields = useGridSelector(apiRef, gridVisibleColumnFieldsSelector); const rootProps = useGridRootProps(); const cellRef = React.useRef(null); const handleRef = useForkRef(ref, cellRef); const inputRef = React.useRef(null); const buttonRef = React.useRef(null); const editingField = useGridSelector(apiRef, gridHeaderFilteringEditFieldSelector); const isEditing = editingField === colDef.field; const menuOpenField = useGridSelector(apiRef, gridHeaderFilteringMenuSelector); const isMenuOpen = menuOpenField === colDef.field; // TODO: Support for `isAnyOf` operator const filterOperators = React.useMemo(() => { if (!colDef.filterOperators) { return []; } return colDef.filterOperators.filter(operator => operator.value !== 'isAnyOf'); }, [colDef.filterOperators]); const filterModel = useGridSelector(apiRef, gridFilterModelSelector); const filterableColumnsLookup = useGridSelector(apiRef, gridFilterableColumnLookupSelector); const isFilterReadOnly = React.useMemo(() => { if (!filterModel?.items.length) { return false; } const filterModelItem = filterModel.items.find(it => it.field === colDef.field); return filterModelItem ? !filterableColumnsLookup[filterModelItem.field] : false; }, [colDef.field, filterModel, filterableColumnsLookup]); const currentOperator = React.useMemo(() => filterOperators.find(operator => operator.value === item.operator) ?? filterOperators[0], [item.operator, filterOperators]); const InputComponent = colDef.filterable || isFilterReadOnly ? currentOperator.InputComponent : null; const applyFilterChanges = React.useCallback(updatedItem => { if (item.value && !updatedItem.value) { apiRef.current.deleteFilterItem(updatedItem); return; } apiRef.current.upsertFilterItem(updatedItem); }, [apiRef, item]); const clearFilterItem = React.useCallback(() => { apiRef.current.deleteFilterItem(item); }, [apiRef, item]); let headerFilterComponent; if (colDef.renderHeaderFilter) { headerFilterComponent = colDef.renderHeaderFilter(_extends({}, props, { inputRef })); } React.useLayoutEffect(() => { if (hasFocus && !isMenuOpen) { let focusableElement = cellRef.current.querySelector('[tabindex="0"]'); if (isEditing && InputComponent) { focusableElement = inputRef.current; } const elementToFocus = focusableElement || cellRef.current; elementToFocus?.focus(); apiRef.current.columnHeadersContainerRef.current.scrollLeft = 0; } }, [InputComponent, apiRef, hasFocus, isEditing, isMenuOpen]); const onKeyDown = React.useCallback(event => { if (isMenuOpen || isNavigationKey(event.key) || isFilterReadOnly) { return; } switch (event.key) { case 'Escape': if (isEditing) { apiRef.current.stopHeaderFilterEditMode(); } break; case 'Enter': if (isEditing) { if (!event.defaultPrevented) { apiRef.current.stopHeaderFilterEditMode(); break; } } if (event.metaKey || event.ctrlKey) { headerFilterMenuRef.current = buttonRef.current; apiRef.current.showHeaderFilterMenu(colDef.field); break; } apiRef.current.startHeaderFilterEditMode(colDef.field); break; case 'Tab': { if (isEditing) { const fieldToFocus = columnFields[colIndex + (event.shiftKey ? -1 : 1)] ?? null; if (fieldToFocus) { apiRef.current.startHeaderFilterEditMode(fieldToFocus); apiRef.current.setColumnHeaderFilterFocus(fieldToFocus, event); } } break; } default: if (isEditing || event.metaKey || event.ctrlKey || event.altKey || event.shiftKey) { break; } apiRef.current.startHeaderFilterEditMode(colDef.field); break; } }, [apiRef, colDef.field, colIndex, columnFields, headerFilterMenuRef, isEditing, isFilterReadOnly, isMenuOpen]); const publish = React.useCallback((eventName, propHandler) => event => { apiRef.current.publishEvent(eventName, apiRef.current.getColumnHeaderParams(colDef.field), event); if (propHandler) { propHandler(event); } }, [apiRef, colDef.field]); const onMouseDown = React.useCallback(event => { if (!hasFocus) { if (inputRef.current?.contains?.(event.target)) { inputRef.current.focus(); } apiRef.current.setColumnHeaderFilterFocus(colDef.field, event); } }, [apiRef, colDef.field, hasFocus]); const mouseEventsHandlers = React.useMemo(() => ({ onKeyDown: publish('headerFilterKeyDown', onKeyDown), onClick: publish('headerFilterClick'), onMouseDown: publish('headerFilterMouseDown', onMouseDown), onBlur: publish('headerFilterBlur') }), [onMouseDown, onKeyDown, publish]); const showLeftBorder = shouldCellShowLeftBorder(pinnedPosition, indexInSection); const showRightBorder = shouldCellShowRightBorder(pinnedPosition, indexInSection, sectionLength, rootProps.showCellVerticalBorder, gridHasFiller); const ownerState = _extends({}, rootProps, { pinnedPosition, colDef, showLeftBorder, showRightBorder }); const classes = useUtilityClasses(ownerState); const isNoInputOperator = currentOperator.requiresFilterValue === false; const isApplied = Boolean(item?.value) || isNoInputOperator; const label = currentOperator.headerLabel ?? apiRef.current.getLocaleText(`headerFilterOperator${capitalize(item.operator)}`); const isFilterActive = isApplied || hasFocus; return /*#__PURE__*/_jsxs("div", _extends({ className: clsx(classes.root, headerClassName), ref: handleRef, style: _extends({ height, width, minWidth: width, maxWidth: width }, styleProp), role: "columnheader", "aria-colindex": colIndex + 1, "aria-label": headerFilterComponent == null ? colDef.headerName ?? colDef.field : undefined }, other, mouseEventsHandlers, { children: [headerFilterComponent, InputComponent && headerFilterComponent === undefined ? /*#__PURE__*/_jsxs(React.Fragment, { children: [/*#__PURE__*/_jsx(InputComponent, _extends({ apiRef: apiRef, item: item, inputRef: inputRef, applyValue: applyFilterChanges, onFocus: () => apiRef.current.startHeaderFilterEditMode(colDef.field), onBlur: event => { apiRef.current.stopHeaderFilterEditMode(); // Blurring an input element should reset focus state only if `relatedTarget` is not the header filter cell if (!event.relatedTarget?.className.includes('columnHeader')) { apiRef.current.setState(state => _extends({}, state, { focus: { cell: null, columnHeader: null, columnHeaderFilter: null, columnGroupHeader: null } })); } }, label: capitalize(label), placeholder: "", isFilterActive: isFilterActive, clearButton: showClearIcon && isApplied ? /*#__PURE__*/_jsx(GridHeaderFilterClearButton, { onClick: clearFilterItem, disabled: isFilterReadOnly }) : null, disabled: isFilterReadOnly || isNoInputOperator, tabIndex: -1, InputLabelProps: null, sx: colDef.type === 'date' || colDef.type === 'dateTime' ? dateSx : undefined }, isNoInputOperator ? { value: '' } : {}, currentOperator?.InputComponentProps, InputComponentProps)), /*#__PURE__*/_jsx(GridHeaderFilterMenuContainer, { operators: filterOperators, item: item, field: colDef.field, disabled: isFilterReadOnly, applyFilterChanges: applyFilterChanges, headerFilterMenuRef: headerFilterMenuRef, buttonRef: buttonRef })] }) : null] })); }); process.env.NODE_ENV !== "production" ? GridHeaderFilterCell.propTypes = { // ----------------------------- Warning -------------------------------- // | These PropTypes are generated from the TypeScript type definitions | // | To update them edit the TypeScript types and run "pnpm proptypes" | // ---------------------------------------------------------------------- colDef: PropTypes.object.isRequired, colIndex: PropTypes.number.isRequired, gridHasFiller: PropTypes.bool.isRequired, hasFocus: PropTypes.bool, /** * Class name that will be added in the column header cell. */ headerClassName: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), headerFilterMenuRef: PropTypes.shape({ current: PropTypes.object }).isRequired, height: PropTypes.number.isRequired, indexInSection: PropTypes.number.isRequired, InputComponentProps: PropTypes.object, item: PropTypes.shape({ field: PropTypes.string.isRequired, id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), operator: PropTypes.string.isRequired, value: PropTypes.any }).isRequired, pinnedPosition: PropTypes.oneOf(['left', 'right']), sectionLength: PropTypes.number.isRequired, showClearIcon: PropTypes.bool, sortIndex: PropTypes.number, style: PropTypes.object, tabIndex: PropTypes.oneOf([-1, 0]).isRequired, width: PropTypes.number.isRequired } : void 0; const Memoized = fastMemo(GridHeaderFilterCell); export { Memoized as GridHeaderFilterCell };