UNPKG

@mui/x-data-grid

Version:

The community edition of the data grid component (MUI X).

374 lines (364 loc) 16 kB
"use strict"; 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;