@mui/x-data-grid
Version:
The Community plan edition of the Data Grid components (MUI X).
485 lines (480 loc) • 19.8 kB
JavaScript
import _extends from "@babel/runtime/helpers/esm/extends";
import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose";
const _excluded = ["item", "hasMultipleFilters", "deleteFilter", "applyFilterChanges", "showMultiFilterOperators", "disableMultiFilterOperator", "applyMultiFilterOperatorChanges", "focusElementRef", "logicOperators", "columnsSort", "filterColumns", "deleteIconProps", "logicOperatorInputProps", "operatorInputProps", "columnInputProps", "valueInputProps", "readOnly", "children"],
_excluded2 = ["InputComponentProps"];
import * as React from 'react';
import PropTypes from 'prop-types';
import { unstable_composeClasses as composeClasses, unstable_useId as useId, unstable_capitalize as capitalize } from '@mui/utils';
import { styled } from '@mui/material/styles';
import clsx from 'clsx';
import { forwardRef } from '@mui/x-internals/forwardRef';
import { gridFilterableColumnDefinitionsSelector, gridColumnLookupSelector } from "../../../hooks/features/columns/gridColumnsSelector.js";
import { gridFilterModelSelector } from "../../../hooks/features/filter/gridFilterSelector.js";
import { useGridSelector } from "../../../hooks/utils/useGridSelector.js";
import { GridLogicOperator } from "../../../models/gridFilterItem.js";
import { useGridApiContext } from "../../../hooks/utils/useGridApiContext.js";
import { useGridRootProps } from "../../../hooks/utils/useGridRootProps.js";
import { getDataGridUtilityClass } from "../../../constants/gridClasses.js";
import { getValueFromValueOptions, getValueOptions } from "./filterPanelUtils.js";
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { createElement as _createElement } from "react";
const useUtilityClasses = ownerState => {
const {
classes
} = ownerState;
const slots = {
root: ['filterForm'],
deleteIcon: ['filterFormDeleteIcon'],
logicOperatorInput: ['filterFormLogicOperatorInput'],
columnInput: ['filterFormColumnInput'],
operatorInput: ['filterFormOperatorInput'],
valueInput: ['filterFormValueInput']
};
return composeClasses(slots, getDataGridUtilityClass, classes);
};
const GridFilterFormRoot = styled('div', {
name: 'MuiDataGrid',
slot: 'FilterForm',
overridesResolver: (props, styles) => styles.filterForm
})(({
theme
}) => ({
display: 'flex',
padding: theme.spacing(1)
}));
const FilterFormDeleteIcon = styled('div', {
name: 'MuiDataGrid',
slot: 'FilterFormDeleteIcon',
overridesResolver: (_, styles) => styles.filterFormDeleteIcon
})(({
theme
}) => ({
flexShrink: 0,
justifyContent: 'flex-end',
marginRight: theme.spacing(0.5),
marginBottom: theme.spacing(0.2)
}));
const FilterFormLogicOperatorInput = styled('div', {
name: 'MuiDataGrid',
slot: 'FilterFormLogicOperatorInput',
overridesResolver: (_, styles) => styles.filterFormLogicOperatorInput
})({
minWidth: 55,
marginRight: 5,
justifyContent: 'end'
});
const FilterFormColumnInput = styled('div', {
name: 'MuiDataGrid',
slot: 'FilterFormColumnInput',
overridesResolver: (_, styles) => styles.filterFormColumnInput
})({
width: 150
});
const FilterFormOperatorInput = styled('div', {
name: 'MuiDataGrid',
slot: 'FilterFormOperatorInput',
overridesResolver: (_, styles) => styles.filterFormOperatorInput
})({
width: 150
});
const FilterFormValueInput = styled('div', {
name: 'MuiDataGrid',
slot: 'FilterFormValueInput',
overridesResolver: (_, styles) => styles.filterFormValueInput
})({
width: 190
});
const getLogicOperatorLocaleKey = logicOperator => {
switch (logicOperator) {
case GridLogicOperator.And:
return 'filterPanelOperatorAnd';
case GridLogicOperator.Or:
return 'filterPanelOperatorOr';
default:
throw new Error('MUI X: Invalid `logicOperator` property in the `GridFilterPanel`.');
}
};
const getColumnLabel = col => col.headerName || col.field;
const collator = new Intl.Collator();
const GridFilterForm = forwardRef(function GridFilterForm(props, ref) {
const {
item,
hasMultipleFilters,
deleteFilter,
applyFilterChanges,
showMultiFilterOperators,
disableMultiFilterOperator,
applyMultiFilterOperatorChanges,
focusElementRef,
logicOperators = [GridLogicOperator.And, GridLogicOperator.Or],
columnsSort,
filterColumns,
deleteIconProps = {},
logicOperatorInputProps = {},
operatorInputProps = {},
columnInputProps = {},
valueInputProps = {},
readOnly
} = props,
other = _objectWithoutPropertiesLoose(props, _excluded);
const apiRef = useGridApiContext();
const columnLookup = useGridSelector(apiRef, gridColumnLookupSelector);
const filterableColumns = useGridSelector(apiRef, gridFilterableColumnDefinitionsSelector);
const filterModel = useGridSelector(apiRef, gridFilterModelSelector);
const columnSelectId = useId();
const columnSelectLabelId = useId();
const operatorSelectId = useId();
const operatorSelectLabelId = useId();
const rootProps = useGridRootProps();
const classes = useUtilityClasses(rootProps);
const valueRef = React.useRef(null);
const filterSelectorRef = React.useRef(null);
const multiFilterOperator = filterModel.logicOperator ?? GridLogicOperator.And;
const hasLogicOperatorColumn = hasMultipleFilters && logicOperators.length > 0;
const baseFormControlProps = rootProps.slotProps?.baseFormControl || {};
const baseSelectProps = rootProps.slotProps?.baseSelect || {};
const isBaseSelectNative = baseSelectProps.native ?? false;
const baseInputLabelProps = rootProps.slotProps?.baseInputLabel || {};
const baseSelectOptionProps = rootProps.slotProps?.baseSelectOption || {};
const {
InputComponentProps
} = valueInputProps,
valueInputPropsOther = _objectWithoutPropertiesLoose(valueInputProps, _excluded2);
const {
filteredColumns,
selectedField
} = React.useMemo(() => {
let itemField = item.field;
// Yields a valid value if the current filter belongs to a column that is not filterable
const selectedNonFilterableColumn = columnLookup[item.field].filterable === false ? columnLookup[item.field] : null;
if (selectedNonFilterableColumn) {
return {
filteredColumns: [selectedNonFilterableColumn],
selectedField: itemField
};
}
if (filterColumns === undefined || typeof filterColumns !== 'function') {
return {
filteredColumns: filterableColumns,
selectedField: itemField
};
}
const filteredFields = filterColumns({
field: item.field,
columns: filterableColumns,
currentFilters: filterModel?.items || []
});
return {
filteredColumns: filterableColumns.filter(column => {
const isFieldIncluded = filteredFields.includes(column.field);
if (column.field === item.field && !isFieldIncluded) {
itemField = undefined;
}
return isFieldIncluded;
}),
selectedField: itemField
};
}, [filterColumns, filterModel?.items, filterableColumns, item.field, columnLookup]);
const sortedFilteredColumns = React.useMemo(() => {
switch (columnsSort) {
case 'asc':
return filteredColumns.sort((a, b) => collator.compare(getColumnLabel(a), getColumnLabel(b)));
case 'desc':
return filteredColumns.sort((a, b) => -collator.compare(getColumnLabel(a), getColumnLabel(b)));
default:
return filteredColumns;
}
}, [filteredColumns, columnsSort]);
const currentColumn = item.field ? apiRef.current.getColumn(item.field) : null;
const currentOperator = React.useMemo(() => {
if (!item.operator || !currentColumn) {
return null;
}
return currentColumn.filterOperators?.find(operator => operator.value === item.operator);
}, [item, currentColumn]);
const changeColumn = React.useCallback(event => {
const field = event.target.value;
const column = apiRef.current.getColumn(field);
if (column.field === currentColumn.field) {
// column did not change
return;
}
// try to keep the same operator when column change
const newOperator = column.filterOperators.find(operator => operator.value === item.operator) || column.filterOperators[0];
// Erase filter value if the input component or filtered column type is modified
const eraseFilterValue = !newOperator.InputComponent || newOperator.InputComponent !== currentOperator?.InputComponent || column.type !== currentColumn.type;
let filterValue = eraseFilterValue ? undefined : item.value;
// Check filter value against the new valueOptions
if (column.type === 'singleSelect' && filterValue !== undefined) {
const colDef = column;
const valueOptions = getValueOptions(colDef);
if (Array.isArray(filterValue)) {
filterValue = filterValue.filter(val => {
return (
// Only keep values that are in the new value options
getValueFromValueOptions(val, valueOptions, colDef?.getOptionValue) !== undefined
);
});
} else if (getValueFromValueOptions(item.value, valueOptions, colDef?.getOptionValue) === undefined) {
// Reset the filter value if it is not in the new value options
filterValue = undefined;
}
}
applyFilterChanges(_extends({}, item, {
field,
operator: newOperator.value,
value: filterValue
}));
}, [apiRef, applyFilterChanges, item, currentColumn, currentOperator]);
const changeOperator = React.useCallback(event => {
const operator = event.target.value;
const newOperator = currentColumn?.filterOperators.find(op => op.value === operator);
const eraseItemValue = !newOperator?.InputComponent || newOperator?.InputComponent !== currentOperator?.InputComponent;
applyFilterChanges(_extends({}, item, {
operator,
value: eraseItemValue ? undefined : item.value
}));
}, [applyFilterChanges, item, currentColumn, currentOperator]);
const changeLogicOperator = React.useCallback(event => {
const logicOperator = event.target.value === GridLogicOperator.And.toString() ? GridLogicOperator.And : GridLogicOperator.Or;
applyMultiFilterOperatorChanges(logicOperator);
}, [applyMultiFilterOperatorChanges]);
const handleDeleteFilter = () => {
deleteFilter(item);
};
React.useImperativeHandle(focusElementRef, () => ({
focus: () => {
if (currentOperator?.InputComponent) {
valueRef?.current?.focus();
} else {
filterSelectorRef.current.focus();
}
}
}), [currentOperator]);
return /*#__PURE__*/_jsxs(GridFilterFormRoot, _extends({
className: classes.root,
"data-id": item.id,
ownerState: rootProps
}, other, {
ref: ref,
children: [/*#__PURE__*/_jsx(FilterFormDeleteIcon, _extends({
variant: "standard",
as: rootProps.slots.baseFormControl
}, baseFormControlProps, deleteIconProps, {
className: clsx(classes.deleteIcon, baseFormControlProps.className, deleteIconProps.className),
ownerState: rootProps,
children: /*#__PURE__*/_jsx(rootProps.slots.baseIconButton, _extends({
"aria-label": apiRef.current.getLocaleText('filterPanelDeleteIconLabel'),
title: apiRef.current.getLocaleText('filterPanelDeleteIconLabel'),
onClick: handleDeleteFilter,
size: "small",
disabled: readOnly
}, rootProps.slotProps?.baseIconButton, {
children: /*#__PURE__*/_jsx(rootProps.slots.filterPanelDeleteIcon, {
fontSize: "small"
})
}))
})), /*#__PURE__*/_jsx(FilterFormLogicOperatorInput, _extends({
variant: "standard",
as: rootProps.slots.baseFormControl
}, baseFormControlProps, logicOperatorInputProps, {
sx: [hasLogicOperatorColumn ? {
display: 'flex'
} : {
display: 'none'
}, showMultiFilterOperators ? {
visibility: 'visible'
} : {
visibility: 'hidden'
}, baseFormControlProps.sx, logicOperatorInputProps.sx],
className: clsx(classes.logicOperatorInput, baseFormControlProps.className, logicOperatorInputProps.className),
ownerState: rootProps,
children: /*#__PURE__*/_jsx(rootProps.slots.baseSelect, _extends({
inputProps: {
'aria-label': apiRef.current.getLocaleText('filterPanelLogicOperator')
},
value: multiFilterOperator ?? '',
onChange: changeLogicOperator,
disabled: !!disableMultiFilterOperator || logicOperators.length === 1,
native: isBaseSelectNative
}, rootProps.slotProps?.baseSelect, {
children: logicOperators.map(logicOperator => /*#__PURE__*/_createElement(rootProps.slots.baseSelectOption, _extends({}, baseSelectOptionProps, {
native: isBaseSelectNative,
key: logicOperator.toString(),
value: logicOperator.toString()
}), apiRef.current.getLocaleText(getLogicOperatorLocaleKey(logicOperator))))
}))
})), /*#__PURE__*/_jsxs(FilterFormColumnInput, _extends({
variant: "standard",
as: rootProps.slots.baseFormControl
}, baseFormControlProps, columnInputProps, {
className: clsx(classes.columnInput, baseFormControlProps.className, columnInputProps.className),
ownerState: rootProps,
children: [/*#__PURE__*/_jsx(rootProps.slots.baseInputLabel, _extends({}, baseInputLabelProps, {
htmlFor: columnSelectId,
id: columnSelectLabelId,
children: apiRef.current.getLocaleText('filterPanelColumns')
})), /*#__PURE__*/_jsx(rootProps.slots.baseSelect, _extends({
labelId: columnSelectLabelId,
id: columnSelectId,
label: apiRef.current.getLocaleText('filterPanelColumns'),
value: selectedField ?? '',
onChange: changeColumn,
native: isBaseSelectNative,
disabled: readOnly
}, rootProps.slotProps?.baseSelect, {
children: sortedFilteredColumns.map(col => /*#__PURE__*/_createElement(rootProps.slots.baseSelectOption, _extends({}, baseSelectOptionProps, {
native: isBaseSelectNative,
key: col.field,
value: col.field
}), getColumnLabel(col)))
}))]
})), /*#__PURE__*/_jsxs(FilterFormOperatorInput, _extends({
variant: "standard",
as: rootProps.slots.baseFormControl
}, baseFormControlProps, operatorInputProps, {
className: clsx(classes.operatorInput, baseFormControlProps.className, operatorInputProps.className),
ownerState: rootProps,
children: [/*#__PURE__*/_jsx(rootProps.slots.baseInputLabel, _extends({}, baseInputLabelProps, {
htmlFor: operatorSelectId,
id: operatorSelectLabelId,
children: apiRef.current.getLocaleText('filterPanelOperator')
})), /*#__PURE__*/_jsx(rootProps.slots.baseSelect, _extends({
labelId: operatorSelectLabelId,
label: apiRef.current.getLocaleText('filterPanelOperator'),
id: operatorSelectId,
value: item.operator,
onChange: changeOperator,
native: isBaseSelectNative,
inputRef: filterSelectorRef,
disabled: readOnly
}, rootProps.slotProps?.baseSelect, {
children: currentColumn?.filterOperators?.map(operator => /*#__PURE__*/_createElement(rootProps.slots.baseSelectOption, _extends({}, baseSelectOptionProps, {
native: isBaseSelectNative,
key: operator.value,
value: operator.value
}), operator.label || apiRef.current.getLocaleText(`filterOperator${capitalize(operator.value)}`)))
}))]
})), /*#__PURE__*/_jsx(FilterFormValueInput, _extends({
variant: "standard",
as: rootProps.slots.baseFormControl
}, baseFormControlProps, valueInputPropsOther, {
className: clsx(classes.valueInput, baseFormControlProps.className, valueInputPropsOther.className),
ownerState: rootProps,
children: currentOperator?.InputComponent ? /*#__PURE__*/_jsx(currentOperator.InputComponent, _extends({
apiRef: apiRef,
item: item,
applyValue: applyFilterChanges,
focusElementRef: valueRef,
disabled: readOnly
}, currentOperator.InputComponentProps, InputComponentProps), item.field) : null
}))]
}));
});
process.env.NODE_ENV !== "production" ? GridFilterForm.propTypes = {
// ----------------------------- Warning --------------------------------
// | These PropTypes are generated from the TypeScript type definitions |
// | To update them edit the TypeScript types and run "pnpm proptypes" |
// ----------------------------------------------------------------------
/**
* Callback called when the operator, column field or value is changed.
* @param {GridFilterItem} item The updated [[GridFilterItem]].
*/
applyFilterChanges: PropTypes.func.isRequired,
/**
* Callback called when the logic operator is changed.
* @param {GridLogicOperator} operator The new logic operator.
*/
applyMultiFilterOperatorChanges: PropTypes.func.isRequired,
/**
* @ignore - do not document.
*/
children: PropTypes.node,
/**
* Props passed to the column input component.
* @default {}
*/
columnInputProps: PropTypes.any,
/**
* Changes how the options in the columns selector should be ordered.
* If not specified, the order is derived from the `columns` prop.
*/
columnsSort: PropTypes.oneOf(['asc', 'desc']),
/**
* Callback called when the delete button is clicked.
* @param {GridFilterItem} item The deleted [[GridFilterItem]].
*/
deleteFilter: PropTypes.func.isRequired,
/**
* Props passed to the delete icon.
* @default {}
*/
deleteIconProps: PropTypes.any,
/**
* If `true`, disables the logic operator field but still renders it.
*/
disableMultiFilterOperator: PropTypes.bool,
/**
* Allows to filter the columns displayed in the filter form.
* @param {FilterColumnsArgs} args The columns of the grid and name of field.
* @returns {GridColDef['field'][]} The filtered fields array.
*/
filterColumns: PropTypes.func,
/**
* A ref allowing to set imperative focus.
* It can be passed to the el
*/
focusElementRef: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([PropTypes.func, PropTypes.object]),
/**
* If `true`, the logic operator field is rendered.
* The field will be invisible if `showMultiFilterOperators` is also `true`.
*/
hasMultipleFilters: PropTypes.bool.isRequired,
/**
* The [[GridFilterItem]] representing this form.
*/
item: PropTypes.shape({
field: PropTypes.string.isRequired,
id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
operator: PropTypes.string.isRequired,
value: PropTypes.any
}).isRequired,
/**
* Props passed to the logic operator input component.
* @default {}
*/
logicOperatorInputProps: PropTypes.any,
/**
* Sets the available logic operators.
* @default [GridLogicOperator.And, GridLogicOperator.Or]
*/
logicOperators: PropTypes.arrayOf(PropTypes.oneOf(['and', 'or']).isRequired),
/**
* Props passed to the operator input component.
* @default {}
*/
operatorInputProps: PropTypes.any,
/**
* `true` if the filter is disabled/read only.
* i.e. `colDef.fiterable = false` but passed in `filterModel`
* @default false
*/
readOnly: PropTypes.bool,
/**
* If `true`, the logic operator field is visible.
*/
showMultiFilterOperators: PropTypes.bool,
/**
* Props passed to the value input component.
* @default {}
*/
valueInputProps: PropTypes.any
} : void 0;
/**
* Demos:
* - [Filtering - overview](https://mui.com/x/react-data-grid/filtering/)
*
* API:
* - [GridFilterForm API](https://mui.com/x/api/data-grid/grid-filter-form/)
*/
export { GridFilterForm };