@mui/x-data-grid
Version:
The community edition of the data grid component (MUI X).
374 lines (364 loc) • 16 kB
JavaScript
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.sanitizeFilterModel = exports.removeDiacritics = exports.passFilterLogic = exports.mergeStateWithFilterModel = exports.cleanFilterItem = exports.buildAggregatedFilterApplier = void 0;
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var _models = require("../../../models");
var _utils = require("../../../colDef/utils");
var _gridFilterState = require("./gridFilterState");
var _warning = require("../../../utils/warning");
var _getPublicApiRef = require("../../../utils/getPublicApiRef");
var _columns = require("../columns");
// Fixes https://github.com/mui/mui-x/issues/10056
const globalScope = typeof window === 'undefined' ? globalThis : window;
const evalCode = globalScope[atob('ZXZhbA==')];
let hasEval;
try {
hasEval = evalCode('true');
} catch (_) {
hasEval = false;
}
/**
* Adds default values to the optional fields of a filter items.
* @param {GridFilterItem} item The raw filter item.
* @param {React.MutableRefObject<GridPrivateApiCommunity>} apiRef The API of the grid.
* @return {GridFilterItem} The clean filter item with an uniq ID and an always-defined operator.
* TODO: Make the typing reflect the different between GridFilterInputItem and GridFilterItem.
*/
const cleanFilterItem = (item, apiRef) => {
const cleanItem = (0, _extends2.default)({}, item);
if (cleanItem.id == null) {
cleanItem.id = Math.round(Math.random() * 1e5);
}
if (cleanItem.operator == null) {
// Selects a default operator
// We don't use `apiRef.current.getColumn` because it is not ready during state initialization
const column = (0, _columns.gridColumnLookupSelector)(apiRef)[cleanItem.field];
cleanItem.operator = column && column.filterOperators[0].value;
}
return cleanItem;
};
exports.cleanFilterItem = cleanFilterItem;
const filterModelDisableMultiColumnsFilteringWarning = (0, _warning.buildWarning)(['MUI: The `filterModel` can only contain a single item when the `disableMultipleColumnsFiltering` prop is set to `true`.', 'If you are using the community version of the `DataGrid`, this prop is always `true`.'], 'error');
const filterModelMissingItemIdWarning = (0, _warning.buildWarning)('MUI: The `id` field is required on `filterModel.items` when you use multiple filters.', 'error');
const filterModelMissingItemOperatorWarning = (0, _warning.buildWarning)('MUI: The `operator` field is required on `filterModel.items`, one or more of your filtering item has no `operator` provided.', 'error');
const sanitizeFilterModel = (model, disableMultipleColumnsFiltering, apiRef) => {
const hasSeveralItems = model.items.length > 1;
let items;
if (hasSeveralItems && disableMultipleColumnsFiltering) {
filterModelDisableMultiColumnsFilteringWarning();
items = [model.items[0]];
} else {
items = model.items;
}
const hasItemsWithoutIds = hasSeveralItems && items.some(item => item.id == null);
const hasItemWithoutOperator = items.some(item => item.operator == null);
if (hasItemsWithoutIds) {
filterModelMissingItemIdWarning();
}
if (hasItemWithoutOperator) {
filterModelMissingItemOperatorWarning();
}
if (hasItemWithoutOperator || hasItemsWithoutIds) {
return (0, _extends2.default)({}, model, {
items: items.map(item => cleanFilterItem(item, apiRef))
});
}
if (model.items !== items) {
return (0, _extends2.default)({}, model, {
items
});
}
return model;
};
exports.sanitizeFilterModel = sanitizeFilterModel;
const mergeStateWithFilterModel = (filterModel, disableMultipleColumnsFiltering, apiRef) => filteringState => (0, _extends2.default)({}, filteringState, {
filterModel: sanitizeFilterModel(filterModel, disableMultipleColumnsFiltering, apiRef)
});
exports.mergeStateWithFilterModel = mergeStateWithFilterModel;
const removeDiacritics = value => {
if (typeof value === 'string') {
return value.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
}
return value;
};
exports.removeDiacritics = removeDiacritics;
const getFilterCallbackFromItem = (filterItem, apiRef) => {
if (!filterItem.field || !filterItem.operator) {
return null;
}
const column = apiRef.current.getColumn(filterItem.field);
if (!column) {
return null;
}
let parsedValue;
if (column.valueParser) {
const parser = column.valueParser;
parsedValue = Array.isArray(filterItem.value) ? filterItem.value?.map(x => parser(x)) : parser(filterItem.value);
} else {
parsedValue = filterItem.value;
}
const {
ignoreDiacritics
} = apiRef.current.rootProps;
if (ignoreDiacritics) {
parsedValue = removeDiacritics(parsedValue);
}
const newFilterItem = (0, _extends2.default)({}, filterItem, {
value: parsedValue
});
const filterOperators = column.filterOperators;
if (!filterOperators?.length) {
throw new Error(`MUI: No filter operators found for column '${column.field}'.`);
}
const filterOperator = filterOperators.find(operator => operator.value === newFilterItem.operator);
if (!filterOperator) {
throw new Error(`MUI: No filter operator found for column '${column.field}' and operator value '${newFilterItem.operator}'.`);
}
const hasUserFunctionLegacy = !(0, _utils.isInternalFilter)(filterOperator.getApplyFilterFn);
const hasUserFunctionV7 = !(0, _utils.isInternalFilter)(filterOperator.getApplyFilterFnV7);
const publicApiRef = (0, _getPublicApiRef.getPublicApiRef)(apiRef);
if (filterOperator.getApplyFilterFnV7 && !(hasUserFunctionLegacy && !hasUserFunctionV7)) {
const applyFilterOnRow = filterOperator.getApplyFilterFnV7(newFilterItem, column);
if (typeof applyFilterOnRow !== 'function') {
return null;
}
return {
v7: true,
item: newFilterItem,
fn: row => {
let value = apiRef.current.getRowValue(row, column);
if (ignoreDiacritics) {
value = removeDiacritics(value);
}
return applyFilterOnRow(value, row, column, publicApiRef);
}
};
}
const applyFilterOnRow = filterOperator.getApplyFilterFn(newFilterItem, column);
if (typeof applyFilterOnRow !== 'function') {
return null;
}
return {
v7: false,
item: newFilterItem,
fn: rowId => {
const params = apiRef.current.getCellParams(rowId, newFilterItem.field);
_utils.GLOBAL_API_REF.current = publicApiRef;
if (ignoreDiacritics) {
params.value = removeDiacritics(params.value);
}
const result = applyFilterOnRow(params);
_utils.GLOBAL_API_REF.current = null;
return result;
}
};
};
let filterItemsApplierId = 1;
/**
* Generates a method to easily check if a row is matching the current filter model.
* @param {GridFilterModel} filterModel The model with which we want to filter the rows.
* @param {React.MutableRefObject<GridPrivateApiCommunity>} apiRef The API of the grid.
* @returns {GridAggregatedFilterItemApplier | null} A method that checks if a row is matching the current filter model. If `null`, we consider that all the rows are matching the filters.
*/
const buildAggregatedFilterItemsApplier = (filterModel, apiRef, disableEval) => {
const {
items
} = filterModel;
const appliers = items.map(item => getFilterCallbackFromItem(item, apiRef)).filter(callback => !!callback);
if (appliers.length === 0) {
return null;
}
if (!hasEval || disableEval) {
// This is the original logic, which is used if `eval()` is not supported (aka prevented by CSP).
return (row, shouldApplyFilter) => {
const resultPerItemId = {};
for (let i = 0; i < appliers.length; i += 1) {
const applier = appliers[i];
if (!shouldApplyFilter || shouldApplyFilter(applier.item.field)) {
resultPerItemId[applier.item.id] = applier.v7 ? applier.fn(row) : applier.fn(apiRef.current.getRowId(row));
}
}
return resultPerItemId;
};
}
// We generate a new function with `eval()` to avoid expensive patterns for JS engines
// such as a dynamic object assignment, e.g. `{ [dynamicKey]: value }`.
const filterItemTemplate = `(function filterItem$$(getRowId, appliers, row, shouldApplyFilter) {
${appliers.map((applier, i) => `const shouldApply${i} = !shouldApplyFilter || shouldApplyFilter(${JSON.stringify(applier.item.field)});`).join('\n')}
const result$$ = {
${appliers.map((applier, i) => `${JSON.stringify(String(applier.item.id))}:
!shouldApply${i} ?
false :
${applier.v7 ? `appliers[${i}].fn(row)` : `appliers[${i}].fn(getRowId(row))`},
`).join('\n')}};
return result$$;
})`;
const filterItemCore = evalCode(filterItemTemplate.replaceAll('$$', String(filterItemsApplierId)));
const filterItem = (row, shouldApplyItem) => {
return filterItemCore(apiRef.current.getRowId, appliers, row, shouldApplyItem);
};
filterItemsApplierId += 1;
return filterItem;
};
/**
* Generates a method to easily check if a row is matching the current quick filter.
* @param {any[]} filterModel The model with which we want to filter the rows.
* @param {React.MutableRefObject<GridPrivateApiCommunity>} apiRef The API of the grid.
* @returns {GridAggregatedFilterItemApplier | null} A method that checks if a row is matching the current filter model. If `null`, we consider that all the rows are matching the filters.
*/
const buildAggregatedQuickFilterApplier = (filterModel, apiRef) => {
const quickFilterValues = filterModel.quickFilterValues?.filter(Boolean) ?? [];
if (quickFilterValues.length === 0) {
return null;
}
const quickFilterExcludeHiddenColumns = filterModel.quickFilterExcludeHiddenColumns ?? false;
const columnFields = quickFilterExcludeHiddenColumns ? (0, _columns.gridVisibleColumnFieldsSelector)(apiRef) : (0, _columns.gridColumnFieldsSelector)(apiRef);
const appliersPerField = [];
const {
ignoreDiacritics
} = apiRef.current.rootProps;
const publicApiRef = (0, _getPublicApiRef.getPublicApiRef)(apiRef);
columnFields.forEach(field => {
const column = apiRef.current.getColumn(field);
const getApplyQuickFilterFn = column?.getApplyQuickFilterFn;
const getApplyQuickFilterFnV7 = column?.getApplyQuickFilterFnV7;
const hasUserFunctionLegacy = !(0, _utils.isInternalFilter)(getApplyQuickFilterFn);
const hasUserFunctionV7 = !(0, _utils.isInternalFilter)(getApplyQuickFilterFnV7);
if (getApplyQuickFilterFnV7 && !(hasUserFunctionLegacy && !hasUserFunctionV7)) {
appliersPerField.push({
column,
appliers: quickFilterValues.map(quickFilterValue => {
const value = ignoreDiacritics ? removeDiacritics(quickFilterValue) : quickFilterValue;
return {
v7: true,
fn: getApplyQuickFilterFnV7(value, column, publicApiRef)
};
})
});
} else if (getApplyQuickFilterFn) {
appliersPerField.push({
column,
appliers: quickFilterValues.map(quickFilterValue => {
const value = ignoreDiacritics ? removeDiacritics(quickFilterValue) : quickFilterValue;
return {
v7: false,
fn: getApplyQuickFilterFn(value, column, publicApiRef)
};
})
});
}
});
return function isRowMatchingQuickFilter(row, shouldApplyFilter) {
const result = {};
const usedCellParams = {};
/* eslint-disable no-restricted-syntax, no-labels */
outer: for (let v = 0; v < quickFilterValues.length; v += 1) {
const filterValue = quickFilterValues[v];
for (let i = 0; i < appliersPerField.length; i += 1) {
const {
column,
appliers
} = appliersPerField[i];
const {
field
} = column;
if (shouldApplyFilter && !shouldApplyFilter(field)) {
continue;
}
const applier = appliers[v];
let value = apiRef.current.getRowValue(row, column);
if (applier.fn === null) {
continue;
}
if (applier.v7) {
if (ignoreDiacritics) {
value = removeDiacritics(value);
}
const isMatching = applier.fn(value, row, column, publicApiRef);
if (isMatching) {
result[filterValue] = true;
continue outer;
}
} else {
const cellParams = usedCellParams[field] ?? apiRef.current.getCellParams(apiRef.current.getRowId(row), field);
if (ignoreDiacritics) {
cellParams.value = removeDiacritics(cellParams.value);
}
usedCellParams[field] = cellParams;
const isMatching = applier.fn(cellParams);
if (isMatching) {
result[filterValue] = true;
continue outer;
}
}
}
result[filterValue] = false;
}
/* eslint-enable no-restricted-syntax, no-labels */
return result;
};
};
const buildAggregatedFilterApplier = (filterModel, apiRef, disableEval) => {
const isRowMatchingFilterItems = buildAggregatedFilterItemsApplier(filterModel, apiRef, disableEval);
const isRowMatchingQuickFilter = buildAggregatedQuickFilterApplier(filterModel, apiRef);
return function isRowMatchingFilters(row, shouldApplyFilter, result) {
result.passingFilterItems = isRowMatchingFilterItems?.(row, shouldApplyFilter) ?? null;
result.passingQuickFilterValues = isRowMatchingQuickFilter?.(row, shouldApplyFilter) ?? null;
};
};
exports.buildAggregatedFilterApplier = buildAggregatedFilterApplier;
const isNotNull = result => result != null;
const filterModelItems = (cache, apiRef, items) => {
if (!cache.cleanedFilterItems) {
cache.cleanedFilterItems = items.filter(item => getFilterCallbackFromItem(item, apiRef) !== null);
}
return cache.cleanedFilterItems;
};
const passFilterLogic = (allFilterItemResults, allQuickFilterResults, filterModel, apiRef, cache) => {
const cleanedFilterItems = filterModelItems(cache, apiRef, filterModel.items);
const cleanedFilterItemResults = allFilterItemResults.filter(isNotNull);
const cleanedQuickFilterResults = allQuickFilterResults.filter(isNotNull);
// get result for filter items model
if (cleanedFilterItemResults.length > 0) {
// Return true if the item pass with one of the rows
const filterItemPredicate = item => {
return cleanedFilterItemResults.some(filterItemResult => filterItemResult[item.id]);
};
const logicOperator = filterModel.logicOperator ?? (0, _gridFilterState.getDefaultGridFilterModel)().logicOperator;
if (logicOperator === _models.GridLogicOperator.And) {
const passesAllFilters = cleanedFilterItems.every(filterItemPredicate);
if (!passesAllFilters) {
return false;
}
} else {
const passesSomeFilters = cleanedFilterItems.some(filterItemPredicate);
if (!passesSomeFilters) {
return false;
}
}
}
// get result for quick filter model
if (cleanedQuickFilterResults.length > 0 && filterModel.quickFilterValues != null) {
// Return true if the item pass with one of the rows
const quickFilterValuePredicate = value => {
return cleanedQuickFilterResults.some(quickFilterValueResult => quickFilterValueResult[value]);
};
const quickFilterLogicOperator = filterModel.quickFilterLogicOperator ?? (0, _gridFilterState.getDefaultGridFilterModel)().quickFilterLogicOperator;
if (quickFilterLogicOperator === _models.GridLogicOperator.And) {
const passesAllQuickFilterValues = filterModel.quickFilterValues.every(quickFilterValuePredicate);
if (!passesAllQuickFilterValues) {
return false;
}
} else {
const passesSomeQuickFilterValues = filterModel.quickFilterValues.some(quickFilterValuePredicate);
if (!passesSomeQuickFilterValues) {
return false;
}
}
}
return true;
};
exports.passFilterLogic = passFilterLogic;
;