Skip to content

Commit

Permalink
[Tree View] Add React Compiler linting rules
Browse files Browse the repository at this point in the history
  • Loading branch information
flaviendelangle committed Jan 27, 2025
1 parent e211ed5 commit 47ed330
Show file tree
Hide file tree
Showing 21 changed files with 305 additions and 353 deletions.
2 changes: 1 addition & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const ENABLE_REACT_COMPILER_PLUGIN_DATA_GRID =
const ENABLE_REACT_COMPILER_PLUGIN_DATE_PICKERS =
process.env.ENABLE_REACT_COMPILER_PLUGIN_DATE_PICKERS ?? false;
const ENABLE_REACT_COMPILER_PLUGIN_TREE_VIEW =
process.env.ENABLE_REACT_COMPILER_PLUGIN_TREE_VIEW ?? false;
process.env.ENABLE_REACT_COMPILER_PLUGIN_TREE_VIEW ?? true;

const isAnyReactCompilerPluginEnabled =
ENABLE_REACT_COMPILER_PLUGIN ||
Expand Down
29 changes: 1 addition & 28 deletions packages/x-tree-view/src/internals/models/plugin.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from 'react';
import { EventHandlers } from '@mui/utils/types';
import { TreeViewExperimentalFeatures, TreeViewInstance, TreeViewModel } from './treeView';
import { TreeViewExperimentalFeatures, TreeViewInstance } from './treeView';
import type { MergeSignaturesProperty, OptionalIfEmpty } from './helpers';
import { TreeViewEventLookupElement } from './events';
import type { TreeViewCorePluginSignatures } from '../corePlugins';
Expand All @@ -14,20 +14,11 @@ export interface TreeViewPluginOptions<TSignature extends TreeViewAnyPluginSigna
slots: TSignature['slots'];
slotProps: TSignature['slotProps'];
experimentalFeatures: TreeViewUsedExperimentalFeatures<TSignature>;
models: TreeViewUsedModels<TSignature>;
store: TreeViewUsedStore<TSignature>;
rootRef: React.RefObject<HTMLUListElement | null>;
plugins: TreeViewPlugin<TreeViewAnyPluginSignature>[];
}

type TreeViewModelsInitializer<TSignature extends TreeViewAnyPluginSignature> = {
[TControlled in keyof TSignature['models']]: {
getDefaultValue: (
params: TSignature['defaultizedParams'],
) => Exclude<TSignature['defaultizedParams'][TControlled], undefined>;
};
};

type TreeViewResponse<TSignature extends TreeViewAnyPluginSignature> = {
getRootProps?: <TOther extends EventHandlers = {}>(
otherHandlers: TOther,
Expand All @@ -48,7 +39,6 @@ export type TreeViewPluginSignature<
contextValue?: {};
slots?: { [key in keyof T['slots']]: React.ElementType };
slotProps?: { [key in keyof T['slotProps']]: {} | (() => {}) };
modelNames?: keyof T['defaultizedParams'];
experimentalFeatures?: string;
dependencies?: readonly TreeViewAnyPluginSignature[];
optionalDependencies?: readonly TreeViewAnyPluginSignature[];
Expand All @@ -64,13 +54,6 @@ export type TreeViewPluginSignature<
contextValue: T extends { contextValue: {} } ? T['contextValue'] : {};
slots: T extends { slots: {} } ? T['slots'] : {};
slotProps: T extends { slotProps: {} } ? T['slotProps'] : {};
models: T extends { defaultizedParams: {}; modelNames: keyof T['defaultizedParams'] }
? {
[TControlled in T['modelNames']]-?: TreeViewModel<
Exclude<T['defaultizedParams'][TControlled], undefined>
>;
}
: {};
experimentalFeatures: T extends { experimentalFeatures: string }
? { [key in T['experimentalFeatures']]?: boolean }
: {};
Expand All @@ -92,7 +75,6 @@ export type TreeViewAnyPluginSignature = {
contextValue: any;
slots: any;
slotProps: any;
models: any;
experimentalFeatures: any;
publicAPI: any;
};
Expand Down Expand Up @@ -133,14 +115,6 @@ type TreeViewUsedExperimentalFeatures<TSignature extends TreeViewAnyPluginSignat
TSignature['optionalDependencies']
>;

type RemoveSetValue<Models extends Record<string, TreeViewModel<any>>> = {
[K in keyof Models]: Omit<Models[K], 'setValue'>;
};

export type TreeViewUsedModels<TSignature extends TreeViewAnyPluginSignature> =
TSignature['models'] &
RemoveSetValue<MergeSignaturesProperty<TreeViewRequiredPlugins<TSignature>, 'models'>>;

export type TreeViewUsedEvents<TSignature extends TreeViewAnyPluginSignature> =
TSignature['events'] & MergeSignaturesProperty<TreeViewRequiredPlugins<TSignature>, 'events'>;

Expand All @@ -161,7 +135,6 @@ export type TreeViewPlugin<TSignature extends TreeViewAnyPluginSignature> = {
}) => TSignature['defaultizedParams'];
getInitialState?: (params: TreeViewUsedDefaultizedParams<TSignature>) => TSignature['state'];
getInitialCache?: () => TSignature['cache'];
models?: TreeViewModelsInitializer<TSignature>;
params: Record<keyof TSignature['params'], true>;
itemPlugin?: TreeViewItemPlugin;
/**
Expand Down
6 changes: 0 additions & 6 deletions packages/x-tree-view/src/internals/models/treeView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,6 @@ export interface TreeViewItemMeta {
label?: string;
}

export interface TreeViewModel<TValue> {
name: string;
value: TValue;
setControlledValue: (value: TValue | ((prevValue: TValue) => TValue)) => void;
}

export type TreeViewInstance<
TSignatures extends readonly TreeViewAnyPluginSignature[],
TOptionalSignatures extends readonly TreeViewAnyPluginSignature[] = [],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,43 @@
import { TreeViewItemId } from '../../../models';
import { createSelector, TreeViewRootSelector } from '../../utils/selectors';
import { selectorItemMeta } from '../useTreeViewItems/useTreeViewItems.selectors';
import { UseTreeViewExpansionSignature } from './useTreeViewExpansion.types';

const selectorExpansion: TreeViewRootSelector<UseTreeViewExpansionSignature> = (state) =>
state.expansion;

/**
* Get the expanded items.
* @param {TreeViewState<[UseTreeViewExpansionSignature]>} state The state of the tree view.
* @returns {TreeViewItemId[]} The expanded items.
*/
export const selectorExpandedItems = createSelector(
[selectorExpansion],
(expansionState) => expansionState.expandedItems,
);

/**
* Get the expanded items as a Map.
* @param {TreeViewState<[UseTreeViewExpansionSignature]>} state The state of the tree view.
* @returns {TreeViewExpansionValue} The expanded items as a Map.
*/
export const selectorExpandedItemsMap = createSelector([selectorExpandedItems], (expandedItems) => {
const expandedItemsMap = new Map<TreeViewItemId, true>();
expandedItems.forEach((id) => {
expandedItemsMap.set(id, true);
});

return expandedItemsMap;
});

/**
* Check if an item is expanded.
* @param {TreeViewState<[UseTreeViewExpansionSignature]>} state The state of the tree view.
* @returns {boolean} `true` if the item is expanded, `false` otherwise.
*/
export const selectorIsItemExpanded = createSelector(
[selectorExpansion, (_, itemId: string) => itemId],
(expansionState, itemId) => expansionState.expandedItemsMap.has(itemId),
[selectorExpandedItemsMap, (_, itemId: string) => itemId],
(expandedItemsMap, itemId) => expandedItemsMap.has(itemId),
);

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,28 @@ import useEnhancedEffect from '@mui/utils/useEnhancedEffect';
import { TreeViewPlugin } from '../../models';
import { UseTreeViewExpansionSignature } from './useTreeViewExpansion.types';
import { TreeViewItemId } from '../../../models';
import { selectorIsItemExpandable, selectorIsItemExpanded } from './useTreeViewExpansion.selectors';
import { createExpandedItemsMap, getExpansionTrigger } from './useTreeViewExpansion.utils';
import {
selectorExpandedItems,
selectorIsItemExpandable,
selectorIsItemExpanded,
} from './useTreeViewExpansion.selectors';
import { getExpansionTrigger } from './useTreeViewExpansion.utils';
import {
selectorItemMeta,
selectorItemOrderedChildrenIds,
} from '../useTreeViewItems/useTreeViewItems.selectors';
import { useAssertModelConsistency } from '../../utils/models';

export const useTreeViewExpansion: TreeViewPlugin<UseTreeViewExpansionSignature> = ({
instance,
store,
params,
models,
}) => {
useEnhancedEffect(() => {
store.update((prevState) => ({
...prevState,
expansion: {
...prevState.expansion,
expandedItemsMap: createExpandedItemsMap(models.expandedItems.value),
},
}));
}, [store, models.expandedItems.value]);
useAssertModelConsistency({
state: 'expandedItems',
controlled: params.expandedItems,
defaultValue: params.defaultExpandedItems,
});

useEnhancedEffect(() => {
store.update((prevState) => {
Expand All @@ -48,8 +48,13 @@ export const useTreeViewExpansion: TreeViewPlugin<UseTreeViewExpansionSignature>
}, [store, params.isItemEditable, params.expansionTrigger]);

const setExpandedItems = (event: React.SyntheticEvent, value: TreeViewItemId[]) => {
if (params.expandedItems === undefined) {
store.update((prevState) => ({
...prevState,
expansion: { ...prevState.expansion, expandedItems: value },
}));
}
params.onExpandedItemsChange?.(event, value);
models.expandedItems.setControlledValue(value);
};

const toggleItemExpansion = useEventCallback(
Expand All @@ -66,18 +71,19 @@ export const useTreeViewExpansion: TreeViewPlugin<UseTreeViewExpansionSignature>
return;
}

let newExpanded: string[];
const oldModel = selectorExpandedItems(store.value);
let newModel: string[];
if (isExpanded) {
newExpanded = [itemId].concat(models.expandedItems.value);
newModel = [itemId].concat(oldModel);
} else {
newExpanded = models.expandedItems.value.filter((id) => id !== itemId);
newModel = oldModel.filter((id) => id !== itemId);
}

if (params.onItemExpansionToggle) {
params.onItemExpansionToggle(event, itemId, isExpanded);
}

setExpandedItems(event, newExpanded);
setExpandedItems(event, newModel);
},
);

Expand All @@ -93,8 +99,7 @@ export const useTreeViewExpansion: TreeViewPlugin<UseTreeViewExpansionSignature>
(child) =>
selectorIsItemExpandable(store.value, child) && !selectorIsItemExpanded(store.value, child),
);

const newExpanded = models.expandedItems.value.concat(diff);
const newModel = selectorExpandedItems(store.value).concat(diff);

if (diff.length > 0) {
if (params.onItemExpansionToggle) {
Expand All @@ -103,7 +108,7 @@ export const useTreeViewExpansion: TreeViewPlugin<UseTreeViewExpansionSignature>
});
}

setExpandedItems(event, newExpanded);
setExpandedItems(event, newModel);
}
};

Expand All @@ -119,12 +124,6 @@ export const useTreeViewExpansion: TreeViewPlugin<UseTreeViewExpansionSignature>
};
};

useTreeViewExpansion.models = {
expandedItems: {
getDefaultValue: (params) => params.defaultExpandedItems,
},
};

const DEFAULT_EXPANDED_ITEMS: string[] = [];

useTreeViewExpansion.getDefaultizedParams = ({ params }) => ({
Expand All @@ -134,9 +133,8 @@ useTreeViewExpansion.getDefaultizedParams = ({ params }) => ({

useTreeViewExpansion.getInitialState = (params) => ({
expansion: {
expandedItemsMap: createExpandedItemsMap(
expandedItems:
params.expandedItems === undefined ? params.defaultExpandedItems : params.expandedItems,
),
expansionTrigger: getExpansionTrigger(params),
},
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export type UseTreeViewExpansionDefaultizedParameters = DefaultizedProps<

export interface UseTreeViewExpansionState {
expansion: {
expandedItemsMap: Map<string, true>;
expandedItems: string[];
expansionTrigger: 'content' | 'iconContainer';
};
}
Expand All @@ -84,7 +84,6 @@ export type UseTreeViewExpansionSignature = TreeViewPluginSignature<{
defaultizedParams: UseTreeViewExpansionDefaultizedParameters;
instance: UseTreeViewExpansionInstance;
publicAPI: UseTreeViewExpansionPublicAPI;
modelNames: 'expandedItems';
state: UseTreeViewExpansionState;
dependencies: [UseTreeViewItemsSignature];
optionalDependencies: [UseTreeViewLabelSignature];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,6 @@
import { TreeViewItemId } from '../../../models';
import { TreeViewUsedDefaultizedParams } from '../../models';
import { UseTreeViewExpansionSignature } from './useTreeViewExpansion.types';

export const createExpandedItemsMap = (expandedItems: string[]) => {
const expandedItemsMap = new Map<TreeViewItemId, true>();
expandedItems.forEach((id) => {
expandedItemsMap.set(id, true);
});

return expandedItemsMap;
};

export const getExpansionTrigger = ({
isItemEditable,
expansionTrigger,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import { UseTreeViewFocusSignature } from './useTreeViewFocus.types';
import { createSelector, TreeViewRootSelector } from '../../utils/selectors';
import { selectorSelectionModelArray } from '../useTreeViewSelection/useTreeViewSelection.selectors';
import {
selectorDisabledItemFocusable,
selectorItemMetaLookup,
selectorItemOrderedChildrenIds,
} from '../useTreeViewItems/useTreeViewItems.selectors';
import { isItemDisabled } from '../useTreeViewItems/useTreeViewItems.utils';
import { selectorExpandedItemsMap } from '../useTreeViewExpansion/useTreeViewExpansion.selectors';

const selectorTreeViewFocusState: TreeViewRootSelector<UseTreeViewFocusSignature> = (state) =>
state.focus;
Expand All @@ -12,8 +20,37 @@ const selectorTreeViewFocusState: TreeViewRootSelector<UseTreeViewFocusSignature
* @returns {TreeViewItemId | null} The id of the item that should be sequentially focusable.
*/
export const selectorDefaultFocusableItemId = createSelector(
selectorTreeViewFocusState,
(focus) => focus.defaultFocusableItemId,
[
selectorSelectionModelArray,
selectorExpandedItemsMap,
selectorItemMetaLookup,
selectorDisabledItemFocusable,
(state) => selectorItemOrderedChildrenIds(state, null),
],
(selectedItems, expandedItemsMap, itemMetaLookup, disabledItemsFocusable, orderedRootItemIds) => {
const firstSelectedItem = selectedItems.find((itemId) => {
if (!disabledItemsFocusable && isItemDisabled(itemMetaLookup, itemId)) {
return false;
}

const itemMeta = itemMetaLookup[itemId];
return itemMeta && (itemMeta.parentId == null || expandedItemsMap.has(itemMeta.parentId));
});

if (firstSelectedItem != null) {
return firstSelectedItem;
}

const firstNavigableItem = orderedRootItemIds.find(
(itemId) => !disabledItemsFocusable || !isItemDisabled(itemMetaLookup, itemId),
);

if (firstNavigableItem != null) {
return firstNavigableItem;
}

return null;
},
);

/**
Expand Down
Loading

0 comments on commit 47ed330

Please sign in to comment.