From 5887a3b93c9c85ac2e054a041429cc4ad5f47c0c Mon Sep 17 00:00:00 2001 From: Matheus Wichman Date: Thu, 19 Jan 2023 19:31:44 -0300 Subject: [PATCH 1/9] [DataGrid] Allow to customize label and value for `singleSelect` --- docs/pages/x/api/data-grid/grid-col-def.md | 83 +++++++++---------- .../src/services/gridColDefGenerator.ts | 6 +- .../export/serializer/excelSerializer.ts | 41 +++++---- .../rowGrouping/createGroupingColDef.tsx | 6 +- .../src/colDef/gridSingleSelectColDef.tsx | 24 ++++-- .../cell/GridEditSingleSelectCell.tsx | 56 +++++++++---- .../GridFilterInputMultipleSingleSelect.tsx | 80 +++++++++--------- .../GridFilterInputSingleSelect.tsx | 69 +++++++++------ .../panel/filterPanel/filterPanelUtils.ts | 23 +++-- .../grid/x-data-grid/src/internals/index.ts | 7 +- .../src/models/colDef/gridColDef.ts | 32 +++++-- scripts/x-data-grid-generator.exports.json | 2 +- 12 files changed, 262 insertions(+), 167 deletions(-) diff --git a/docs/pages/x/api/data-grid/grid-col-def.md b/docs/pages/x/api/data-grid/grid-col-def.md index 5ed97d384d84c..b5df5ecd3d97a 100644 --- a/docs/pages/x/api/data-grid/grid-col-def.md +++ b/docs/pages/x/api/data-grid/grid-col-def.md @@ -14,45 +14,44 @@ import { GridColDef } from '@mui/x-data-grid'; ## Properties -| Name | Type | Default | Description | -| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| aggregable? [](/x/introduction/licensing/#premium-plan) | boolean | true | If `true`, the cells of the column can be aggregated based. | -| align? | GridAlignment | | Allows to align the column values in cells. | -| availableAggregationFunctions? [](/x/introduction/licensing/#premium-plan) | string[] | | Limit the aggregation function usable on this column.
By default, the column will have all the aggregation functions that are compatible with its type. | -| cellClassName? | GridCellClassNamePropType<R, V> | | Class name that will be added in cells for that column. | -| colSpan? | number \| ((params: GridCellParams<R, V, F>) => number \| undefined) | 1 | Number of columns a cell should span. | -| description? | string | | The description of the column rendered as tooltip if the column header name is not fully displayed. | -| disableColumnMenu? | boolean | false | If `true`, the column menu is disabled for this column. | -| disableExport? | boolean | false | If `true`, this column will not be included in exports. | -| disableReorder? | boolean | false | If `true`, this column cannot be reordered. | -| editable? | boolean | false | If `true`, the cells of the column are editable. | -| field | string | | The column identifier. It's used to map with GridRowModel values. | -| filterable? | boolean | true | If `true`, the column is filterable. | -| filterOperators? | GridFilterOperator<R, V, F>[] | | Allows setting the filter operators for this column. | -| flex? | number | | If set, it indicates that a column has fluid width. Range [0, ∞). | -| getApplyQuickFilterFn? | (value: any, colDef: GridStateColDef, apiRef: React.MutableRefObject<GridApiCommunity>) => null \| ((params: GridCellParams<R, V, F>) => boolean) | | The callback that generates a filtering function for a given quick filter value.
This function can return `null` to skip filtering for this value and column. | -| groupable? | boolean | true | If `true`, the rows can be grouped based on this column values (pro-plan only).
Only available in DataGridPremium. | -| groupingValueGetter? [](/x/introduction/licensing/#premium-plan) | (params: GridGroupingValueGetterParams<R, V>) => GridKeyValue \| null \| undefined | | Function that transforms a complex cell value into a key that be used for grouping the rows. | -| headerAlign? | GridAlignment | | Header cell element alignment. | -| headerClassName? | GridColumnHeaderClassNamePropType | | Class name that will be added in the column header cell. | -| headerName? | string | | The title of the column rendered in the column header cell. | -| hideable? | boolean | true | If `false`, removes the buttons for hiding this column. | -| hideSortIcons? | boolean | false | Toggle the visibility of the sort icons. | -| maxWidth? | number | Infinity | Sets the maximum width of a column. | -| minWidth? | number | 50 | Sets the minimum width of a column. | -| pinnable? | boolean | true | If `false`, the menu items for column pinning menu will not be rendered.
Only available in DataGridPro. | -| preProcessEditCellProps? | (params: GridPreProcessEditCellProps) => GridEditCellProps \| Promise<GridEditCellProps> | | Callback fired when the edit props of the cell changes.
It allows to process the props that saved into the state. | -| renderCell? | (params: GridRenderCellParams<R, V, F>) => React.ReactNode | | Allows to override the component rendered as cell for this column. | -| renderEditCell? | (params: GridRenderEditCellParams<R, V, F>) => React.ReactNode | | Allows to override the component rendered in edit cell mode for this column. | -| renderHeader? | (params: GridColumnHeaderParams<R, V, F>) => React.ReactNode | | Allows to render a component in the column header cell. | -| resizable? | boolean | true | If `true`, the column is resizable. | -| sortable? | boolean | true | If `true`, the column is sortable. | -| sortComparator? | GridComparatorFn<V> | | A comparator function used to sort rows. | -| sortingOrder? | GridSortDirection[] | | The order of the sorting sequence. | -| type? | GridColType | 'actions' | Type allows to merge this object with a default definition [GridColDef](/x/api/data-grid/grid-col-def/). | -| valueFormatter? | (params: GridValueFormatterParams<V>) => F | | Function that allows to apply a formatter before rendering its value. | -| valueGetter? | (params: GridValueGetterParams<R, any>) => V | | Function that allows to get a specific data instead of field to render in the cell. | -| valueOptions? | Array<ValueOptions> \| ((params: GridValueOptionsParams<R>) => Array<ValueOptions>) | | To be used in combination with `type: 'singleSelect'`. This is an array (or a function returning an array) of the possible cell values and labels. | -| valueParser? | (value: F \| undefined, params?: GridCellParams<R, V, F>) => V | | Function that takes the user-entered value and converts it to a value used internally. | -| valueSetter? | (params: GridValueSetterParams<R, V>) => R | | Function that allows to customize how the entered value is stored in the row.
It only works with cell/row editing. | -| width? | number | 100 | Set the width of the column. | +| Name | Type | Default | Description | +| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| aggregable? [](/x/introduction/licensing/#premium-plan) | boolean | true | If `true`, the cells of the column can be aggregated based. | +| align? | GridAlignment | | Allows to align the column values in cells. | +| availableAggregationFunctions? [](/x/introduction/licensing/#premium-plan) | string[] | | Limit the aggregation function usable on this column.
By default, the column will have all the aggregation functions that are compatible with its type. | +| cellClassName? | GridCellClassNamePropType<R, V> | | Class name that will be added in cells for that column. | +| colSpan? | number \| ((params: GridCellParams<R, V, F>) => number \| undefined) | 1 | Number of columns a cell should span. | +| description? | string | | The description of the column rendered as tooltip if the column header name is not fully displayed. | +| disableColumnMenu? | boolean | false | If `true`, the column menu is disabled for this column. | +| disableExport? | boolean | false | If `true`, this column will not be included in exports. | +| disableReorder? | boolean | false | If `true`, this column cannot be reordered. | +| editable? | boolean | false | If `true`, the cells of the column are editable. | +| field | string | | The column identifier. It's used to map with GridRowModel values. | +| filterable? | boolean | true | If `true`, the column is filterable. | +| filterOperators? | GridFilterOperator<R, V, F>[] | | Allows setting the filter operators for this column. | +| flex? | number | | If set, it indicates that a column has fluid width. Range [0, ∞). | +| getApplyQuickFilterFn? | (value: any, colDef: GridStateColDef, apiRef: React.MutableRefObject<GridApiCommunity>) => null \| ((params: GridCellParams<R, V, F>) => boolean) | | The callback that generates a filtering function for a given quick filter value.
This function can return `null` to skip filtering for this value and column. | +| groupable? | boolean | true | If `true`, the rows can be grouped based on this column values (pro-plan only).
Only available in DataGridPremium. | +| groupingValueGetter? [](/x/introduction/licensing/#premium-plan) | (params: GridGroupingValueGetterParams<R, V>) => GridKeyValue \| null \| undefined | | Function that transforms a complex cell value into a key that be used for grouping the rows. | +| headerAlign? | GridAlignment | | Header cell element alignment. | +| headerClassName? | GridColumnHeaderClassNamePropType | | Class name that will be added in the column header cell. | +| headerName? | string | | The title of the column rendered in the column header cell. | +| hideable? | boolean | true | If `false`, removes the buttons for hiding this column. | +| hideSortIcons? | boolean | false | Toggle the visibility of the sort icons. | +| maxWidth? | number | Infinity | Sets the maximum width of a column. | +| minWidth? | number | 50 | Sets the minimum width of a column. | +| pinnable? | boolean | true | If `false`, the menu items for column pinning menu will not be rendered.
Only available in DataGridPro. | +| preProcessEditCellProps? | (params: GridPreProcessEditCellProps) => GridEditCellProps \| Promise<GridEditCellProps> | | Callback fired when the edit props of the cell changes.
It allows to process the props that saved into the state. | +| renderCell? | (params: GridRenderCellParams<R, V, F>) => React.ReactNode | | Allows to override the component rendered as cell for this column. | +| renderEditCell? | (params: GridRenderEditCellParams<R, V, F>) => React.ReactNode | | Allows to override the component rendered in edit cell mode for this column. | +| renderHeader? | (params: GridColumnHeaderParams<R, V, F>) => React.ReactNode | | Allows to render a component in the column header cell. | +| resizable? | boolean | true | If `true`, the column is resizable. | +| sortable? | boolean | true | If `true`, the column is sortable. | +| sortComparator? | GridComparatorFn<V> | | A comparator function used to sort rows. | +| sortingOrder? | GridSortDirection[] | | The order of the sorting sequence. | +| type? | GridColType | 'singleSelect' | Type allows to merge this object with a default definition [GridColDef](/x/api/data-grid/grid-col-def/). | +| valueFormatter? | (params: GridValueFormatterParams<V>) => F | | Function that allows to apply a formatter before rendering its value. | +| valueGetter? | (params: GridValueGetterParams<R, any>) => V | | Function that allows to get a specific data instead of field to render in the cell. | +| valueParser? | (value: F \| undefined, params?: GridCellParams<R, V, F>) => V | | Function that takes the user-entered value and converts it to a value used internally. | +| valueSetter? | (params: GridValueSetterParams<R, V>) => R | | Function that allows to customize how the entered value is stored in the row.
It only works with cell/row editing. | +| width? | number | 100 | Set the width of the column. | diff --git a/packages/grid/x-data-grid-generator/src/services/gridColDefGenerator.ts b/packages/grid/x-data-grid-generator/src/services/gridColDefGenerator.ts index f93a835816cbd..b082329b6ae7a 100644 --- a/packages/grid/x-data-grid-generator/src/services/gridColDefGenerator.ts +++ b/packages/grid/x-data-grid-generator/src/services/gridColDefGenerator.ts @@ -1,4 +1,4 @@ -import { GridBaseColDef } from '@mui/x-data-grid-pro/internals'; +import { GridColDef } from '@mui/x-data-grid-pro'; export interface GridDataGeneratorContext { /** @@ -10,7 +10,7 @@ export interface GridDataGeneratorContext { values?: Record; } -export interface GridColDefGenerator extends GridBaseColDef { +export type GridColDefGenerator = GridColDef & { generateData?: (row: any, context: GridDataGeneratorContext) => any; /** @@ -23,4 +23,4 @@ export interface GridColDefGenerator extends GridBaseColDef { * If `true`, the column will be marked as hidden in the `columnVisibilityModel`. */ hide?: boolean; -} +}; diff --git a/packages/grid/x-data-grid-premium/src/hooks/features/export/serializer/excelSerializer.ts b/packages/grid/x-data-grid-premium/src/hooks/features/export/serializer/excelSerializer.ts index 02d136ec3485a..ba97b71cac07b 100644 --- a/packages/grid/x-data-grid-premium/src/hooks/features/export/serializer/excelSerializer.ts +++ b/packages/grid/x-data-grid-premium/src/hooks/features/export/serializer/excelSerializer.ts @@ -8,7 +8,13 @@ import { GRID_DATE_COL_DEF, GRID_DATETIME_COL_DEF, } from '@mui/x-data-grid-pro'; -import { buildWarning, GridStateColDef, isObject } from '@mui/x-data-grid/internals'; +import { + buildWarning, + GridStateColDef, + GridSingleSelectColDef, + isObject, + isSingleSelectColDef, +} from '@mui/x-data-grid/internals'; import { GridExceljsProcessInput, ColumnsStylesInterface } from '../gridExcelExportInterface'; import { GridPrivateApiPremium } from '../../../../models/gridApiPremium'; @@ -23,7 +29,7 @@ const warnInvalidFormattedValue = buildWarning([ ]); const getFormattedValueOptions = ( - colDef: GridColDef, + colDef: GridSingleSelectColDef, valueOptions: ValueOptions[], api: GridApi, ) => { @@ -84,16 +90,17 @@ const serializeRow = ( switch (cellParams.colDef.type) { case 'singleSelect': { - if (typeof cellParams.colDef.valueOptions === 'function') { + const castColumn = cellParams.colDef as GridSingleSelectColDef; + if (typeof castColumn.valueOptions === 'function') { // If value option depends on the row, set specific options to the cell // This dataValidation is buggy with LibreOffice and does not allow to have coma - const valueOptions = cellParams.colDef.valueOptions({ id, row, field: cellParams.field }); - const formattedValueOptions = getFormattedValueOptions( - cellParams.colDef, - valueOptions, - api, - ); - dataValidation[column.field] = { + const valueOptions = castColumn.valueOptions({ + id, + row, + field: cellParams.field, + }); + const formattedValueOptions = getFormattedValueOptions(castColumn, valueOptions, api); + dataValidation[castColumn.field] = { type: 'list', allowBlank: true, formulae: [ @@ -104,23 +111,23 @@ const serializeRow = ( }; } else { // If value option is defined for the column, refer to another sheet - dataValidation[column.field] = { + dataValidation[castColumn.field] = { type: 'list', allowBlank: true, - formulae: [defaultValueOptionsFormulae[column.field]], + formulae: [defaultValueOptionsFormulae[castColumn.field]], }; } - const formattedValue = api.getCellParams(id, column.field).formattedValue; + const formattedValue = api.getCellParams(id, castColumn.field).formattedValue; if (process.env.NODE_ENV !== 'production') { if (String(cellParams.formattedValue) === '[object Object]') { warnInvalidFormattedValue(); } } if (isObject<{ label: any }>(formattedValue)) { - row[column.field] = formattedValue?.label; + row[castColumn.field] = formattedValue?.label; } else { - row[column.field] = formattedValue as any; + row[castColumn.field] = formattedValue as any; } break; } @@ -296,10 +303,10 @@ export async function buildExcel( const columnsWithArrayValueOptions = columns.filter( (column) => - column.type === 'singleSelect' && + isSingleSelectColDef(column) && column.valueOptions && typeof column.valueOptions !== 'function', - ); + ) as GridSingleSelectColDef[]; const defaultValueOptionsFormulae: { [field: string]: string } = {}; if (columnsWithArrayValueOptions.length) { diff --git a/packages/grid/x-data-grid-premium/src/hooks/features/rowGrouping/createGroupingColDef.tsx b/packages/grid/x-data-grid-premium/src/hooks/features/rowGrouping/createGroupingColDef.tsx index 5ac63eea820b9..40f3457d272fb 100644 --- a/packages/grid/x-data-grid-premium/src/hooks/features/rowGrouping/createGroupingColDef.tsx +++ b/packages/grid/x-data-grid-premium/src/hooks/features/rowGrouping/createGroupingColDef.tsx @@ -7,7 +7,7 @@ import { GridGroupingColDefOverride, GridGroupNode, } from '@mui/x-data-grid-pro'; -import { GridColumnRawLookup } from '@mui/x-data-grid-pro/internals'; +import { GridColumnRawLookup, isSingleSelectColDef } from '@mui/x-data-grid-pro/internals'; import { GridApiPremium } from '../../../models/gridApiPremium'; import { GridGroupingColumnFooterCell } from '../../../components/GridGroupingColumnFooterCell'; import { GridGroupingCriteriaCell } from '../../../components/GridGroupingCriteriaCell'; @@ -66,7 +66,7 @@ const getLeafProperties = (leafColDef: GridColDef): Partial => ({ headerName: leafColDef.headerName ?? leafColDef.field, sortable: leafColDef.sortable, filterable: leafColDef.filterable, - valueOptions: leafColDef.valueOptions, + valueOptions: isSingleSelectColDef(leafColDef) ? leafColDef.valueOptions : undefined, filterOperators: leafColDef.filterOperators?.map((operator) => ({ ...operator, getApplyFilterFn: (filterItem, column) => { @@ -94,7 +94,7 @@ const getGroupingCriteriaProperties = (groupedByColDef: GridColDef, applyHeaderN const properties: Partial = { sortable: groupedByColDef.sortable, filterable: groupedByColDef.filterable, - valueOptions: groupedByColDef.valueOptions, + valueOptions: isSingleSelectColDef(groupedByColDef) ? groupedByColDef.valueOptions : undefined, sortComparator: (v1, v2, cellParams1, cellParams2) => { // We only want to sort the groups of the current grouping criteria if ( diff --git a/packages/grid/x-data-grid/src/colDef/gridSingleSelectColDef.tsx b/packages/grid/x-data-grid/src/colDef/gridSingleSelectColDef.tsx index 7b1863f8c7174..8ed353a159f51 100644 --- a/packages/grid/x-data-grid/src/colDef/gridSingleSelectColDef.tsx +++ b/packages/grid/x-data-grid/src/colDef/gridSingleSelectColDef.tsx @@ -1,20 +1,34 @@ import { GRID_STRING_COL_DEF } from './gridStringColDef'; -import { GridColTypeDef, ValueOptions } from '../models/colDef/gridColDef'; +import { GridSingleSelectColDef, ValueOptions } from '../models/colDef/gridColDef'; import { renderEditSingleSelectCell } from '../components/cell/GridEditSingleSelectCell'; import { getGridSingleSelectOperators } from './gridSingleSelectOperators'; -import { getLabelFromValueOption } from '../components/panel/filterPanel/filterPanelUtils'; +import { isSingleSelectColDef } from '../components/panel/filterPanel/filterPanelUtils'; const isArrayOfObjects = (options: any): options is Array<{ value: any; label: string }> => { return typeof options[0] === 'object'; }; -export const GRID_SINGLE_SELECT_COL_DEF: GridColTypeDef = { +const defaultGetOptionValue = (value: ValueOptions) => { + return typeof value === 'object' ? value.value : value; +}; + +const defaultGetOptionLabel = (value: ValueOptions) => { + return typeof value === 'object' ? value.label : String(value); +}; + +export const GRID_SINGLE_SELECT_COL_DEF: Omit = { ...GRID_STRING_COL_DEF, type: 'singleSelect', + getOptionLabel: defaultGetOptionLabel, + getOptionValue: defaultGetOptionValue, valueFormatter(params) { const { id, field, value, api } = params; const colDef = params.api.getColumn(field); + if (!isSingleSelectColDef(colDef)) { + return ''; + } + let valueOptions: Array; if (typeof colDef.valueOptions === 'function') { valueOptions = colDef.valueOptions!({ id, row: id ? api.getRow(id) : null, field }); @@ -31,11 +45,11 @@ export const GRID_SINGLE_SELECT_COL_DEF: GridColTypeDef = { } if (!isArrayOfObjects(valueOptions)) { - return getLabelFromValueOption(value); + return colDef.getOptionLabel!(value); } const valueOption = valueOptions.find((option) => option.value === value); - return valueOption ? getLabelFromValueOption(valueOption) : ''; + return valueOption ? colDef.getOptionLabel!(valueOption) : ''; }, renderEditCell: renderEditSingleSelectCell, filterOperators: getGridSingleSelectOperators(), diff --git a/packages/grid/x-data-grid/src/components/cell/GridEditSingleSelectCell.tsx b/packages/grid/x-data-grid/src/components/cell/GridEditSingleSelectCell.tsx index eb17895500e8a..75df2bd732407 100644 --- a/packages/grid/x-data-grid/src/components/cell/GridEditSingleSelectCell.tsx +++ b/packages/grid/x-data-grid/src/components/cell/GridEditSingleSelectCell.tsx @@ -7,16 +7,17 @@ import { GridRenderEditCellParams } from '../../models/params/gridCellParams'; import { isEscapeKey } from '../../utils/keyboardUtils'; import { useGridRootProps } from '../../hooks/utils/useGridRootProps'; import { GridEditModes } from '../../models/gridEditRowModel'; -import { ValueOptions } from '../../models/colDef/gridColDef'; +import { GridSingleSelectColDef, ValueOptions } from '../../models/colDef/gridColDef'; import { - getLabelFromValueOption, getValueFromValueOptions, + isSingleSelectColDef, } from '../panel/filterPanel/filterPanelUtils'; import { useGridApiContext } from '../../hooks/utils/useGridApiContext'; export interface GridEditSingleSelectCellProps extends GridRenderEditCellParams, - Omit { + Omit, + Pick { /** * Callback called when the value is changed by the user. * @param {SelectChangeEvent} event The event source of the callback. @@ -28,12 +29,6 @@ export interface GridEditSingleSelectCellProps * If true, the select opens by default. */ initialOpen?: boolean; - /** - * Used to determine the text displayed for a given value option. - * @param {ValueOptions} value The current value option. - * @returns {string} The text to be displayed. - */ - getOptionLabel?: (value: ValueOptions) => string; } function isKeyboardEvent(event: any): event is React.KeyboardEvent { @@ -61,7 +56,8 @@ function GridEditSingleSelectCell(props: GridEditSingleSelectCellProps) { error, onValueChange, initialOpen = rootProps.editMode === GridEditModes.Cell, - getOptionLabel = getLabelFromValueOption, + getOptionLabel: getOptionLabelProp, + getOptionValue: getOptionValueProp, ...other } = props; @@ -73,18 +69,34 @@ function GridEditSingleSelectCell(props: GridEditSingleSelectCellProps) { const baseSelectProps = rootProps.componentsProps?.baseSelect || {}; const isSelectNative = baseSelectProps.native ?? false; - let valueOptions: Array; - if (typeof colDef.valueOptions === 'function') { - valueOptions = colDef.valueOptions!({ id, row, field }); + let resolvedColumn: GridSingleSelectColDef | null = null; + if (isSingleSelectColDef(colDef)) { + resolvedColumn = colDef; + } + + const getOptionValue = getOptionValueProp || resolvedColumn?.getOptionValue!; + const getOptionLabel = getOptionLabelProp || resolvedColumn?.getOptionLabel!; + + let valueOptions: Array | null = null; + if (typeof resolvedColumn?.valueOptions === 'function') { + valueOptions = resolvedColumn?.valueOptions!({ id, row, field }); } else { - valueOptions = colDef.valueOptions!; + valueOptions = resolvedColumn?.valueOptions!; } const handleChange: SelectProps['onChange'] = async (event) => { + if (!isSingleSelectColDef(colDef) || !valueOptions) { + return; + } + setOpen(false); const target = event.target as HTMLInputElement; // NativeSelect casts the value to a string. - const formattedTargetValue = getValueFromValueOptions(target.value, valueOptions); + const formattedTargetValue = getValueFromValueOptions( + target.value, + valueOptions, + getOptionValue, + ); if (onValueChange) { await onValueChange(event, formattedTargetValue); @@ -118,6 +130,10 @@ function GridEditSingleSelectCell(props: GridEditSingleSelectCellProps) { const OptionComponent = isSelectNative ? 'option' : MenuItem; + if (!valueOptions || !resolvedColumn) { + return null; + } + return ( {valueOptions.map((valueOption) => { - const value = typeof valueOption === 'object' ? valueOption.value : valueOption; + const value = getOptionValue(valueOption); return ( @@ -175,11 +191,17 @@ GridEditSingleSelectCell.propTypes = { */ formattedValue: PropTypes.any, /** - * Used to determine the text displayed for a given value option. + * Used to determine the label displayed for a given value option. * @param {ValueOptions} value The current value option. * @returns {string} The text to be displayed. */ getOptionLabel: PropTypes.func, + /** + * Used to determine the value used for a value option. + * @param {ValueOptions} value The current value option. + * @returns {string} The value to be used. + */ + getOptionValue: PropTypes.func, /** * If true, the cell is the active element. */ diff --git a/packages/grid/x-data-grid/src/components/panel/filterPanel/GridFilterInputMultipleSingleSelect.tsx b/packages/grid/x-data-grid/src/components/panel/filterPanel/GridFilterInputMultipleSingleSelect.tsx index 418e81812b6a6..01d6f43a13a65 100644 --- a/packages/grid/x-data-grid/src/components/panel/filterPanel/GridFilterInputMultipleSingleSelect.tsx +++ b/packages/grid/x-data-grid/src/components/panel/filterPanel/GridFilterInputMultipleSingleSelect.tsx @@ -3,10 +3,10 @@ import PropTypes from 'prop-types'; import Autocomplete, { AutocompleteProps, createFilterOptions } from '@mui/material/Autocomplete'; import Chip from '@mui/material/Chip'; import { unstable_useId as useId } from '@mui/utils'; -import { getLabelFromValueOption, getValueFromOption } from './filterPanelUtils'; import { useGridRootProps } from '../../../hooks/utils/useGridRootProps'; import { GridFilterInputValueProps } from './GridFilterInputValueProps'; -import type { ValueOptions } from '../../../models/colDef/gridColDef'; +import type { GridSingleSelectColDef, ValueOptions } from '../../../models/colDef/gridColDef'; +import { isSingleSelectColDef } from './filterPanelUtils'; export interface GridFilterInputMultipleSingleSelectProps extends Omit< @@ -22,24 +22,11 @@ export interface GridFilterInputMultipleSingleSelectProps | 'color' | 'getOptionLabel' >, + Pick, GridFilterInputValueProps { type?: 'singleSelect'; - /** - * Used to determine the text displayed for a given value option. - * @param {ValueOptions} value The current value option. - * @returns {string} The text to be displayed. - */ - getOptionLabel?: (value: ValueOptions) => string; } -const isOptionEqualToValue: AutocompleteProps< - ValueOptions, - true, - false, - true ->['isOptionEqualToValue'] = (option, value) => - getValueFromOption(option) === getValueFromOption(value); - const filter = createFilterOptions(); function GridFilterInputMultipleSingleSelect(props: GridFilterInputMultipleSingleSelectProps) { @@ -54,7 +41,8 @@ function GridFilterInputMultipleSingleSelect(props: GridFilterInputMultipleSingl helperText, size, variant = 'standard', - getOptionLabel = getLabelFromValueOption, + getOptionLabel: getOptionLabelProp, + getOptionValue: getOptionValueProp, ...other } = props; const TextFieldProps = { @@ -68,7 +56,22 @@ function GridFilterInputMultipleSingleSelect(props: GridFilterInputMultipleSingl const id = useId(); const rootProps = useGridRootProps(); - const resolvedColumn = item.field ? apiRef.current.getColumn(item.field) : null; + let resolvedColumn: GridSingleSelectColDef | null = null; + if (item.field) { + const column = apiRef.current.getColumn(item.field); + if (isSingleSelectColDef(column)) { + resolvedColumn = column; + } + } + + const getOptionValue = getOptionValueProp || resolvedColumn?.getOptionValue!; + const getOptionLabel = getOptionLabelProp || resolvedColumn?.getOptionLabel!; + + const isOptionEqualToValue = React.useCallback( + (option: ValueOptions, value: ValueOptions) => getOptionValue(option) === getOptionValue(value), + [getOptionValue], + ); + const resolvedValueOptions = React.useMemo(() => { if (!resolvedColumn?.valueOptions) { return []; @@ -80,26 +83,23 @@ function GridFilterInputMultipleSingleSelect(props: GridFilterInputMultipleSingl return resolvedColumn.valueOptions; }, [resolvedColumn]); + const resolvedFormattedValueOptions = React.useMemo(() => { - return resolvedValueOptions?.map(getValueFromOption); - }, [resolvedValueOptions]); + return resolvedValueOptions?.map(getOptionValue); + }, [resolvedValueOptions, getOptionValue]); // The value is computed from the item.value and used directly // If it was done by a useEffect/useState, the Autocomplete could receive incoherent value and options - const filterValues = React.useMemo(() => { + const filteredValues = React.useMemo(() => { if (!Array.isArray(item.value)) { return []; } if (resolvedValueOptions !== undefined) { const itemValueIndexes = item.value.map((element) => { - // get the index matching between values and valueOptions - const formattedElement = getValueFromOption(element); - const index = - resolvedFormattedValueOptions?.findIndex( - (formatedOption) => formatedOption === formattedElement, - ) || 0; - - return index; + // Gets the index matching between values and valueOptions + return resolvedFormattedValueOptions?.findIndex( + (formatedOption) => formatedOption === element, + ); }); return itemValueIndexes @@ -110,19 +110,19 @@ function GridFilterInputMultipleSingleSelect(props: GridFilterInputMultipleSingl }, [item.value, resolvedValueOptions, resolvedFormattedValueOptions]); React.useEffect(() => { - if (!Array.isArray(item.value) || filterValues.length !== item.value.length) { - // update the state if the filter value has been cleaned by the component - applyValue({ ...item, value: filterValues.map(getValueFromOption) }); + if (!Array.isArray(item.value) || filteredValues.length !== item.value.length) { + // Updates the state if the filter value has been cleaned by the component + applyValue({ ...item, value: filteredValues.map(getOptionValue) }); } - }, [item, filterValues, applyValue]); + }, [item, filteredValues, applyValue, getOptionValue]); const handleChange = React.useCallback< NonNullable['onChange']> >( (event, value) => { - applyValue({ ...item, value: [...value.map(getValueFromOption)] }); + applyValue({ ...item, value: value.map(getOptionValue) }); }, - [applyValue, item], + [applyValue, item, getOptionValue], ); return ( @@ -132,7 +132,7 @@ function GridFilterInputMultipleSingleSelect(props: GridFilterInputMultipleSingl isOptionEqualToValue={isOptionEqualToValue} filterOptions={filter} id={id} - value={filterValues} + value={filteredValues} onChange={handleChange} getOptionLabel={getOptionLabel} renderTags={(value, getTagProps) => @@ -179,11 +179,17 @@ GridFilterInputMultipleSingleSelect.propTypes = { PropTypes.object, ]), /** - * Used to determine the text displayed for a given value option. + * Used to determine the label displayed for a given value option. * @param {ValueOptions} value The current value option. * @returns {string} The text to be displayed. */ getOptionLabel: PropTypes.func, + /** + * Used to determine the value used for a value option. + * @param {ValueOptions} value The current value option. + * @returns {string} The value to be used. + */ + getOptionValue: PropTypes.func, item: PropTypes.shape({ field: PropTypes.string.isRequired, id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), diff --git a/packages/grid/x-data-grid/src/components/panel/filterPanel/GridFilterInputSingleSelect.tsx b/packages/grid/x-data-grid/src/components/panel/filterPanel/GridFilterInputSingleSelect.tsx index 26c95e3d794a1..1c09aad9b4985 100644 --- a/packages/grid/x-data-grid/src/components/panel/filterPanel/GridFilterInputSingleSelect.tsx +++ b/packages/grid/x-data-grid/src/components/panel/filterPanel/GridFilterInputSingleSelect.tsx @@ -4,14 +4,15 @@ import { TextFieldProps } from '@mui/material/TextField'; import { unstable_useId as useId } from '@mui/utils'; import MenuItem from '@mui/material/MenuItem'; import { GridFilterInputValueProps } from './GridFilterInputValueProps'; -import { GridColDef, ValueOptions } from '../../../models/colDef/gridColDef'; +import { GridSingleSelectColDef, ValueOptions } from '../../../models/colDef/gridColDef'; import { useGridRootProps } from '../../../hooks/utils/useGridRootProps'; -import { getLabelFromValueOption, getValueFromValueOptions } from './filterPanelUtils'; +import { getValueFromValueOptions, isSingleSelectColDef } from './filterPanelUtils'; const renderSingleSelectOptions = ( - { valueOptions, field }: GridColDef, + { valueOptions, field }: GridSingleSelectColDef, OptionComponent: React.ElementType, - getOptionLabel: (value: ValueOptions) => React.ReactNode, + getOptionLabel: (value: ValueOptions) => string, + getOptionValue: (value: ValueOptions) => any, ) => { const iterableColumnValues = typeof valueOptions === 'function' @@ -19,9 +20,7 @@ const renderSingleSelectOptions = ( : ['', ...(valueOptions || [])]; return iterableColumnValues.map((option) => { - const isOptionTypeObject = typeof option === 'object'; - - const value = isOptionTypeObject ? option.value : option; + const value = getOptionValue(option); const label = getOptionLabel(option); return ( @@ -33,14 +32,9 @@ const renderSingleSelectOptions = ( }; export type GridFilterInputSingleSelectProps = GridFilterInputValueProps & - TextFieldProps & { + TextFieldProps & + Pick & { type?: 'singleSelect'; - /** - * Used to determine the text displayed for a given value option. - * @param {ValueOptions} value The current value option. - * @returns {string} The text to be displayed. - */ - getOptionLabel?: (value: ValueOptions) => string; }; function GridFilterInputSingleSelect(props: GridFilterInputSingleSelectProps) { @@ -50,7 +44,8 @@ function GridFilterInputSingleSelect(props: GridFilterInputSingleSelectProps) { type, apiRef, focusElementRef, - getOptionLabel = getLabelFromValueOption, + getOptionLabel: getOptionLabelProp, + getOptionValue: getOptionValueProp, ...others } = props; const [filterValueState, setFilterValueState] = React.useState(item.value ?? ''); @@ -60,28 +55,37 @@ function GridFilterInputSingleSelect(props: GridFilterInputSingleSelectProps) { const baseSelectProps = rootProps.componentsProps?.baseSelect || {}; const isSelectNative = baseSelectProps.native ?? true; - const currentColumn = item.field ? apiRef.current.getColumn(item.field) : null; + let resolvedColumn: GridSingleSelectColDef | null = null; + if (item.field) { + const column = apiRef.current.getColumn(item.field); + if (isSingleSelectColDef(column)) { + resolvedColumn = column; + } + } + + const getOptionValue = getOptionValueProp || resolvedColumn?.getOptionValue!; + const getOptionLabel = getOptionLabelProp || resolvedColumn?.getOptionLabel!; const currentValueOptions = React.useMemo(() => { - if (currentColumn === null) { + if (!resolvedColumn) { return undefined; } - return typeof currentColumn.valueOptions === 'function' - ? currentColumn.valueOptions({ field: currentColumn.field }) - : currentColumn.valueOptions; - }, [currentColumn]); + return typeof resolvedColumn.valueOptions === 'function' + ? resolvedColumn.valueOptions({ field: resolvedColumn.field }) + : resolvedColumn.valueOptions; + }, [resolvedColumn]); const onFilterChange = React.useCallback( (event: React.ChangeEvent) => { let value = event.target.value; // NativeSelect casts the value to a string. - value = getValueFromValueOptions(value, currentValueOptions); + value = getValueFromValueOptions(value, currentValueOptions, getOptionValue); setFilterValueState(String(value)); applyValue({ ...item, value }); }, - [applyValue, item, currentValueOptions], + [currentValueOptions, getOptionValue, applyValue, item], ); React.useEffect(() => { @@ -89,7 +93,7 @@ function GridFilterInputSingleSelect(props: GridFilterInputSingleSelectProps) { if (currentValueOptions !== undefined) { // sanitize if valueOptions are provided - itemValue = getValueFromValueOptions(item.value, currentValueOptions); + itemValue = getValueFromValueOptions(item.value, currentValueOptions, getOptionValue); if (itemValue !== item.value) { applyValue({ ...item, value: itemValue }); return; @@ -101,7 +105,11 @@ function GridFilterInputSingleSelect(props: GridFilterInputSingleSelectProps) { itemValue = itemValue ?? ''; setFilterValueState(String(itemValue)); - }, [item, currentValueOptions, applyValue]); + }, [item, currentValueOptions, applyValue, getOptionValue]); + + if (!isSingleSelectColDef(resolvedColumn)) { + return null; + } return ( {renderSingleSelectOptions( - apiRef.current.getColumn(item.field), + resolvedColumn, isSelectNative ? 'option' : MenuItem, getOptionLabel, + getOptionValue, )} ); @@ -147,11 +156,17 @@ GridFilterInputSingleSelect.propTypes = { PropTypes.object, ]), /** - * Used to determine the text displayed for a given value option. + * Used to determine the label displayed for a given value option. * @param {ValueOptions} value The current value option. * @returns {string} The text to be displayed. */ getOptionLabel: PropTypes.func, + /** + * Used to determine the value used for a value option. + * @param {ValueOptions} value The current value option. + * @returns {string} The value to be used. + */ + getOptionValue: PropTypes.func, item: PropTypes.shape({ field: PropTypes.string.isRequired, id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), diff --git a/packages/grid/x-data-grid/src/components/panel/filterPanel/filterPanelUtils.ts b/packages/grid/x-data-grid/src/components/panel/filterPanel/filterPanelUtils.ts index 2d9d098ebc01e..639caf97f50d1 100644 --- a/packages/grid/x-data-grid/src/components/panel/filterPanel/filterPanelUtils.ts +++ b/packages/grid/x-data-grid/src/components/panel/filterPanel/filterPanelUtils.ts @@ -1,21 +1,26 @@ -import type { ValueOptions } from '../../../models/colDef/gridColDef'; +import type { + GridColDef, + GridSingleSelectColDef, + ValueOptions, +} from '../../../models/colDef/gridColDef'; -export function getValueFromOption(option: any | undefined) { - if (typeof option === 'object' && option !== null) { - return option.value; - } - return option; +export function isSingleSelectColDef(colDef: GridColDef | null): colDef is GridSingleSelectColDef { + return colDef?.type === 'singleSelect'; } -export function getValueFromValueOptions(value: string, valueOptions?: any[]) { +export function getValueFromValueOptions( + value: string, + valueOptions: any[] | undefined, + getOptionValue: (value: ValueOptions) => any, +) { if (valueOptions === undefined) { return undefined; } const result = valueOptions.find((option) => { - const optionValue = getValueFromOption(option); + const optionValue = getOptionValue(option); return String(optionValue) === String(value); }); - return getValueFromOption(result); + return getOptionValue(result); } export const getLabelFromValueOption = (valueOption: ValueOptions) => { diff --git a/packages/grid/x-data-grid/src/internals/index.ts b/packages/grid/x-data-grid/src/internals/index.ts index c96dcd343e100..7649c7b6eb452 100644 --- a/packages/grid/x-data-grid/src/internals/index.ts +++ b/packages/grid/x-data-grid/src/internals/index.ts @@ -1,4 +1,8 @@ -export type { GridBaseColDef, GridStateColDef } from '../models/colDef/gridColDef'; +export type { + GridBaseColDef, + GridStateColDef, + GridSingleSelectColDef, +} from '../models/colDef/gridColDef'; export { GridVirtualScroller } from '../components/virtualization/GridVirtualScroller'; export { GridVirtualScrollerContent } from '../components/virtualization/GridVirtualScrollerContent'; export { GridVirtualScrollerRenderZone } from '../components/virtualization/GridVirtualScrollerRenderZone'; @@ -37,6 +41,7 @@ export { useGridCsvExport } from '../hooks/features/export/useGridCsvExport'; export { useGridPrintExport } from '../hooks/features/export/useGridPrintExport'; export { useGridFilter, filterStateInitializer } from '../hooks/features/filter/useGridFilter'; export { passFilterLogic } from '../hooks/features/filter/gridFilterUtils'; +export { isSingleSelectColDef } from '../components/panel/filterPanel/filterPanelUtils'; export type { GridAggregatedFilterItemApplier } from '../hooks/features/filter/gridFilterState'; export { useGridFocus, focusStateInitializer } from '../hooks/features/focus/useGridFocus'; export { useGridKeyboardNavigation } from '../hooks/features/keyboardNavigation/useGridKeyboardNavigation'; diff --git a/packages/grid/x-data-grid/src/models/colDef/gridColDef.ts b/packages/grid/x-data-grid/src/models/colDef/gridColDef.ts index 410c668194e0b..49712984db2e4 100644 --- a/packages/grid/x-data-grid/src/models/colDef/gridColDef.ts +++ b/packages/grid/x-data-grid/src/models/colDef/gridColDef.ts @@ -114,10 +114,6 @@ export interface GridBaseColDef | ((params: GridValueOptionsParams) => Array); /** * Allows to align the column values in cells. */ @@ -257,12 +253,38 @@ export interface GridActionsColDef) => React.ReactElement[]; } +export interface GridSingleSelectColDef + extends GridBaseColDef { + /** + * Type allows to merge this object with a default definition [[GridColDef]]. + * @default 'singleSelect' + */ + type: 'singleSelect'; + /** + * To be used in combination with `type: 'singleSelect'`. This is an array (or a function returning an array) of the possible cell values and labels. + */ + valueOptions?: Array | ((params: GridValueOptionsParams) => Array); + /** + * Used to determine the label displayed for a given value option. + * @param {ValueOptions} value The current value option. + * @returns {string} The text to be displayed. + */ + getOptionLabel?: (value: ValueOptions) => string; + /** + * Used to determine the value used for a value option. + * @param {ValueOptions} value The current value option. + * @returns {string} The value to be used. + */ + getOptionValue?: (value: ValueOptions) => any; +} + /** * Column Definition interface. */ export type GridColDef = | GridBaseColDef - | GridActionsColDef; + | GridActionsColDef + | GridSingleSelectColDef; export type GridColTypeDef = Omit, 'field'> & { extendType?: GridNativeColTypes; diff --git a/scripts/x-data-grid-generator.exports.json b/scripts/x-data-grid-generator.exports.json index 4fe1b54b56522..e69f7b247f042 100644 --- a/scripts/x-data-grid-generator.exports.json +++ b/scripts/x-data-grid-generator.exports.json @@ -13,7 +13,7 @@ { "name": "getInitialState", "kind": "Variable" }, { "name": "getRealGridData", "kind": "Function" }, { "name": "GridBasicData", "kind": "Interface" }, - { "name": "GridColDefGenerator", "kind": "Interface" }, + { "name": "GridColDefGenerator", "kind": "TypeAlias" }, { "name": "GridDataGeneratorContext", "kind": "Interface" }, { "name": "GridDemoData", "kind": "Interface" }, { "name": "loadServerRows", "kind": "Variable" }, From b5e68656679264d6b6fcbbbd745957ab85f8de9d Mon Sep 17 00:00:00 2001 From: Matheus Wichman Date: Mon, 13 Feb 2023 20:44:21 -0300 Subject: [PATCH 2/9] Update docs --- .../column-definition/column-definition.md | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/docs/data/data-grid/column-definition/column-definition.md b/docs/data/data-grid/column-definition/column-definition.md index 4ec1481a9022e..eabdd27df9ec2 100644 --- a/docs/data/data-grid/column-definition/column-definition.md +++ b/docs/data/data-grid/column-definition/column-definition.md @@ -227,7 +227,29 @@ However, some types require additional properties to be set to make them work co ``` :::warning - When using objects values for `valueOptions` you need to provide `value` and `label` fields for each option: `{ value: string, label: string }` + When using objects values for `valueOptions` you need to provide the `value` and `label` attributes for each option. + However, you can customize which attribute is used as value and label by using `getOptionValue` and `getOptionLabel`, respectively. + + ```tsx + // Without getOptionValue and getOptionLabel + { + valueOptions: [ + { value: 'BR', label: 'Brazil' } + { value: 'FR', label: 'France' } + ] + } + + // With getOptionValue and getOptionLabel + { + getOptionValue: (value: ValueOptions) => value.code, + getOptionLabel: (value: ValueOptions) => value.label, + valueOptions: [ + { code: 'BR', name: 'Brazil' } + { code: 'FR', name: 'France' } + ] + } + ``` + ::: - If the column type is `'actions'`, you need to provide a `getActions` function that returns an array of actions available for each row (React elements). From e7b99a542d0666bb2bdd668d3cb452cdf999b62d Mon Sep 17 00:00:00 2001 From: Matheus Wichman Date: Mon, 13 Feb 2023 20:54:12 -0300 Subject: [PATCH 3/9] Fix wrong key --- docs/data/data-grid/column-definition/column-definition.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/data/data-grid/column-definition/column-definition.md b/docs/data/data-grid/column-definition/column-definition.md index eabdd27df9ec2..b3c7593980937 100644 --- a/docs/data/data-grid/column-definition/column-definition.md +++ b/docs/data/data-grid/column-definition/column-definition.md @@ -242,7 +242,7 @@ However, some types require additional properties to be set to make them work co // With getOptionValue and getOptionLabel { getOptionValue: (value: ValueOptions) => value.code, - getOptionLabel: (value: ValueOptions) => value.label, + getOptionLabel: (value: ValueOptions) => value.name, valueOptions: [ { code: 'BR', name: 'Brazil' } { code: 'FR', name: 'France' } From c15782cea522d0a44713841492ef19bb7d65b58b Mon Sep 17 00:00:00 2001 From: Matheus Wichman Date: Mon, 13 Feb 2023 22:28:51 -0300 Subject: [PATCH 4/9] Use `unknown` instead --- .../column-definition/column-definition.md | 4 ++-- .../src/colDef/gridSingleSelectColDef.tsx | 14 +++++++++----- .../x-data-grid/src/models/colDef/gridColDef.ts | 10 +++++----- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/docs/data/data-grid/column-definition/column-definition.md b/docs/data/data-grid/column-definition/column-definition.md index b3c7593980937..1e39f4f2c9866 100644 --- a/docs/data/data-grid/column-definition/column-definition.md +++ b/docs/data/data-grid/column-definition/column-definition.md @@ -241,8 +241,8 @@ However, some types require additional properties to be set to make them work co // With getOptionValue and getOptionLabel { - getOptionValue: (value: ValueOptions) => value.code, - getOptionLabel: (value: ValueOptions) => value.name, + getOptionValue: (value: any) => value.code, + getOptionLabel: (value: any) => value.name, valueOptions: [ { code: 'BR', name: 'Brazil' } { code: 'FR', name: 'France' } diff --git a/packages/grid/x-data-grid/src/colDef/gridSingleSelectColDef.tsx b/packages/grid/x-data-grid/src/colDef/gridSingleSelectColDef.tsx index 8ed353a159f51..18d2ee3a29645 100644 --- a/packages/grid/x-data-grid/src/colDef/gridSingleSelectColDef.tsx +++ b/packages/grid/x-data-grid/src/colDef/gridSingleSelectColDef.tsx @@ -8,12 +8,16 @@ const isArrayOfObjects = (options: any): options is Array<{ value: any; label: s return typeof options[0] === 'object'; }; -const defaultGetOptionValue = (value: ValueOptions) => { - return typeof value === 'object' ? value.value : value; +function isObject(value: unknown): value is Record { + return typeof value === 'object' && value !== null; +} + +const defaultGetOptionValue = (value: unknown) => { + return isObject(value) ? value.value : value; }; -const defaultGetOptionLabel = (value: ValueOptions) => { - return typeof value === 'object' ? value.label : String(value); +const defaultGetOptionLabel = (value: unknown) => { + return isObject(value) ? value.label : String(value); }; export const GRID_SINGLE_SELECT_COL_DEF: Omit = { @@ -48,7 +52,7 @@ export const GRID_SINGLE_SELECT_COL_DEF: Omit = return colDef.getOptionLabel!(value); } - const valueOption = valueOptions.find((option) => option.value === value); + const valueOption = valueOptions.find((option) => colDef.getOptionValue!(option) === value); return valueOption ? colDef.getOptionLabel!(valueOption) : ''; }, renderEditCell: renderEditSingleSelectCell, diff --git a/packages/grid/x-data-grid/src/models/colDef/gridColDef.ts b/packages/grid/x-data-grid/src/models/colDef/gridColDef.ts index 4c0da4828d964..e5d62bd5e45d0 100644 --- a/packages/grid/x-data-grid/src/models/colDef/gridColDef.ts +++ b/packages/grid/x-data-grid/src/models/colDef/gridColDef.ts @@ -25,7 +25,7 @@ import { GridApiCommunity } from '../api/gridApiCommunity'; */ export type GridAlignment = 'left' | 'right' | 'center'; -export type ValueOptions = string | number | { value: any; label: string }; +export type ValueOptions = string | number | { value: any; label: string } | Record; /** * Value that can be used as a key for grouping rows @@ -272,16 +272,16 @@ export interface GridSingleSelectColDef | ((params: GridValueOptionsParams) => Array); /** * Used to determine the label displayed for a given value option. - * @param {ValueOptions} value The current value option. + * @param {unknown} value The current value option. * @returns {string} The text to be displayed. */ - getOptionLabel?: (value: ValueOptions) => string; + getOptionLabel?: (value: unknown) => string; /** * Used to determine the value used for a value option. - * @param {ValueOptions} value The current value option. + * @param {unknown} value The current value option. * @returns {string} The value to be used. */ - getOptionValue?: (value: ValueOptions) => any; + getOptionValue?: (value: unknown) => any; } /** From 7a99d15544610efcbde208dd63dd54b47d7ed518 Mon Sep 17 00:00:00 2001 From: Matheus Wichman Date: Mon, 13 Feb 2023 22:49:16 -0300 Subject: [PATCH 5/9] yarn proptypes --- docs/pages/x/api/data-grid/grid-single-select-col-def.md | 4 ++-- .../src/components/cell/GridEditSingleSelectCell.tsx | 4 ++-- .../panel/filterPanel/GridFilterInputMultipleSingleSelect.tsx | 4 ++-- .../panel/filterPanel/GridFilterInputSingleSelect.tsx | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/pages/x/api/data-grid/grid-single-select-col-def.md b/docs/pages/x/api/data-grid/grid-single-select-col-def.md index 44bd9c064f7f7..6c4b1cdab08b9 100644 --- a/docs/pages/x/api/data-grid/grid-single-select-col-def.md +++ b/docs/pages/x/api/data-grid/grid-single-select-col-def.md @@ -31,8 +31,8 @@ import { GridSingleSelectColDef } from '@mui/x-data-grid'; | filterOperators? | GridFilterOperator<R, V, F>[] | | Allows setting the filter operators for this column. | | flex? | number | | If set, it indicates that a column has fluid width. Range [0, ∞). | | getApplyQuickFilterFn? | (value: any, colDef: GridStateColDef, apiRef: React.MutableRefObject<GridApiCommunity>) => null \| ((params: GridCellParams<R, V, F>) => boolean) | | The callback that generates a filtering function for a given quick filter value.
This function can return `null` to skip filtering for this value and column. | -| getOptionLabel? | (value: ValueOptions) => string | | Used to determine the label displayed for a given value option. | -| getOptionValue? | (value: ValueOptions) => any | | Used to determine the value used for a value option. | +| getOptionLabel? | (value: unknown) => string | | Used to determine the label displayed for a given value option. | +| getOptionValue? | (value: unknown) => any | | Used to determine the value used for a value option. | | groupable? | boolean | true | If `true`, the rows can be grouped based on this column values (pro-plan only).
Only available in DataGridPremium. | | groupingValueGetter? [](/x/introduction/licensing/#premium-plan) | (params: GridGroupingValueGetterParams<R, V>) => GridKeyValue \| null \| undefined | | Function that transforms a complex cell value into a key that be used for grouping the rows. | | headerAlign? | GridAlignment | | Header cell element alignment. | diff --git a/packages/grid/x-data-grid/src/components/cell/GridEditSingleSelectCell.tsx b/packages/grid/x-data-grid/src/components/cell/GridEditSingleSelectCell.tsx index 21816d12ecf93..35845513ddc91 100644 --- a/packages/grid/x-data-grid/src/components/cell/GridEditSingleSelectCell.tsx +++ b/packages/grid/x-data-grid/src/components/cell/GridEditSingleSelectCell.tsx @@ -195,13 +195,13 @@ GridEditSingleSelectCell.propTypes = { formattedValue: PropTypes.any, /** * Used to determine the label displayed for a given value option. - * @param {ValueOptions} value The current value option. + * @param {unknown} value The current value option. * @returns {string} The text to be displayed. */ getOptionLabel: PropTypes.func, /** * Used to determine the value used for a value option. - * @param {ValueOptions} value The current value option. + * @param {unknown} value The current value option. * @returns {string} The value to be used. */ getOptionValue: PropTypes.func, diff --git a/packages/grid/x-data-grid/src/components/panel/filterPanel/GridFilterInputMultipleSingleSelect.tsx b/packages/grid/x-data-grid/src/components/panel/filterPanel/GridFilterInputMultipleSingleSelect.tsx index 786502607f19d..16c23b5a9f192 100644 --- a/packages/grid/x-data-grid/src/components/panel/filterPanel/GridFilterInputMultipleSingleSelect.tsx +++ b/packages/grid/x-data-grid/src/components/panel/filterPanel/GridFilterInputMultipleSingleSelect.tsx @@ -180,13 +180,13 @@ GridFilterInputMultipleSingleSelect.propTypes = { ]), /** * Used to determine the label displayed for a given value option. - * @param {ValueOptions} value The current value option. + * @param {unknown} value The current value option. * @returns {string} The text to be displayed. */ getOptionLabel: PropTypes.func, /** * Used to determine the value used for a value option. - * @param {ValueOptions} value The current value option. + * @param {unknown} value The current value option. * @returns {string} The value to be used. */ getOptionValue: PropTypes.func, diff --git a/packages/grid/x-data-grid/src/components/panel/filterPanel/GridFilterInputSingleSelect.tsx b/packages/grid/x-data-grid/src/components/panel/filterPanel/GridFilterInputSingleSelect.tsx index 1a4ad1914e814..3e7f3e281e57b 100644 --- a/packages/grid/x-data-grid/src/components/panel/filterPanel/GridFilterInputSingleSelect.tsx +++ b/packages/grid/x-data-grid/src/components/panel/filterPanel/GridFilterInputSingleSelect.tsx @@ -161,13 +161,13 @@ GridFilterInputSingleSelect.propTypes = { ]), /** * Used to determine the label displayed for a given value option. - * @param {ValueOptions} value The current value option. + * @param {unknown} value The current value option. * @returns {string} The text to be displayed. */ getOptionLabel: PropTypes.func, /** * Used to determine the value used for a value option. - * @param {ValueOptions} value The current value option. + * @param {unknown} value The current value option. * @returns {string} The value to be used. */ getOptionValue: PropTypes.func, From e1e212099b155b47f95b07b3daf0e7f4803f4d3d Mon Sep 17 00:00:00 2001 From: Matheus Wichman Date: Tue, 14 Feb 2023 08:52:45 -0300 Subject: [PATCH 6/9] Add TS tests --- .../src/colDef/gridSingleSelectColDef.tsx | 8 ++++---- .../src/models/colDef/gridColDef.ts | 8 ++++---- .../x-data-grid/src/tests/DataGrid.spec.tsx | 20 +++++++++++++++++++ 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/packages/grid/x-data-grid/src/colDef/gridSingleSelectColDef.tsx b/packages/grid/x-data-grid/src/colDef/gridSingleSelectColDef.tsx index 18d2ee3a29645..51e84e7c14094 100644 --- a/packages/grid/x-data-grid/src/colDef/gridSingleSelectColDef.tsx +++ b/packages/grid/x-data-grid/src/colDef/gridSingleSelectColDef.tsx @@ -4,19 +4,19 @@ import { renderEditSingleSelectCell } from '../components/cell/GridEditSingleSel import { getGridSingleSelectOperators } from './gridSingleSelectOperators'; import { isSingleSelectColDef } from '../components/panel/filterPanel/filterPanelUtils'; -const isArrayOfObjects = (options: any): options is Array<{ value: any; label: string }> => { +const isArrayOfObjects = (options: any): options is Array> => { return typeof options[0] === 'object'; }; -function isObject(value: unknown): value is Record { +function isObject(value: any): value is Record { return typeof value === 'object' && value !== null; } -const defaultGetOptionValue = (value: unknown) => { +const defaultGetOptionValue = (value: ValueOptions) => { return isObject(value) ? value.value : value; }; -const defaultGetOptionLabel = (value: unknown) => { +const defaultGetOptionLabel = (value: ValueOptions) => { return isObject(value) ? value.label : String(value); }; diff --git a/packages/grid/x-data-grid/src/models/colDef/gridColDef.ts b/packages/grid/x-data-grid/src/models/colDef/gridColDef.ts index e5d62bd5e45d0..cdea807119b3a 100644 --- a/packages/grid/x-data-grid/src/models/colDef/gridColDef.ts +++ b/packages/grid/x-data-grid/src/models/colDef/gridColDef.ts @@ -272,16 +272,16 @@ export interface GridSingleSelectColDef | ((params: GridValueOptionsParams) => Array); /** * Used to determine the label displayed for a given value option. - * @param {unknown} value The current value option. + * @param {ValueOptions} value The current value option. * @returns {string} The text to be displayed. */ - getOptionLabel?: (value: unknown) => string; + getOptionLabel?: (value: ValueOptions) => string; /** * Used to determine the value used for a value option. - * @param {unknown} value The current value option. + * @param {ValueOptions} value The current value option. * @returns {string} The value to be used. */ - getOptionValue?: (value: unknown) => any; + getOptionValue?: (value: ValueOptions) => any; } /** diff --git a/packages/grid/x-data-grid/src/tests/DataGrid.spec.tsx b/packages/grid/x-data-grid/src/tests/DataGrid.spec.tsx index 19b4692de014e..8594b30c33d5a 100644 --- a/packages/grid/x-data-grid/src/tests/DataGrid.spec.tsx +++ b/packages/grid/x-data-grid/src/tests/DataGrid.spec.tsx @@ -165,6 +165,26 @@ function SingleSelectColDef() { type: 'singleSelect', valueOptions: ['United Kingdom', 'Spain', 'Brazil'], }, + { + field: 'country', + type: 'singleSelect', + valueOptions: [ + { value: 'UK', label: 'United Kingdom' }, + { value: 'ES', label: 'Spain' }, + { value: 'BR', label: 'Brazil' }, + ], + }, + { + field: 'country', + type: 'singleSelect', + getOptionValue: (value: any) => value.code, + getOptionLabel: (value: any) => value.label, + valueOptions: [ + { code: 'UK', name: 'United Kingdom' }, + { code: 'ES', name: 'Spain' }, + { code: 'BR', name: 'Brazil' }, + ], + }, ]} /> From 7bad45258c5a8b342a479099389a34e299bcd8c3 Mon Sep 17 00:00:00 2001 From: Matheus Wichman Date: Tue, 14 Feb 2023 09:07:31 -0300 Subject: [PATCH 7/9] yarn proptypes --- docs/pages/x/api/data-grid/grid-single-select-col-def.md | 4 ++-- .../src/components/cell/GridEditSingleSelectCell.tsx | 4 ++-- .../panel/filterPanel/GridFilterInputMultipleSingleSelect.tsx | 4 ++-- .../panel/filterPanel/GridFilterInputSingleSelect.tsx | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/pages/x/api/data-grid/grid-single-select-col-def.md b/docs/pages/x/api/data-grid/grid-single-select-col-def.md index 6c4b1cdab08b9..44bd9c064f7f7 100644 --- a/docs/pages/x/api/data-grid/grid-single-select-col-def.md +++ b/docs/pages/x/api/data-grid/grid-single-select-col-def.md @@ -31,8 +31,8 @@ import { GridSingleSelectColDef } from '@mui/x-data-grid'; | filterOperators? | GridFilterOperator<R, V, F>[] | | Allows setting the filter operators for this column. | | flex? | number | | If set, it indicates that a column has fluid width. Range [0, ∞). | | getApplyQuickFilterFn? | (value: any, colDef: GridStateColDef, apiRef: React.MutableRefObject<GridApiCommunity>) => null \| ((params: GridCellParams<R, V, F>) => boolean) | | The callback that generates a filtering function for a given quick filter value.
This function can return `null` to skip filtering for this value and column. | -| getOptionLabel? | (value: unknown) => string | | Used to determine the label displayed for a given value option. | -| getOptionValue? | (value: unknown) => any | | Used to determine the value used for a value option. | +| getOptionLabel? | (value: ValueOptions) => string | | Used to determine the label displayed for a given value option. | +| getOptionValue? | (value: ValueOptions) => any | | Used to determine the value used for a value option. | | groupable? | boolean | true | If `true`, the rows can be grouped based on this column values (pro-plan only).
Only available in DataGridPremium. | | groupingValueGetter? [](/x/introduction/licensing/#premium-plan) | (params: GridGroupingValueGetterParams<R, V>) => GridKeyValue \| null \| undefined | | Function that transforms a complex cell value into a key that be used for grouping the rows. | | headerAlign? | GridAlignment | | Header cell element alignment. | diff --git a/packages/grid/x-data-grid/src/components/cell/GridEditSingleSelectCell.tsx b/packages/grid/x-data-grid/src/components/cell/GridEditSingleSelectCell.tsx index 35845513ddc91..21816d12ecf93 100644 --- a/packages/grid/x-data-grid/src/components/cell/GridEditSingleSelectCell.tsx +++ b/packages/grid/x-data-grid/src/components/cell/GridEditSingleSelectCell.tsx @@ -195,13 +195,13 @@ GridEditSingleSelectCell.propTypes = { formattedValue: PropTypes.any, /** * Used to determine the label displayed for a given value option. - * @param {unknown} value The current value option. + * @param {ValueOptions} value The current value option. * @returns {string} The text to be displayed. */ getOptionLabel: PropTypes.func, /** * Used to determine the value used for a value option. - * @param {unknown} value The current value option. + * @param {ValueOptions} value The current value option. * @returns {string} The value to be used. */ getOptionValue: PropTypes.func, diff --git a/packages/grid/x-data-grid/src/components/panel/filterPanel/GridFilterInputMultipleSingleSelect.tsx b/packages/grid/x-data-grid/src/components/panel/filterPanel/GridFilterInputMultipleSingleSelect.tsx index 16c23b5a9f192..786502607f19d 100644 --- a/packages/grid/x-data-grid/src/components/panel/filterPanel/GridFilterInputMultipleSingleSelect.tsx +++ b/packages/grid/x-data-grid/src/components/panel/filterPanel/GridFilterInputMultipleSingleSelect.tsx @@ -180,13 +180,13 @@ GridFilterInputMultipleSingleSelect.propTypes = { ]), /** * Used to determine the label displayed for a given value option. - * @param {unknown} value The current value option. + * @param {ValueOptions} value The current value option. * @returns {string} The text to be displayed. */ getOptionLabel: PropTypes.func, /** * Used to determine the value used for a value option. - * @param {unknown} value The current value option. + * @param {ValueOptions} value The current value option. * @returns {string} The value to be used. */ getOptionValue: PropTypes.func, diff --git a/packages/grid/x-data-grid/src/components/panel/filterPanel/GridFilterInputSingleSelect.tsx b/packages/grid/x-data-grid/src/components/panel/filterPanel/GridFilterInputSingleSelect.tsx index 3e7f3e281e57b..1a4ad1914e814 100644 --- a/packages/grid/x-data-grid/src/components/panel/filterPanel/GridFilterInputSingleSelect.tsx +++ b/packages/grid/x-data-grid/src/components/panel/filterPanel/GridFilterInputSingleSelect.tsx @@ -161,13 +161,13 @@ GridFilterInputSingleSelect.propTypes = { ]), /** * Used to determine the label displayed for a given value option. - * @param {unknown} value The current value option. + * @param {ValueOptions} value The current value option. * @returns {string} The text to be displayed. */ getOptionLabel: PropTypes.func, /** * Used to determine the value used for a value option. - * @param {unknown} value The current value option. + * @param {ValueOptions} value The current value option. * @returns {string} The value to be used. */ getOptionValue: PropTypes.func, From ea6384bcd2dc798d0664c75ec55c32e8a94e68ed Mon Sep 17 00:00:00 2001 From: Matheus Wichman Date: Thu, 16 Feb 2023 13:10:09 -0300 Subject: [PATCH 8/9] Reuse `isObject` --- .../grid/x-data-grid/src/colDef/gridSingleSelectColDef.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/grid/x-data-grid/src/colDef/gridSingleSelectColDef.tsx b/packages/grid/x-data-grid/src/colDef/gridSingleSelectColDef.tsx index 51e84e7c14094..225659e9a32ca 100644 --- a/packages/grid/x-data-grid/src/colDef/gridSingleSelectColDef.tsx +++ b/packages/grid/x-data-grid/src/colDef/gridSingleSelectColDef.tsx @@ -3,15 +3,12 @@ import { GridSingleSelectColDef, ValueOptions } from '../models/colDef/gridColDe import { renderEditSingleSelectCell } from '../components/cell/GridEditSingleSelectCell'; import { getGridSingleSelectOperators } from './gridSingleSelectOperators'; import { isSingleSelectColDef } from '../components/panel/filterPanel/filterPanelUtils'; +import { isObject } from '../utils/utils'; const isArrayOfObjects = (options: any): options is Array> => { return typeof options[0] === 'object'; }; -function isObject(value: any): value is Record { - return typeof value === 'object' && value !== null; -} - const defaultGetOptionValue = (value: ValueOptions) => { return isObject(value) ? value.value : value; }; From 97dfb27762f24c04c08793efe019cafb71c2dd32 Mon Sep 17 00:00:00 2001 From: Matheus Wichman Date: Thu, 16 Feb 2023 13:15:23 -0300 Subject: [PATCH 9/9] Reuse definitions --- .../panel/filterPanel/GridFilterInputSingleSelect.tsx | 6 +++--- .../src/components/panel/filterPanel/filterPanelUtils.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/grid/x-data-grid/src/components/panel/filterPanel/GridFilterInputSingleSelect.tsx b/packages/grid/x-data-grid/src/components/panel/filterPanel/GridFilterInputSingleSelect.tsx index 1a4ad1914e814..c62a5beec5c67 100644 --- a/packages/grid/x-data-grid/src/components/panel/filterPanel/GridFilterInputSingleSelect.tsx +++ b/packages/grid/x-data-grid/src/components/panel/filterPanel/GridFilterInputSingleSelect.tsx @@ -4,15 +4,15 @@ import { TextFieldProps } from '@mui/material/TextField'; import { unstable_useId as useId } from '@mui/utils'; import MenuItem from '@mui/material/MenuItem'; import { GridFilterInputValueProps } from './GridFilterInputValueProps'; -import { GridSingleSelectColDef, ValueOptions } from '../../../models/colDef/gridColDef'; +import { GridSingleSelectColDef } from '../../../models/colDef/gridColDef'; import { useGridRootProps } from '../../../hooks/utils/useGridRootProps'; import { getValueFromValueOptions, isSingleSelectColDef } from './filterPanelUtils'; const renderSingleSelectOptions = ( { valueOptions, field }: GridSingleSelectColDef, OptionComponent: React.ElementType, - getOptionLabel: (value: ValueOptions) => string, - getOptionValue: (value: ValueOptions) => any, + getOptionLabel: NonNullable, + getOptionValue: NonNullable, ) => { const iterableColumnValues = typeof valueOptions === 'function' diff --git a/packages/grid/x-data-grid/src/components/panel/filterPanel/filterPanelUtils.ts b/packages/grid/x-data-grid/src/components/panel/filterPanel/filterPanelUtils.ts index 639caf97f50d1..8a7958af0c8b6 100644 --- a/packages/grid/x-data-grid/src/components/panel/filterPanel/filterPanelUtils.ts +++ b/packages/grid/x-data-grid/src/components/panel/filterPanel/filterPanelUtils.ts @@ -11,7 +11,7 @@ export function isSingleSelectColDef(colDef: GridColDef | null): colDef is GridS export function getValueFromValueOptions( value: string, valueOptions: any[] | undefined, - getOptionValue: (value: ValueOptions) => any, + getOptionValue: NonNullable, ) { if (valueOptions === undefined) { return undefined;