Skip to content

Commit

Permalink
[DataGrid] Fix number filter field formatting values while typing (#1…
Browse files Browse the repository at this point in the history
  • Loading branch information
arminmeh authored Jan 3, 2025
1 parent d85fff1 commit 5f0cd45
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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 } = {
Expand Down Expand Up @@ -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}
Expand Down
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,
Expand All @@ -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';

Expand Down Expand Up @@ -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', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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'}
Expand Down Expand Up @@ -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);
}

Expand Down

0 comments on commit 5f0cd45

Please sign in to comment.