Skip to content

Commit

Permalink
Fix
Browse files Browse the repository at this point in the history
  • Loading branch information
flaviendelangle committed Oct 24, 2024
1 parent 4cef1db commit 66fde8c
Show file tree
Hide file tree
Showing 13 changed files with 135 additions and 166 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ const getIconFromFileType = (fileType: FileType) => {

interface CustomTreeItemProps
extends Omit<UseTreeItemParameters, 'rootRef'>,
Omit<React.HTMLAttributes<HTMLLIElement>, 'onFocus' | 'children'> {}
Omit<React.HTMLAttributes<HTMLLIElement>, 'onFocus'> {}

const CustomTreeItem = React.forwardRef(function CustomTreeItem(
props: CustomTreeItemProps,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ const CustomTreeItemContent = styled(TreeItemContent)(({ theme }) => ({

interface CustomTreeItemProps
extends Omit<UseTreeItemParameters, 'rootRef'>,
Omit<React.HTMLAttributes<HTMLLIElement>, 'onFocus' | 'children'> {}
Omit<React.HTMLAttributes<HTMLLIElement>, 'onFocus'> {}

const CustomTreeItem = React.forwardRef(function CustomTreeItem(
props: CustomTreeItemProps,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ const getIconFromFileType = (fileType: FileType) => {

interface CustomTreeItemProps
extends Omit<UseTreeItemParameters, 'rootRef'>,
Omit<React.HTMLAttributes<HTMLLIElement>, 'onFocus' | 'children'> {}
Omit<React.HTMLAttributes<HTMLLIElement>, 'onFocus'> {}

const CustomTreeItem = React.forwardRef(function CustomTreeItem(
props: CustomTreeItemProps,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ const MUI_X_PRODUCTS: TreeViewBaseItem[] = [

interface CustomTreeItemProps
extends Omit<UseTreeItemParameters, 'rootRef'>,
Omit<React.HTMLAttributes<HTMLLIElement>, 'onFocus' | 'children'> {}
Omit<React.HTMLAttributes<HTMLLIElement>, 'onFocus'> {}

const CustomTreeItem = React.forwardRef(function CustomTreeItem(
props: CustomTreeItemProps,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const CustomTreeItemContent = styled(TreeItemContent)(({ theme }) => ({

interface CustomTreeItemProps
extends Omit<UseTreeItemParameters, 'rootRef'>,
Omit<React.HTMLAttributes<HTMLLIElement>, 'onFocus' | 'children'> {}
Omit<React.HTMLAttributes<HTMLLIElement>, 'onFocus'> {}

const CustomTreeItem = React.forwardRef(function CustomTreeItem(
props: CustomTreeItemProps,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ const CustomTreeItemCheckbox = styled(TreeItemCheckbox)(({ theme }) => ({

interface CustomTreeItemProps
extends Omit<UseTreeItemParameters, 'rootRef'>,
Omit<React.HTMLAttributes<HTMLLIElement>, 'onFocus' | 'children'> {}
Omit<React.HTMLAttributes<HTMLLIElement>, 'onFocus'> {}

const CustomTreeItem = React.forwardRef(function CustomTreeItem(
props: CustomTreeItemProps,
Expand Down
84 changes: 2 additions & 82 deletions packages/x-tree-view/src/TreeItem/TreeItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import { TreeItemIcon } from '../TreeItemIcon';
import { TreeItemDragAndDropOverlay } from '../TreeItemDragAndDropOverlay';
import { TreeItemProvider } from '../TreeItemProvider';
import { TreeItemLabelInput } from '../TreeItemLabelInput';
import { TreeViewItemToRenderProps } from '../internals/plugins/useTreeViewItems';

const useThemeProps = createUseThemeProps('MuiTreeItem');

Expand Down Expand Up @@ -226,7 +225,7 @@ type TreeItemComponent = ((
*
* - [TreeItem API](https://mui.com/x/api/tree-view/tree-item-2/)
*/
const TreeItem = React.forwardRef(function TreeItem(
export const TreeItem = React.forwardRef(function TreeItem(
inProps: TreeItemProps,
forwardedRef: React.Ref<HTMLLIElement>,
) {
Expand Down Expand Up @@ -359,7 +358,7 @@ const TreeItem = React.forwardRef(function TreeItem(
</Root>
</TreeItemProvider>
);
});
}) as TreeItemComponent;

TreeItem.propTypes = {
// ----------------------------- Warning --------------------------------
Expand Down Expand Up @@ -417,82 +416,3 @@ TreeItem.propTypes = {
*/
slots: PropTypes.object,
} as any;

const areChildrenEqual = (childA: TreeViewItemToRenderProps, childB: TreeViewItemToRenderProps) => {
if (childA.itemId !== childB.itemId) {
return false;
}
if (childA.id !== childB.id) {
return false;
}
if (childA.label !== childB.label) {
return false;
}
if (childA.children.length !== childB.children.length) {
return false;
}
for (let i = 0; i < childA.children.length; i += 1) {
if (!areChildrenEqual(childA.children[i], childB.children[i])) {
return false;
}
}
return true;
};

// Logic copied from `fastObjectShallowCompare` but with a deep comparison for `props.children`
const is = Object.is;
const propsAreEqual = (a: TreeItemProps, b: TreeItemProps) => {
if (a === b) {
return true;
}
if (!(a instanceof Object) || !(b instanceof Object)) {
return false;
}

let aLength = 0;
let bLength = 0;

/* eslint-disable guard-for-in */
for (const key in a) {
aLength += 1;

if (key === 'children') {
const childrenA = a[key];
const childrenB = b[key];
if (!Array.isArray(childrenA) || !Array.isArray(childrenB)) {
if (!is(a[key], b[key])) {
return false;
}
} else if (childrenA.length !== childrenB.length) {
return false;
} else {
for (let i = 0; i < childrenA.length; i += 1) {
if (React.isValidElement(childrenA[i]) || React.isValidElement(childrenB[i])) {
if (!is(a[key], b[key])) {
return false;
}
} else if (!areChildrenEqual(childrenA[i], childrenB[i])) {
return false;
}
}
}
} else {
if (!is(a[key as keyof TreeItemProps], b[key as keyof TreeItemProps])) {
return false;
}
if (!(key in b)) {
return false;
}
}
}

/* eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-unused-vars */
for (const _ in b) {
bLength += 1;
}
return aLength === bLength;
};

const MemoizedTreeItem = React.memo(TreeItem, propsAreEqual) as TreeItemComponent;

export { MemoizedTreeItem as TreeItem };
2 changes: 1 addition & 1 deletion packages/x-tree-view/src/TreeItem/TreeItem.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export interface TreeItemSlotProps extends TreeItemIconSlotProps {

export interface TreeItemProps
extends Omit<UseTreeItemParameters, 'rootRef'>,
Omit<React.HTMLAttributes<HTMLLIElement>, 'onFocus' | 'children'> {
Omit<React.HTMLAttributes<HTMLLIElement>, 'onFocus'> {
className?: string;
/**
* Override or extend the styles applied to the component.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@ import { TreeViewCancellableEvent } from '../../models';
import { useTreeViewContext } from '../../internals/TreeViewProvider';
import { UseTreeViewSelectionSignature } from '../../internals/plugins/useTreeViewSelection';
import { UseTreeViewExpansionSignature } from '../../internals/plugins/useTreeViewExpansion';
import {
TreeViewItemToRenderProps,
UseTreeViewItemsSignature,
} from '../../internals/plugins/useTreeViewItems';
import { UseTreeViewItemsSignature } from '../../internals/plugins/useTreeViewItems';
import { UseTreeViewFocusSignature } from '../../internals/plugins/useTreeViewFocus';
import {
UseTreeViewLabelSignature,
Expand Down Expand Up @@ -63,9 +60,7 @@ interface UseTreeItemUtilsReturnValue<
publicAPI: TreeViewPublicAPI<TSignatures, TOptionalSignatures>;
}

export const isItemExpandable = (
reactChildren: React.ReactNode | TreeViewItemToRenderProps | TreeViewItemToRenderProps[],
) => {
export const isItemExpandable = (reactChildren: React.ReactNode) => {
if (Array.isArray(reactChildren)) {
return reactChildren.length > 0 && reactChildren.some(isItemExpandable);
}
Expand All @@ -80,7 +75,7 @@ export const useTreeItemUtils = <
children,
}: {
itemId: string;
children?: React.ReactNode | TreeViewItemToRenderProps[];
children?: React.ReactNode;
}): UseTreeItemUtilsReturnValue<TSignatures, TOptionalSignatures> => {
const {
instance,
Expand Down
156 changes: 120 additions & 36 deletions packages/x-tree-view/src/internals/components/RichTreeViewItems.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,54 +4,100 @@ import { SlotComponentProps } from '@mui/utils';
import { TreeItem, TreeItemProps } from '../../TreeItem';
import { TreeViewItemId } from '../../models';
import { TreeViewItemToRenderProps } from '../plugins/useTreeViewItems';
import { RichTreeViewItemsContext } from '../hooks/useRichTreeViewItemsContext';

interface RichTreeViewItemsOwnerState {
itemId: TreeViewItemId;
label: string;
}
const RichTreeViewItemsContext = React.createContext<
((item: TreeViewItemToRenderProps) => React.ReactNode) | null
>(null);

export interface RichTreeViewItemsSlots {
/**
* Custom component to render a Tree Item.
* @default TreeItem.
*/
item?: React.JSXElementConstructor<TreeItemProps>;
if (process.env.NODE_ENV !== 'production') {
RichTreeViewItemsContext.displayName = 'RichTreeViewItemsProvider';
}

export interface RichTreeViewItemsSlotProps {
item?: SlotComponentProps<typeof TreeItem, {}, RichTreeViewItemsOwnerState>;
}
const areChildrenEqual = (childA: TreeViewItemToRenderProps, childB: TreeViewItemToRenderProps) => {
if (childA.itemId !== childB.itemId) {
return false;
}
if (childA.id !== childB.id) {
return false;
}
if (childA.label !== childB.label) {
return false;
}
if (childA.children.length !== childB.children.length) {
return false;
}
for (let i = 0; i < childA.children.length; i += 1) {
if (!areChildrenEqual(childA.children[i], childB.children[i])) {
return false;
}
}
return true;
};

export interface RichTreeViewItemsProps {
itemsToRender: TreeViewItemToRenderProps[];
/**
* Overridable component slots.
* @default {}
*/
slots?: RichTreeViewItemsSlots;
/**
* The props used for each component slot.
* @default {}
*/
slotProps?: RichTreeViewItemsSlotProps;
}
// Logic copied from `fastObjectShallowCompare` but with a deep comparison for `props.children`
const is = Object.is;
const propsAreEqual = (a: WrappedTreeItemProps, b: WrappedTreeItemProps) => {
if (a === b) {
return true;
}
if (!(a instanceof Object) || !(b instanceof Object)) {
return false;
}

interface WrappedTreeItemProps extends Pick<TreeItemProps, 'id' | 'itemId' | 'children'> {
itemSlot: React.JSXElementConstructor<TreeItemProps> | undefined;
itemSlotProps: SlotComponentProps<typeof TreeItem, {}, RichTreeViewItemsOwnerState> | undefined;
label: string;
itemsToRender: TreeViewItemToRenderProps[] | undefined;
}
let aLength = 0;
let bLength = 0;

/* eslint-disable guard-for-in */
for (const key in a) {
aLength += 1;

if (key === 'children') {
const childrenA = a[key];
const childrenB = b[key];
if (!Array.isArray(childrenA) || !Array.isArray(childrenB)) {
if (!is(a[key], b[key])) {
return false;
}
} else if (childrenA.length !== childrenB.length) {
return false;
} else {
for (let i = 0; i < childrenA.length; i += 1) {
if (React.isValidElement(childrenA[i]) || React.isValidElement(childrenB[i])) {
if (!is(a[key], b[key])) {
return false;
}
} else if (!areChildrenEqual(childrenA[i], childrenB[i])) {
return false;
}
}
}
} else {
if (!is(a[key as keyof TreeItemProps], b[key as keyof TreeItemProps])) {
return false;
}
if (!(key in b)) {
return false;
}
}
}

function WrappedTreeItem({
/* eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-unused-vars */
for (const _ in b) {
bLength += 1;
}
return aLength === bLength;
};

const WrappedTreeItem = React.memo(function WrappedTreeItem({
itemSlot,
itemSlotProps,
label,
id,
itemId,
itemsToRender,
}: WrappedTreeItemProps) {
const renderItemForRichTreeView = React.useContext(RichTreeViewItemsContext)!;

const Item = (itemSlot ?? TreeItem) as React.JSXElementConstructor<TreeItemProps>;
const { ownerState, ...itemProps } = useSlotProps({
elementType: Item,
Expand All @@ -60,8 +106,8 @@ function WrappedTreeItem({
ownerState: { itemId, label },
});

return <Item {...itemProps}>{itemsToRender}</Item>;
}
return <Item {...itemProps}>{itemsToRender?.map(renderItemForRichTreeView)}</Item>;
}, propsAreEqual);

export function RichTreeViewItems(props: RichTreeViewItemsProps) {
const { itemsToRender, slots, slotProps } = props;
Expand Down Expand Up @@ -92,3 +138,41 @@ export function RichTreeViewItems(props: RichTreeViewItemsProps) {
</RichTreeViewItemsContext.Provider>
);
}

interface RichTreeViewItemsOwnerState {
itemId: TreeViewItemId;
label: string;
}

export interface RichTreeViewItemsSlots {
/**
* Custom component to render a Tree Item.
* @default TreeItem.
*/
item?: React.JSXElementConstructor<TreeItemProps>;
}

export interface RichTreeViewItemsSlotProps {
item?: SlotComponentProps<typeof TreeItem, {}, RichTreeViewItemsOwnerState>;
}

export interface RichTreeViewItemsProps {
itemsToRender: TreeViewItemToRenderProps[];
/**
* Overridable component slots.
* @default {}
*/
slots?: RichTreeViewItemsSlots;
/**
* The props used for each component slot.
* @default {}
*/
slotProps?: RichTreeViewItemsSlotProps;
}

interface WrappedTreeItemProps extends Pick<TreeItemProps, 'id' | 'itemId' | 'children'> {
itemSlot: React.JSXElementConstructor<TreeItemProps> | undefined;
itemSlotProps: SlotComponentProps<typeof TreeItem, {}, RichTreeViewItemsOwnerState> | undefined;
label: string;
itemsToRender: TreeViewItemToRenderProps[] | undefined;
}
Loading

0 comments on commit 66fde8c

Please sign in to comment.