Skip to content

Commit

Permalink
[DataGrid] Fix number filter field formatting values while typing (mu…
Browse files Browse the repository at this point in the history
arminmeh authored Jan 3, 2025

Verified

This commit was signed with the committer’s verified signature.
targos Michaël Zasso
1 parent d85fff1 commit 5f0cd45
Showing 3 changed files with 67 additions and 18 deletions.
Original file line number Diff line number Diff line change
@@ -89,7 +89,7 @@ const useUtilityClasses = (ownerState: OwnerState) => {
return composeClasses(slots, getDataGridUtilityClass, classes);
};

const dateSx = {
const emptyFieldSx = {
[`& input[value=""]:not(:focus)`]: { color: 'transparent' },
};
const defaultInputComponents: { [key in GridColType]: React.JSXElementConstructor<any> | null } = {
@@ -366,7 +366,11 @@ const GridHeaderFilterCell = forwardRef<HTMLDivElement, GridHeaderFilterCellProp
disabled={isFilterReadOnly || isNoInputOperator}
tabIndex={-1}
InputLabelProps={null}
sx={colDef.type === 'date' || colDef.type === 'dateTime' ? dateSx : undefined}
sx={
colDef.type === 'date' || colDef.type === 'dateTime' || colDef.type === 'number'
? emptyFieldSx
: undefined
}
{...(isNoInputOperator ? { value: '' } : {})}
{...currentOperator?.InputComponentProps}
{...InputComponentProps}
55 changes: 51 additions & 4 deletions packages/x-data-grid-pro/src/tests/filtering.DataGridPro.test.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import * as React from 'react';
import { createRenderer, fireEvent, screen, act, within, waitFor } from '@mui/internal-test-utils';
import { expect } from 'chai';
import { spy } from 'sinon';
import {
getDefaultGridFilterModel,
GridApi,
@@ -18,10 +22,6 @@ import {
getGridStringOperators,
GridFilterItem,
} from '@mui/x-data-grid-pro';
import { createRenderer, fireEvent, screen, act, within } from '@mui/internal-test-utils';
import { expect } from 'chai';
import * as React from 'react';
import { spy } from 'sinon';
import { getColumnHeaderCell, getColumnValues, getSelectInput, grid } from 'test/utils/helperFn';
import { testSkipIf, isJSDOM } from 'test/utils/skipIf';

@@ -1181,6 +1181,53 @@ describe('<DataGridPro /> - Filter', () => {
expect(getRows({ operator: 'is', value: null })).to.deep.equal(ALL_ROWS);
expect(getRows({ operator: 'is', value: 'test' })).to.deep.equal(ALL_ROWS); // Ignores invalid values
});

it('should allow temporary invalid values while updating the number filter', async () => {
clock.restore();
const changeSpy = spy();
const { user } = render(
<TestCase
rows={[
{ id: 1, amount: -10 },
{ id: 2, amount: 10 },
{ id: 3, amount: 100 },
{ id: 4, amount: 1000 },
]}
columns={[{ field: 'amount', type: 'number' }]}
headerFilters
onFilterModelChange={changeSpy}
/>,
);
expect(getColumnValues(0)).to.deep.equal(['-10', '10', '100', '1,000']);

const filterCell = getColumnHeaderCell(0, 1);
await user.click(within(filterCell).getByLabelText('Operator'));
await user.click(screen.getByRole('menuitem', { name: 'Greater than' }));

const input = within(filterCell).getByLabelText('Greater than');
await user.click(input);
expect(input).toHaveFocus();

await user.keyboard('0');
await waitFor(() => expect(getColumnValues(0)).to.deep.equal(['10', '100', '1,000']));
expect(changeSpy.lastCall.args[0].items[0].value).to.equal(0);

await user.keyboard('.');
await waitFor(() => expect(getColumnValues(0)).to.deep.equal(['10', '100', '1,000']));
expect(changeSpy.lastCall.args[0].items[0].value).to.equal(0); // 0.

await user.keyboard('1');
await waitFor(() => expect(getColumnValues(0)).to.deep.equal(['10', '100', '1,000']));
await waitFor(() => expect(changeSpy.lastCall.args[0].items[0].value).to.equal(0.1)); // 0.1

await user.keyboard('e');
await waitFor(() => expect(getColumnValues(0)).to.deep.equal(['-10', '10', '100', '1,000']));
expect(changeSpy.lastCall.args[0].items[0].value).to.equal(undefined); // 0.1e

await user.keyboard('2');
await waitFor(() => expect(getColumnValues(0)).to.deep.equal(['100', '1,000']));
expect(changeSpy.lastCall.args[0].items[0].value).to.equal(10); // 0.1e2
});
});

describe('Read-only filters', () => {
Original file line number Diff line number Diff line change
@@ -37,23 +37,23 @@ function GridFilterInputValue(props: GridTypeFilterInputValueProps) {
} = props;

const filterTimeout = useTimeout();
const [filterValueState, setFilterValueState] = React.useState<string | number | undefined>(
sanitizeFilterItemValue(item.value, type),
const [filterValueState, setFilterValueState] = React.useState<string | undefined>(
sanitizeFilterItemValue(item.value),
);
const [applying, setIsApplying] = React.useState(false);
const id = useId();
const rootProps = useGridRootProps();

const onFilterChange = React.useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
const value = sanitizeFilterItemValue(event.target.value, type);
setFilterValueState(value);
const value = sanitizeFilterItemValue(event.target.value);

setFilterValueState(value);
setIsApplying(true);
filterTimeout.start(rootProps.filterDebounceMs, () => {
const newItem = {
...item,
value,
value: type === 'number' && !Number.isNaN(Number(value)) ? Number(value) : value,
fromInput: id!,
};
applyValue(newItem);
@@ -66,16 +66,16 @@ function GridFilterInputValue(props: GridTypeFilterInputValueProps) {
React.useEffect(() => {
const itemPlusTag = item as ItemPlusTag;
if (itemPlusTag.fromInput !== id || item.value == null) {
setFilterValueState(sanitizeFilterItemValue(item.value, type));
setFilterValueState(sanitizeFilterItemValue(item.value));
}
}, [id, item, type]);
}, [id, item]);

return (
<rootProps.slots.baseTextField
id={id}
label={apiRef.current.getLocaleText('filterPanelInputLabel')}
placeholder={apiRef.current.getLocaleText('filterPanelInputPlaceholder')}
value={filterValueState === undefined ? '' : String(filterValueState)}
value={filterValueState ?? ''}
onChange={onFilterChange}
variant={variant}
type={type || 'text'}
@@ -106,13 +106,11 @@ function GridFilterInputValue(props: GridTypeFilterInputValueProps) {
);
}

function sanitizeFilterItemValue(value: any, type: GridTypeFilterInputValueProps['type']) {
function sanitizeFilterItemValue(value: unknown) {
if (value == null || value === '') {
return undefined;
}
if (type === 'number') {
return Number(value);
}

return String(value);
}

0 comments on commit 5f0cd45

Please sign in to comment.