Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DataGrid] Add new List view selector #15024

Closed
wants to merge 12 commits into from
Closed
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
GridDataSourceGroupNode,
useGridSelector,
} from '@mui/x-data-grid';
import { useGridSelectorV8 } from '@mui/x-data-grid/internals';
import CircularProgress from '@mui/material/CircularProgress';
import { useGridRootProps } from '../hooks/utils/useGridRootProps';
import { useGridPrivateApiContext } from '../hooks/utils/useGridPrivateApiContext';
Expand Down Expand Up @@ -55,8 +54,8 @@ function GridTreeDataGroupingCellIcon(props: GridTreeDataGroupingCellIconProps)
const classes = useUtilityClasses(rootProps);
const { rowNode, id, field, descendantCount } = props;

const isDataLoading = useGridSelectorV8(apiRef, gridDataSourceLoadingIdSelector, id);
const error = useGridSelectorV8(apiRef, gridDataSourceErrorSelector, id);
const isDataLoading = useGridSelector(apiRef, gridDataSourceLoadingIdSelector, id);
const error = useGridSelector(apiRef, gridDataSourceErrorSelector, id);

const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
if (!rowNode.childrenExpanded) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
gridPaginationModelSelector,
GridRowId,
} from '@mui/x-data-grid';
import { createSelector, createSelectorV8 } from '@mui/x-data-grid/internals';
import { createSelector } from '@mui/x-data-grid/internals';
import { GridStatePro } from '../../../models/gridStatePro';

const computeStartEnd = (paginationModel: GridPaginationModel) => {
Expand Down Expand Up @@ -38,7 +38,7 @@ export const gridDataSourceLoadingSelector = createSelector(
(dataSource) => dataSource.loading,
);

export const gridDataSourceLoadingIdSelector = createSelectorV8(
export const gridDataSourceLoadingIdSelector = createSelector(
gridDataSourceStateSelector,
(dataSource, id: GridRowId) => dataSource.loading[id] ?? false,
);
Expand All @@ -48,7 +48,7 @@ export const gridDataSourceErrorsSelector = createSelector(
(dataSource) => dataSource.errors,
);

export const gridDataSourceErrorSelector = createSelectorV8(
export const gridDataSourceErrorSelector = createSelector(
gridDataSourceStateSelector,
(dataSource, id: GridRowId) => dataSource.errors[id],
);
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@ import {
getRightColumnIndex,
findNonRowSpannedCell,
} from './utils';
import { gridListColumnSelector } from '../listView/gridListViewSelectors';
import {
gridListColumnSelector,
gridListViewVisibleColumnSelector,
} from '../listView/gridListViewSelectors';

/**
* @requires useGridSorting (method) - can be after
Expand Down Expand Up @@ -91,6 +94,7 @@ export const useGridKeyboardNavigation = (
colIndex = nextCellColSpanInfo.rightVisibleCellIndex;
}
}

const field = listView
? gridListColumnSelector(apiRef.current.state)!.field
: gridVisibleColumnFieldsSelector(apiRef)[colIndex];
Expand Down Expand Up @@ -512,9 +516,7 @@ export const useGridKeyboardNavigation = (
const firstRowIndexInPage = 0;
const lastRowIndexInPage = currentPageRows.length - 1;
const firstColIndex = 0;
const visibleColumns = listView
? [gridListColumnSelector(apiRef.current.state)]
: gridVisibleColumnDefinitionsSelector(apiRef);
const visibleColumns = gridListViewVisibleColumnSelector(apiRef, listView);
const lastColIndex = visibleColumns.length - 1;
let shouldPreventDefault = true;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
import { createSelector } from '../../../utils/createSelector';
import { GridStateCommunity } from '../../../models/gridStateCommunity';
import { gridVisibleColumnDefinitionsSelector } from '../columns';

/**
* Get a list column definition
*/
export const gridListColumnSelector = (state: GridStateCommunity) => state.listViewColumn;

export const gridListViewVisibleColumnSelector = createSelector(
gridVisibleColumnDefinitionsSelector,
gridListColumnSelector,
(visibleColumn, listViewColumn, listView) => {
if (listView) {
return [listViewColumn];
}

return visibleColumn;
},
);
13 changes: 4 additions & 9 deletions packages/x-data-grid/src/hooks/features/scroll/useGridScroll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@ import { useRtl } from '@mui/system/RtlProvider';
import { GridCellIndexCoordinates } from '../../../models/gridCell';
import { GridPrivateApiCommunity } from '../../../models/api/gridApiCommunity';
import { useGridLogger } from '../../utils/useGridLogger';
import {
gridColumnPositionsSelector,
gridVisibleColumnDefinitionsSelector,
} from '../columns/gridColumnsSelector';
import { gridColumnPositionsSelector } from '../columns/gridColumnsSelector';
import { useGridSelector } from '../../utils/useGridSelector';
import { DataGridProcessedProps } from '../../../models/props/DataGridProps';
import { gridPageSelector, gridPageSizeSelector } from '../pagination/gridPaginationSelector';
Expand All @@ -17,7 +14,7 @@ import { GridScrollApi } from '../../../models/api/gridScrollApi';
import { useGridApiMethod } from '../../utils/useGridApiMethod';
import { gridExpandedSortedRowEntriesSelector } from '../filter/gridFilterSelector';
import { gridDimensionsSelector } from '../dimensions';
import { gridListColumnSelector } from '../listView/gridListViewSelectors';
import { gridListViewVisibleColumnSelector } from '../listView/gridListViewSelectors';

// Logic copied from https://www.w3.org/TR/wai-aria-practices/examples/listbox/js/listbox.js
// Similar to https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView
Expand Down Expand Up @@ -68,9 +65,7 @@ export const useGridScroll = (
(params: Partial<GridCellIndexCoordinates>) => {
const dimensions = gridDimensionsSelector(apiRef.current.state);
const totalRowCount = gridRowCountSelector(apiRef);
const visibleColumns = props.unstable_listView
? [gridListColumnSelector(apiRef.current.state)!]
: gridVisibleColumnDefinitionsSelector(apiRef);
const visibleColumns = gridListViewVisibleColumnSelector(apiRef, props.unstable_listView);
const scrollToHeader = params.rowIndex == null;
if ((!scrollToHeader && totalRowCount === 0) || visibleColumns.length === 0) {
return false;
Expand All @@ -97,7 +92,7 @@ export const useGridScroll = (
}

if (typeof cellWidth === 'undefined') {
cellWidth = visibleColumns[params.colIndex].computedWidth;
cellWidth = visibleColumns[params.colIndex]!.computedWidth;
}
// When using RTL, `scrollLeft` becomes negative, so we must ensure that we only compare values.
scrollCoordinates.left = scrollIntoView({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import type {
GridColumnsRenderContext,
GridRowEntry,
GridRowId,
GridColDef,
} from '../../../models';
import { selectedIdsLookupSelector } from '../rowSelection/gridRowSelectionSelector';
import { gridRowsMetaSelector } from '../rows/gridRowsMetaSelector';
Expand All @@ -46,7 +47,7 @@ import {
} from './gridVirtualizationSelectors';
import { EMPTY_RENDER_CONTEXT } from './useGridVirtualization';
import { gridRowSpanningHiddenCellsOriginMapSelector } from '../rows/gridRowSpanningSelectors';
import { gridListColumnSelector } from '../listView/gridListViewSelectors';
import { gridListViewVisibleColumnSelector } from '../listView/gridListViewSelectors';

const MINIMUM_COLUMN_WIDTH = 50;

Expand Down Expand Up @@ -101,11 +102,11 @@ export const useGridVirtualScroller = () => {
const apiRef = useGridPrivateApiContext() as React.MutableRefObject<PrivateApiWithInfiniteLoader>;
const rootProps = useGridRootProps();
const { unstable_listView: listView } = rootProps;
const visibleColumns = useGridSelector(apiRef, () =>
listView
? [gridListColumnSelector(apiRef.current.state)!]
: gridVisibleColumnDefinitionsSelector(apiRef),
);
const visibleColumns = useGridSelector(
apiRef,
gridListViewVisibleColumnSelector,
listView,
) as any;
const enabledForRows = useGridSelector(apiRef, gridVirtualizationRowEnabledSelector) && !isJSDOM;
const enabledForColumns =
useGridSelector(apiRef, gridVirtualizationColumnEnabledSelector) && !isJSDOM;
Expand Down Expand Up @@ -175,7 +176,9 @@ export const useGridVirtualScroller = () => {
),
columnIndex: React.useMemo(
() =>
cellFocus ? visibleColumns.findIndex((column) => column.field === cellFocus.field) : -1,
cellFocus
? visibleColumns.findIndex((column: GridColDef) => column.field === cellFocus.field)
: -1,
[cellFocus, visibleColumns],
),
};
Expand Down Expand Up @@ -656,9 +659,10 @@ function inputsSelector(
): RenderContextInputs {
const dimensions = gridDimensionsSelector(apiRef.current.state);
const currentPage = getVisibleRows(apiRef, rootProps);
const visibleColumns = rootProps.unstable_listView
? [gridListColumnSelector(apiRef.current.state)!]
: gridVisibleColumnDefinitionsSelector(apiRef);
const visibleColumns = gridListViewVisibleColumnSelector(
apiRef,
rootProps.unstable_listView,
) as any;
const hiddenCellsOriginMap = gridRowSpanningHiddenCellsOriginMapSelector(apiRef);
const lastRowId = apiRef.current.state.rows.dataRowIds.at(-1);
const lastColumn = visibleColumns.at(-1);
Expand Down
76 changes: 8 additions & 68 deletions packages/x-data-grid/src/hooks/utils/useGridSelector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,22 @@ import * as React from 'react';
import { fastObjectShallowCompare } from '@mui/x-internals/fastObjectShallowCompare';
import { warnOnce } from '@mui/x-internals/warning';
import type { GridApiCommon } from '../../models/api/gridApiCommon';
import { OutputSelector, OutputSelectorV8 } from '../../utils/createSelector';
import { OutputSelector } from '../../utils/createSelector';
import { useLazyRef } from './useLazyRef';
import { useOnMount } from './useOnMount';
import type { GridCoreApi } from '../../models/api/gridCoreApi';

function isOutputSelector<Api extends GridApiCommon, T>(
function isOutputSelector<Api extends GridApiCommon, Args, T>(
selector: any,
): selector is OutputSelector<Api['state'], T> {
): selector is OutputSelector<Api['state'], Args, T> {
return selector.acceptsApiRef;
}

type Selector<Api extends GridApiCommon, Args, T> =
| ((state: Api['state']) => T)
| OutputSelectorV8<Api['state'], Args, T>;
| OutputSelector<Api['state'], Args, T>;

// TODO v8: Remove this function
function applySelector<Api extends GridApiCommon, T>(
apiRef: React.MutableRefObject<Api>,
selector: ((state: Api['state']) => T) | OutputSelector<Api['state'], T>,
) {
if (isOutputSelector<Api, T>(selector)) {
return selector(apiRef);
}
return selector(apiRef.current.state);
}

// TODO v8: Rename this function to `applySelector`
function applySelectorV8<Api extends GridApiCommon, Args, T>(
function applySelector<Api extends GridApiCommon, Args, T>(
apiRef: React.MutableRefObject<Api>,
selector: Selector<Api, Args, T>,
args: Args,
Expand All @@ -46,55 +34,7 @@ export const objectShallowCompare = fastObjectShallowCompare;

const createRefs = () => ({ state: null, equals: null, selector: null }) as any;

// TODO v8: Remove this function
export const useGridSelector = <Api extends GridApiCommon, T>(
apiRef: React.MutableRefObject<Api>,
selector: ((state: Api['state']) => T) | OutputSelector<Api['state'], T>,
equals: (a: T, b: T) => boolean = defaultCompare,
) => {
if (process.env.NODE_ENV !== 'production') {
if (!apiRef.current.state) {
warnOnce([
'MUI X: `useGridSelector` has been called before the initialization of the state.',
'This hook can only be used inside the context of the grid.',
]);
}
}

const refs = useLazyRef<
{
state: T;
equals: typeof equals;
selector: typeof selector;
},
never
>(createRefs);
const didInit = refs.current.selector !== null;

const [state, setState] = React.useState<T>(
// We don't use an initialization function to avoid allocations
(didInit ? null : applySelector(apiRef, selector)) as T,
);

refs.current.state = state;
refs.current.equals = equals;
refs.current.selector = selector;

useOnMount(() => {
return apiRef.current.store.subscribe(() => {
const newState = applySelector(apiRef, refs.current.selector);
if (!refs.current.equals(refs.current.state, newState)) {
refs.current.state = newState;
setState(newState);
}
});
});

return state;
};

// TODO v8: Rename this function to `useGridSelector`
export const useGridSelectorV8 = <Api extends GridApiCommon, Args, T>(
export const useGridSelector = <Api extends GridApiCommon, Args, T>(
apiRef: React.MutableRefObject<Api>,
selector: Selector<Api, Args, T>,
args: Args = undefined as Args,
Expand All @@ -121,7 +61,7 @@ export const useGridSelectorV8 = <Api extends GridApiCommon, Args, T>(

const [state, setState] = React.useState<T>(
// We don't use an initialization function to avoid allocations
(didInit ? null : applySelectorV8(apiRef, selector, args, apiRef.current.instanceId)) as T,
(didInit ? null : applySelector(apiRef, selector, args, apiRef.current.instanceId)) as T,
);

refs.current.state = state;
Expand All @@ -130,7 +70,7 @@ export const useGridSelectorV8 = <Api extends GridApiCommon, Args, T>(

useOnMount(() => {
return apiRef.current.store.subscribe(() => {
const newState = applySelectorV8(
const newState = applySelector(
apiRef,
refs.current.selector,
args,
Expand Down
8 changes: 1 addition & 7 deletions packages/x-data-grid/src/internals/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,13 +148,7 @@ export type * from '../models/props/DataGridProps';
export type * from '../models/gridDataSource';
export { getColumnsToExport, defaultGetRowsToExport } from '../hooks/features/export/utils';
export * from '../utils/createControllablePromise';
export {
createSelector,
createSelectorV8,
createSelectorMemoized,
createSelectorMemoizedV8,
} from '../utils/createSelector';
export { useGridSelectorV8 } from '../hooks/utils/useGridSelector';
export { createSelector, createSelectorMemoized } from '../utils/createSelector';
export { gridRowGroupsToFetchSelector } from '../hooks/features/rows/gridRowsSelector';
export {
findParentElementFromClassName,
Expand Down
2 changes: 1 addition & 1 deletion packages/x-data-grid/src/models/controlStateItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export interface GridControlStateItem<
stateId: string;
propModel?: GridEventLookup[E]['params'];
stateSelector:
| OutputSelector<State, GridControlledStateEventLookup[E]['params']>
| OutputSelector<State, any, GridControlledStateEventLookup[E]['params']>
| ((state: State) => GridControlledStateEventLookup[E]['params']);
propOnChange?: (
model: GridControlledStateEventLookup[E]['params'],
Expand Down
3 changes: 3 additions & 0 deletions packages/x-data-grid/src/utils/createSelector.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ describe('createSelector', () => {
it('should fallback to the default behavior when no cache key is provided', () => {
const selector = createSelectorMemoized([], () => []) as OutputSelector<
GridStateCommunity,
any,
any
>;
const state = {} as GridStateCommunity;
Expand Down Expand Up @@ -48,6 +49,7 @@ describe('createSelector', () => {
it('should return different selectors for different cache keys', () => {
const selector = createSelectorMemoized([], () => []) as OutputSelector<
GridStateCommunity,
any,
any
>;
const apiRef1 = {
Expand All @@ -62,6 +64,7 @@ describe('createSelector', () => {
it('should not clear the cache of one selector when another key is passed', () => {
const selector = createSelectorMemoized([], () => []) as OutputSelector<
GridStateCommunity,
any,
any
>;
const apiRef1 = {
Expand Down
Loading