Init dipal x tree

This commit is contained in:
behnam 2024-06-21 19:49:13 +03:00
commit 2c58446690
317 changed files with 15417 additions and 0 deletions

4544
CHANGELOG.md Normal file

File diff suppressed because it is too large Load Diff

72
Collapse/Collapse.d.ts vendored Normal file
View File

@ -0,0 +1,72 @@
import * as React from 'react';
import { SxProps } from '@mui/system';
import { InternalStandardProps as StandardProps, Theme } from '..';
import { TransitionProps } from '../transitions/transition';
import { CollapseClasses } from './collapseClasses';
export interface CollapseProps extends StandardProps<TransitionProps, 'timeout'> {
/**
* The content node to be collapsed.
*/
children?: React.ReactNode;
className?: string;
/**
* Override or extend the styles applied to the component.
*/
classes?: Partial<CollapseClasses>;
/**
* The width (horizontal) or height (vertical) of the container when collapsed.
* @default '0px'
*/
collapsedSize?: string | number;
/**
* The component used for the root node.
* Either a string to use a HTML element or a component.
*/
component?: React.ElementType<TransitionProps>;
/**
* The transition timing function.
* You may specify a single easing or a object containing enter and exit values.
*/
easing?: TransitionProps['easing'];
/**
* If `true`, the component will transition in.
*/
in?: boolean;
/**
* The transition orientation.
* @default 'vertical'
*/
orientation?: 'horizontal' | 'vertical';
/**
* The duration for the transition, in milliseconds.
* You may specify a single timeout for all transitions, or individually with an object.
*
* Set to 'auto' to automatically calculate transition time based on height.
* @default duration.standard
*/
timeout?: TransitionProps['timeout'] | 'auto';
/**
* The system prop that allows defining system overrides as well as additional CSS styles.
*/
sx?: SxProps<Theme>;
}
/**
* The Collapse transition is used by the
* [Vertical Stepper](https://mui.com/material-ui/react-stepper/#vertical-stepper) StepContent component.
* It uses [react-transition-group](https://github.com/reactjs/react-transition-group) internally.
*
* Demos:
*
* - [Card](https://mui.com/material-ui/react-card/)
* - [Lists](https://mui.com/material-ui/react-list/)
* - [Transitions](https://mui.com/material-ui/transitions/)
*
* API:
*
* - [Collapse API](https://mui.com/material-ui/api/collapse/)
* - inherits [Transition API](http://reactcommunity.org/react-transition-group/transition/#Transition-props)
*/
export default function Collapse(props: CollapseProps): JSX.Element;

383
Collapse/Collapse.js Normal file
View File

@ -0,0 +1,383 @@
'use client';
import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose";
import _extends from "@babel/runtime/helpers/esm/extends";
const _excluded = ["addEndListener", "children", "className", "collapsedSize", "component", "easing", "in", "onEnter", "onEntered", "onEntering", "onExit", "onExited", "onExiting", "orientation", "style", "timeout", "TransitionComponent"];
import * as React from 'react';
import clsx from 'clsx';
import PropTypes from 'prop-types';
import { Transition } from 'react-transition-group';
import { elementTypeAcceptingRef } from '@mui/utils';
import { unstable_composeClasses as composeClasses } from '@mui/base/composeClasses';
import styled from '@mui/material/styles/styled';
import useThemeProps from '@mui/material/styles/useThemeProps';
import { duration } from '@mui/material/styles/createTransitions';
import { getTransitionProps } from '@mui/material/transitions/utils';
import useTheme from '@mui/material/styles/useTheme';
import { useForkRef } from '@mui/material/utils';
import { getCollapseUtilityClass } from './collapseClasses';
import { jsx as _jsx } from "react/jsx-runtime";
const useUtilityClasses = ownerState => {
const {
orientation,
classes
} = ownerState;
const slots = {
root: ['root', `${orientation}`],
entered: ['entered'],
hidden: ['hidden'],
wrapper: ['wrapper', `${orientation}`],
wrapperInner: ['wrapperInner', `${orientation}`]
};
return composeClasses(slots, getCollapseUtilityClass, classes);
};
const CollapseRoot = styled('div', {
name: 'MuiCollapse',
slot: 'Root',
overridesResolver: (props, styles) => {
const {
ownerState
} = props;
return [styles.root, styles[ownerState.orientation], ownerState.state === 'entered' && styles.entered, ownerState.state === 'exited' && !ownerState.in && ownerState.collapsedSize === '0px' && styles.hidden];
}
})(({
theme,
ownerState
}) => _extends({
height: 0,
overflow: 'hidden',
transition: theme.transitions.create('height'),
}, ownerState.orientation === 'horizontal' && {
height: 'auto',
width: 0,
transition: theme.transitions.create('width')
}, ownerState.state === 'entered' && _extends({
height: 'auto',
overflow: 'visible'
}, ownerState.orientation === 'horizontal' && {
width: 'auto'
}), ownerState.state === 'exited' && !ownerState.in && ownerState.collapsedSize === '0px' && {
visibility: 'hidden'
}));
const CollapseWrapper = styled('div', {
name: 'MuiCollapse',
slot: 'Wrapper',
overridesResolver: (props, styles) => styles.wrapper
})(({
ownerState
}) => _extends({
// Hack to get children with a negative margin to not falsify the height computation.
display: 'flex',
width: '100%'
}, ownerState.orientation === 'horizontal' && {
width: 'auto',
height: '100%'
}));
const CollapseWrapperInner = styled('div', {
name: 'MuiCollapse',
slot: 'WrapperInner',
overridesResolver: (props, styles) => styles.wrapperInner
})(({
ownerState
}) => _extends({
width: '100%'
}, ownerState.orientation === 'horizontal' && {
width: 'auto',
height: '100%'
}));
/**
* The Collapse transition is used by the
* [Vertical Stepper](/material-ui/react-stepper/#vertical-stepper) StepContent component.
* It uses [react-transition-group](https://github.com/reactjs/react-transition-group) internally.
*/
const Collapse = /*#__PURE__*/React.forwardRef(function Collapse(inProps, ref) {
const props = useThemeProps({
props: inProps,
name: 'MuiCollapse'
});
const {
addEndListener,
children,
className,
collapsedSize: collapsedSizeProp = '0px',
component,
easing,
in: inProp,
onEnter,
onEntered,
onEntering,
onExit,
onExited,
onExiting,
orientation = 'vertical',
style,
timeout = duration.standard,
// eslint-disable-next-line react/prop-types
TransitionComponent = Transition
} = props,
other = _objectWithoutPropertiesLoose(props, _excluded);
const ownerState = _extends({}, props, {
orientation,
collapsedSize: collapsedSizeProp
});
const classes = useUtilityClasses(ownerState);
const theme = useTheme();
const timer = React.useRef();
const wrapperRef = React.useRef(null);
const autoTransitionDuration = React.useRef();
const collapsedSize = typeof collapsedSizeProp === 'number' ? `${collapsedSizeProp}px` : collapsedSizeProp;
const isHorizontal = orientation === 'horizontal';
const size = isHorizontal ? 'width' : 'height';
React.useEffect(() => {
return () => {
clearTimeout(timer.current);
};
}, []);
const nodeRef = React.useRef(null);
const handleRef = useForkRef(ref, nodeRef);
const normalizedTransitionCallback = callback => maybeIsAppearing => {
if (callback) {
const node = nodeRef.current;
// onEnterXxx and onExitXxx callbacks have a different arguments.length value.
if (maybeIsAppearing === undefined) {
callback(node);
} else {
callback(node, maybeIsAppearing);
}
}
};
const getWrapperSize = () => wrapperRef.current ? wrapperRef.current[isHorizontal ? 'clientWidth' : 'clientHeight'] : 0;
const handleEnter = normalizedTransitionCallback((node, isAppearing) => {
if (wrapperRef.current && isHorizontal) {
// Set absolute position to get the size of collapsed content
wrapperRef.current.style.position = 'absolute';
}
node.style[size] = collapsedSize;
if (onEnter) {
onEnter(node, isAppearing);
}
});
const handleEntering = normalizedTransitionCallback((node, isAppearing) => {
const wrapperSize = getWrapperSize();
if (wrapperRef.current && isHorizontal) {
// After the size is read reset the position back to default
wrapperRef.current.style.position = '';
}
const {
duration: transitionDuration,
easing: transitionTimingFunction
} = getTransitionProps({
style,
timeout,
easing
}, {
mode: 'enter'
});
if (timeout === 'auto') {
const duration2 = theme.transitions.getAutoHeightDuration(wrapperSize);
node.style.transitionDuration = `${duration2}ms`;
autoTransitionDuration.current = duration2;
} else {
node.style.transitionDuration = typeof transitionDuration === 'string' ? transitionDuration : `${transitionDuration}ms`;
}
node.style[size] = `${wrapperSize}px`;
node.style.transitionTimingFunction = transitionTimingFunction;
if (onEntering) {
onEntering(node, isAppearing);
}
});
const handleEntered = normalizedTransitionCallback((node, isAppearing) => {
node.style[size] = 'auto';
if (onEntered) {
onEntered(node, isAppearing);
}
});
const handleExit = normalizedTransitionCallback(node => {
node.style[size] = `${getWrapperSize()}px`;
if (onExit) {
onExit(node);
}
});
const handleExited = normalizedTransitionCallback(onExited);
const handleExiting = normalizedTransitionCallback(node => {
const wrapperSize = getWrapperSize();
const {
duration: transitionDuration,
easing: transitionTimingFunction
} = getTransitionProps({
style,
timeout,
easing
}, {
mode: 'exit'
});
if (timeout === 'auto') {
// TODO: rename getAutoHeightDuration to something more generic (width support)
// Actually it just calculates animation duration based on size
const duration2 = theme.transitions.getAutoHeightDuration(wrapperSize);
node.style.transitionDuration = `${duration2}ms`;
autoTransitionDuration.current = duration2;
} else {
node.style.transitionDuration = typeof transitionDuration === 'string' ? transitionDuration : `${transitionDuration}ms`;
}
node.style[size] = collapsedSize;
node.style.transitionTimingFunction = transitionTimingFunction;
if (onExiting) {
onExiting(node);
}
});
const handleAddEndListener = next => {
if (timeout === 'auto') {
timer.current = setTimeout(next, autoTransitionDuration.current || 0);
}
if (addEndListener) {
// Old call signature before `react-transition-group` implemented `nodeRef`
addEndListener(nodeRef.current, next);
}
};
return /*#__PURE__*/_jsx(TransitionComponent, _extends({
in: inProp,
onEnter: handleEnter,
onEntered: handleEntered,
onEntering: handleEntering,
onExit: handleExit,
onExited: handleExited,
onExiting: handleExiting,
addEndListener: handleAddEndListener,
nodeRef: nodeRef,
timeout: timeout === 'auto' ? null : timeout
}, other, {
children: (state, childProps) => /*#__PURE__*/_jsx(CollapseRoot, _extends({
as: component,
className: clsx(classes.root, className, {
'entered': classes.entered,
'exited': !inProp && collapsedSize === '0px' && classes.hidden
}[state]),
style: _extends({
[isHorizontal ? 'minWidth' : 'minHeight']: collapsedSize,
}, style),
ownerState: _extends({}, ownerState, {
state
}),
ref: handleRef
}, childProps, {
children: /*#__PURE__*/_jsx(CollapseWrapper, {
ownerState: _extends({}, ownerState, {
state
}),
className: classes.wrapper,
ref: wrapperRef,
children: /*#__PURE__*/_jsx(CollapseWrapperInner, {
ownerState: _extends({}, ownerState, {
state
}),
className: classes.wrapperInner,
children: children
})
})
}))
}));
});
process.env.NODE_ENV !== "production" ? Collapse.propTypes /* remove-proptypes */ = {
// ----------------------------- Warning --------------------------------
// | These PropTypes are generated from the TypeScript type definitions |
// | To update them edit the d.ts file and run "yarn proptypes" |
// ----------------------------------------------------------------------
/**
* Add a custom transition end trigger. Called with the transitioning DOM
* node and a done callback. Allows for more fine grained transition end
* logic. Note: Timeouts are still used as a fallback if provided.
*/
addEndListener: PropTypes.func,
/**
* The content node to be collapsed.
*/
children: PropTypes.node,
/**
* Override or extend the styles applied to the component.
*/
classes: PropTypes.object,
/**
* @ignore
*/
className: PropTypes.string,
/**
* The width (horizontal) or height (vertical) of the container when collapsed.
* @default '0px'
*/
collapsedSize: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
/**
* The component used for the root node.
* Either a string to use a HTML element or a component.
*/
component: elementTypeAcceptingRef,
/**
* The transition timing function.
* You may specify a single easing or a object containing enter and exit values.
*/
easing: PropTypes.oneOfType([PropTypes.shape({
enter: PropTypes.string,
exit: PropTypes.string
}), PropTypes.string]),
/**
* If `true`, the component will transition in.
*/
in: PropTypes.bool,
/**
* @ignore
*/
onEnter: PropTypes.func,
/**
* @ignore
*/
onEntered: PropTypes.func,
/**
* @ignore
*/
onEntering: PropTypes.func,
/**
* @ignore
*/
onExit: PropTypes.func,
/**
* @ignore
*/
onExited: PropTypes.func,
/**
* @ignore
*/
onExiting: PropTypes.func,
/**
* The transition orientation.
* @default 'vertical'
*/
orientation: PropTypes.oneOf(['horizontal', 'vertical']),
/**
* @ignore
*/
style: PropTypes.object,
/**
* The system prop that allows defining system overrides as well as additional CSS styles.
*/
sx: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), PropTypes.func, PropTypes.object]),
/**
* The duration for the transition, in milliseconds.
* You may specify a single timeout for all transitions, or individually with an object.
*
* Set to 'auto' to automatically calculate transition time based on height.
* @default duration.standard
*/
timeout: PropTypes.oneOfType([PropTypes.oneOf(['auto']), PropTypes.number, PropTypes.shape({
appear: PropTypes.number,
enter: PropTypes.number,
exit: PropTypes.number
})])
} : void 0;
Collapse.muiSupportAuto = true;
export default Collapse;

18
Collapse/collapseClasses.d.ts vendored Normal file
View File

@ -0,0 +1,18 @@
export interface CollapseClasses {
/** Styles applied to the root element. */
root: string;
/** State class applied to the root element if `orientation="horizontal"`. */
horizontal: string;
/** Styles applied to the root element when the transition has entered. */
entered: string;
/** Styles applied to the root element when the transition has exited and `collapsedSize` = 0px. */
hidden: string;
/** Styles applied to the outer wrapper element. */
wrapper: string;
/** Styles applied to the inner wrapper element. */
wrapperInner: string;
}
export type CollapseClassKey = keyof CollapseClasses;
export declare function getCollapseUtilityClass(slot: string): string;
declare const collapseClasses: CollapseClasses;
export default collapseClasses;

View File

@ -0,0 +1,7 @@
import { unstable_generateUtilityClasses as generateUtilityClasses } from '@mui/utils';
import generateUtilityClass from '@mui/material/generateUtilityClass';
export function getCollapseUtilityClass(slot) {
return generateUtilityClass('MuiCollapse', slot);
}
const collapseClasses = generateUtilityClasses('MuiCollapse', ['root', 'horizontal', 'vertical', 'entered', 'hidden', 'wrapper', 'wrapperInner']);
export default collapseClasses;

5
Collapse/index.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
export { default } from './Collapse';
export * from './Collapse';
export { default as collapseClasses } from './collapseClasses';
export * from './collapseClasses';

5
Collapse/index.js Normal file
View File

@ -0,0 +1,5 @@
'use client';
export { default } from './Collapse';
export { default as collapseClasses } from './collapseClasses';
export * from './collapseClasses';

6
Collapse/package.json Normal file
View File

@ -0,0 +1,6 @@
{
"sideEffects": false,
"module": "./index.js",
"main": "../node/Collapse/index.js",
"types": "./index.d.ts"
}

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Material-UI SAS
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

27
README.md Normal file
View File

@ -0,0 +1,27 @@
# MUI X Tree View
This package is the community edition of the tree view components.
It's part of [MUI X](https://mui.com/x/), an open-core extension of MUI Core, with advanced components.
## Installation
Install the package in your project directory with:
```bash
npm install @mui/x-tree-view
```
This component has the following peer dependencies that you will need to install as well.
```json
"peerDependencies": {
"@mui/material": "^5.8.6",
"@mui/system": "^5.8.0",
"react": "^17.0.0 || ^18.0.0",
"react-dom": "^17.0.0 || ^18.0.0"
},
```
## Documentation
Visit [https://mui.com/x/react-tree-view/](https://mui.com/x/react-tree-view/) to view the full documentation.

13
TreeItem/TreeItem.d.ts vendored Normal file
View File

@ -0,0 +1,13 @@
import * as React from 'react';
import { TreeItemProps } from './TreeItem.types';
/**
*
* Demos:
*
* - [Tree View](https://mui.com/x/react-tree-view/)
*
* API:
*
* - [TreeItem API](https://mui.com/x/api/tree-view/tree-item/)
*/
export declare const TreeItem: React.ForwardRefExoticComponent<TreeItemProps & React.RefAttributes<HTMLLIElement>>;

412
TreeItem/TreeItem.js Normal file
View File

@ -0,0 +1,412 @@
import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose";
import _extends from "@babel/runtime/helpers/esm/extends";
const _excluded = ["children", "className", "collapseIcon", "ContentComponent", "ContentProps", "endIcon", "expandIcon", "disabled", "icon", "id", "label", "nodeId", "onClick", "onMouseDown", "TransitionComponent", "TransitionProps"];
import * as React from 'react';
import PropTypes, { instanceOf } from 'prop-types';
import clsx from 'clsx';
import Collapse from './../Collapse';
import { alpha, styled, useThemeProps } from '@mui/material/styles';
import ownerDocument from '@mui/utils/ownerDocument';
import useForkRef from '@mui/utils/useForkRef';
import unsupportedProp from '@mui/utils/unsupportedProp';
import elementTypeAcceptingRef from '@mui/utils/elementTypeAcceptingRef';
import { unstable_composeClasses as composeClasses } from '@mui/base';
import { DescendantProvider, useDescendant } from '../internals/TreeViewProvider/DescendantProvider';
import { TreeItemContent } from './TreeItemContent';
import { treeItemClasses, getTreeItemUtilityClass } from './treeItemClasses';
import { useTreeViewContext } from '../internals/TreeViewProvider/useTreeViewContext';
import { jsx as _jsx } from "react/jsx-runtime";
import { jsxs as _jsxs } from "react/jsx-runtime";
const useUtilityClasses = ownerState => {
const {
classes
} = ownerState;
const slots = {
root: ['root'],
content: ['content'],
expanded: ['expanded'],
selected: ['selected'],
focused: ['focused'],
disabled: ['disabled'],
iconContainer: ['iconContainer'],
label: ['label'],
group: ['group']
};
return composeClasses(slots, getTreeItemUtilityClass, classes);
};
const TreeItemRoot = styled('li', {
name: 'MuiTreeItem',
slot: 'Root',
overridesResolver: (props, styles) => styles.root
})({
listStyle: 'none',
margin: 0,
padding: 0,
outline: 0
});
const StyledTreeItemContent = styled(TreeItemContent, {
name: 'MuiTreeItem',
slot: 'Content',
overridesResolver: (props, styles) => {
return [styles.content, styles.iconContainer && {
[`& .${treeItemClasses.iconContainer}`]: styles.iconContainer
}, styles.label && {
[`& .${treeItemClasses.label}`]: styles.label
}];
}
})(({
theme
}) => ({
padding: '0 8px',
width: '100%',
boxSizing: 'border-box',
// prevent width + padding to overflow
display: 'flex',
alignItems: 'center',
cursor: 'pointer',
WebkitTapHighlightColor: 'transparent',
'&:hover': {
backgroundColor: (theme.vars || theme).palette.action.hover,
// Reset on touch devices, it doesn't add specificity
'@media (hover: none)': {
backgroundColor: 'transparent'
}
},
[`&.${treeItemClasses.disabled}`]: {
opacity: (theme.vars || theme).palette.action.disabledOpacity,
backgroundColor: 'transparent'
},
[`&.${treeItemClasses.focused}`]: {
backgroundColor: (theme.vars || theme).palette.action.focus
},
[`&.${treeItemClasses.selected}`]: {
backgroundColor: theme.vars ? `rgba(${theme.vars.palette.primary.mainChannel} / ${theme.vars.palette.action.selectedOpacity})` : alpha(theme.palette.primary.main, theme.palette.action.selectedOpacity),
'&:hover': {
backgroundColor: theme.vars ? `rgba(${theme.vars.palette.primary.mainChannel} / calc(${theme.vars.palette.action.selectedOpacity} + ${theme.vars.palette.action.hoverOpacity}))` : alpha(theme.palette.primary.main, theme.palette.action.selectedOpacity + theme.palette.action.hoverOpacity),
// Reset on touch devices, it doesn't add specificity
'@media (hover: none)': {
backgroundColor: theme.vars ? `rgba(${theme.vars.palette.primary.mainChannel} / ${theme.vars.palette.action.selectedOpacity})` : alpha(theme.palette.primary.main, theme.palette.action.selectedOpacity)
}
},
[`&.${treeItemClasses.focused}`]: {
backgroundColor: theme.vars ? `rgba(${theme.vars.palette.primary.mainChannel} / calc(${theme.vars.palette.action.selectedOpacity} + ${theme.vars.palette.action.focusOpacity}))` : alpha(theme.palette.primary.main, theme.palette.action.selectedOpacity + theme.palette.action.focusOpacity)
}
},
[`& .${treeItemClasses.iconContainer}`]: {
marginRight: 4,
width: 15,
display: 'flex',
flexShrink: 0,
justifyContent: 'center',
'& svg': {
fontSize: 18
}
},
[`& .${treeItemClasses.label}`]: _extends({
paddingLeft: 4,
width: '100%',
boxSizing: 'border-box',
// prevent width + padding to overflow
// fixes overflow - see https://github.com/mui/material-ui/issues/27372
minWidth: 0,
position: 'relative'
}, theme.typography.body1)
}));
const TreeItemGroup = styled(Collapse, {
name: 'MuiTreeItem',
slot: 'Group',
overridesResolver: (props, styles) => styles.group
})({
margin: 0,
padding: 0,
marginLeft: 17
});
/**
*
* Demos:
*
* - [Tree View](https://mui.com/x/react-tree-view/)
*
* API:
*
* - [TreeItem API](https://mui.com/x/api/tree-view/tree-item/)
*/
export const TreeItem = /*#__PURE__*/React.forwardRef(function TreeItem(inProps, ref) {
const props = useThemeProps({
props: inProps,
name: 'MuiTreeItem'
});
const {
children,
className,
collapseIcon,
ContentComponent = TreeItemContent,
ContentProps,
endIcon,
expandIcon,
disabled: disabledProp,
icon,
id: idProp,
label,
nodeId,
onClick,
onMouseDown,
showexpandicon,
TransitionComponent = Collapse,
TransitionProps
} = props,
other = _objectWithoutPropertiesLoose(props, _excluded);
const {
icons: contextIcons,
multiSelect,
disabledItemsFocusable,
treeId,
instance
} = useTreeViewContext();
let id;
if (idProp != null) {
id = idProp;
} else if (treeId && nodeId) {
id = `${treeId}-${nodeId}`;
}
const [treeItemElement, setTreeItemElement] = React.useState(null);
const contentRef = React.useRef(null);
const handleRef = useForkRef(setTreeItemElement, ref);
const descendant = React.useMemo(() => ({
element: treeItemElement,
id: nodeId
}), [nodeId, treeItemElement]);
const {
index,
parentId
} = useDescendant(descendant);
const expandable = Boolean(Array.isArray(children) ? children.length : children);
const expanded = instance ? instance.isNodeExpanded(nodeId) : false;
const focused = instance ? instance.isNodeFocused(nodeId) : false;
const selected = instance ? instance.isNodeSelected(nodeId) : false;
const disabled = instance ? instance.isNodeDisabled(nodeId) : false;
const ownerState = _extends({}, props, {
expanded,
focused,
selected,
disabled
});
const classes = useUtilityClasses(ownerState);
let displayIcon;
let expansionIcon;
if (expandable || showexpandicon) {
if (!expanded) {
expansionIcon = expandIcon || contextIcons.defaultExpandIcon;
} else {
expansionIcon = collapseIcon || contextIcons.defaultCollapseIcon;
}
}
if (expandable) {
displayIcon = contextIcons.defaultParentIcon;
} else {
displayIcon = endIcon || contextIcons.defaultEndIcon;
}
React.useEffect(() => {
// On the first render a node's index will be -1. We want to wait for the real index.
if (instance && index !== -1) {
instance.updateNode({
id: nodeId,
idAttribute: id,
index,
parentId,
expandable,
disabled: disabledProp
});
return () => instance.removeNode(nodeId);
}
return undefined;
}, [instance, parentId, index, nodeId, expandable, disabledProp, id]);
React.useEffect(() => {
if (instance && label) {
var _contentRef$current$t, _contentRef$current;
return instance.mapFirstChar(nodeId, ((_contentRef$current$t = (_contentRef$current = contentRef.current) == null ? void 0 : _contentRef$current.textContent) != null ? _contentRef$current$t : '').substring(0, 1).toLowerCase());
}
return undefined;
}, [instance, nodeId, label]);
let ariaSelected;
if (multiSelect) {
ariaSelected = selected;
} else if (selected) {
/* single-selection trees unset aria-selected on un-selected items.
*
* If the tree does not support multiple selection, aria-selected
* is set to true for the selected node and it is not present on any other node in the tree.
* Source: https://www.w3.org/WAI/ARIA/apg/patterns/treeview/
*/
ariaSelected = true;
}
function handleFocus(event) {
// DOM focus stays on the tree which manages focus with aria-activedescendant
// if (event.target === event.currentTarget) {
// let rootElement;
// if (typeof event.target.getRootNode === 'function') {
// rootElement = event.target.getRootNode();
// } else {
// rootElement = ownerDocument(event.target);
// }
// rootElement.getElementById(treeId).focus({
// preventScroll: true
// });
// }
const unfocusable = !disabledItemsFocusable && disabled;
if (instance && !focused && event.currentTarget === event.target && !unfocusable) {
instance.focusNode(event, nodeId);
}
}
const labelHeight = 50
const [finalHeight, setHeight] = React.useState(labelHeight)
React.useEffect(() => {
if (!contentRef || !contentRef.current) return;
if (!expandable && !showexpandicon ) return;
const ul = contentRef?.current?.closest('li')?.querySelector('ul')
const divContent = contentRef.current
if (expanded) {
setHeight((ul ?? divContent).scrollHeight + labelHeight)
return
};
setHeight(labelHeight)
}, [expanded, children instanceof Array, (children instanceof Array ? children.length : 0)])
return /*#__PURE__*/_jsxs(TreeItemRoot, _extends({
className: clsx(classes.root, className),
role: "treeitem",
"aria-expanded": expandable ? expanded : undefined,
"aria-selected": ariaSelected,
"aria-disabled": disabled || undefined,
id: id,
tabIndex: -1
}, other, {
ownerState: ownerState,
onFocus: handleFocus,
ref: handleRef,
style: {
height: finalHeight + 'px',
transition: 'height 200ms cubic-bezier(0.4, 0, 0.2, 1) 0ms',
},
children: [/*#__PURE__*/_jsx(StyledTreeItemContent, _extends({
as: ContentComponent,
ref: contentRef,
classes: {
root: classes.content,
expanded: classes.expanded,
selected: classes.selected,
focused: classes.focused,
disabled: classes.disabled,
iconContainer: classes.iconContainer,
label: classes.label
},
label: label,
nodeId: nodeId,
onClick: onClick,
onMouseDown: onMouseDown,
icon: icon,
expansionIcon: expansionIcon,
displayIcon: displayIcon,
ownerState: ownerState,
}, ContentProps)), children && /*#__PURE__*/_jsx(DescendantProvider, {
id: nodeId,
children: /*#__PURE__*/_jsx(TreeItemGroup, _extends({
as: TransitionComponent,
unmountOnExit: true,
className: classes.group,
in: expanded,
component: "ul",
role: "group"
}, TransitionProps, {
children: children
}))
})]
}));
});
process.env.NODE_ENV !== "production" ? TreeItem.propTypes = {
// ----------------------------- Warning --------------------------------
// | These PropTypes are generated from the TypeScript type definitions |
// | To update them edit the TypeScript types and run "yarn proptypes" |
// ----------------------------------------------------------------------
/**
* The content of the component.
*/
children: PropTypes.node,
/**
* Override or extend the styles applied to the component.
*/
classes: PropTypes.object,
/**
* className applied to the root element.
*/
className: PropTypes.string,
/**
* The icon used to collapse the node.
*/
collapseIcon: PropTypes.node,
/**
* The component used for the content node.
* @default TreeItemContent
*/
ContentComponent: elementTypeAcceptingRef,
/**
* Props applied to ContentComponent.
*/
ContentProps: PropTypes.object,
/**
* If `true`, the node is disabled.
* @default false
*/
disabled: PropTypes.bool,
/**
* The icon displayed next to an end node.
*/
endIcon: PropTypes.node,
/**
* The icon used to expand the node.
*/
expandIcon: PropTypes.node,
/**
* The icon to display next to the tree node's label.
*/
icon: PropTypes.node,
/**
* The tree node label.
*/
label: PropTypes.node,
/**
* The id of the node.
*/
nodeId: PropTypes.string.isRequired,
/**
* This prop isn't supported.
* Use the `onNodeFocus` callback on the tree if you need to monitor a node's focus.
*/
onFocus: unsupportedProp,
/**
* The system prop that allows defining system overrides as well as additional CSS styles.
*/
sx: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), PropTypes.func, PropTypes.object]),
/**
* The component used for the transition.
* [Follow this guide](/material-ui/transitions/#transitioncomponent-prop) to learn more about the requirements for this component.
* @default Collapse
*/
TransitionComponent: PropTypes.elementType,
/**
* Props applied to the transition element.
* By default, the element is based on this [`Transition`](http://reactcommunity.org/react-transition-group/transition/) component.
*/
TransitionProps: PropTypes.object
} : void 0;

86
TreeItem/TreeItem.types.d.ts vendored Normal file
View File

@ -0,0 +1,86 @@
import * as React from 'react';
import { Theme } from '@mui/material/styles';
import { TransitionProps } from '@mui/material/transitions';
import { SxProps } from '@mui/system';
import { TreeItemContentProps } from './TreeItemContent';
import { TreeItemClasses } from './treeItemClasses';
export interface TreeItemProps extends Omit<React.HTMLAttributes<HTMLLIElement>, 'onFocus'> {
/**
* The content of the component.
*/
children?: React.ReactNode;
/**
* className applied to the root element.
*/
className?: string;
/**
* Override or extend the styles applied to the component.
*/
classes?: Partial<TreeItemClasses>;
/**
* The icon used to collapse the node.
*/
collapseIcon?: React.ReactNode;
/**
* The component used for the content node.
* @default TreeItemContent
*/
ContentComponent?: React.JSXElementConstructor<TreeItemContentProps>;
/**
* Props applied to ContentComponent.
*/
ContentProps?: React.HTMLAttributes<HTMLElement>;
/**
* If `true`, the node is disabled.
* @default false
*/
disabled?: boolean;
/**
* The icon displayed next to an end node.
*/
endIcon?: React.ReactNode;
/**
* The icon used to expand the node.
*/
expandIcon?: React.ReactNode;
/**
* The icon to display next to the tree node's label.
*/
icon?: React.ReactNode;
/**
* This prop isn't supported.
* Use the `onNodeFocus` callback on the tree if you need to monitor a node's focus.
*/
onFocus?: null;
/**
* The tree node label.
*/
label?: React.ReactNode;
/**
* The id of the node.
*/
nodeId: string;
/**
* The component used for the transition.
* [Follow this guide](/material-ui/transitions/#transitioncomponent-prop) to learn more about the requirements for this component.
* @default Collapse
*/
TransitionComponent?: React.JSXElementConstructor<TransitionProps>;
/**
* Props applied to the transition element.
* By default, the element is based on this [`Transition`](http://reactcommunity.org/react-transition-group/transition/) component.
*/
TransitionProps?: TransitionProps;
/**
* The system prop that allows defining system overrides as well as additional CSS styles.
*/
sx?: SxProps<Theme>;
showexpandicon?: 1 | 0
}
export interface TreeItemOwnerState extends TreeItemProps {
expanded: boolean;
focused: boolean;
selected: boolean;
disabled: boolean;
}

View File

@ -0,0 +1 @@
export {};

52
TreeItem/TreeItemContent.d.ts vendored Normal file
View File

@ -0,0 +1,52 @@
import * as React from 'react';
export interface TreeItemContentProps extends React.HTMLAttributes<HTMLElement> {
/**
* className applied to the root element.
*/
className?: string;
/**
* Override or extend the styles applied to the component.
*/
classes: {
/** Styles applied to the root element. */
root: string;
/** State class applied to the content element when expanded. */
expanded: string;
/** State class applied to the content element when selected. */
selected: string;
/** State class applied to the content element when focused. */
focused: string;
/** State class applied to the element when disabled. */
disabled: string;
/** Styles applied to the tree node icon and collapse/expand icon. */
iconContainer: string;
/** Styles applied to the label element. */
label: string;
};
/**
* The tree node label.
*/
label?: React.ReactNode;
/**
* The id of the node.
*/
nodeId: string;
/**
* The icon to display next to the tree node's label.
*/
icon?: React.ReactNode;
/**
* The icon to display next to the tree node's label. Either an expansion or collapse icon.
*/
expansionIcon?: React.ReactNode;
/**
* The icon to display next to the tree node's label. Either a parent or end icon.
*/
displayIcon?: React.ReactNode;
}
export type TreeItemContentClassKey = keyof NonNullable<TreeItemContentProps['classes']>;
/**
* @ignore - internal component.
*/
declare const TreeItemContent: React.ForwardRefExoticComponent<TreeItemContentProps & React.RefAttributes<HTMLDivElement>>;
export { TreeItemContent };

101
TreeItem/TreeItemContent.js Normal file
View File

@ -0,0 +1,101 @@
import _extends from "@babel/runtime/helpers/esm/extends";
import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose";
const _excluded = ["classes", "className", "displayIcon", "expansionIcon", "icon", "label", "nodeId", "onClick", "onMouseDown"];
import * as React from 'react';
import PropTypes from 'prop-types';
import clsx from 'clsx';
import { useTreeItem } from './useTreeItem';
import { jsx as _jsx } from "react/jsx-runtime";
import { jsxs as _jsxs } from "react/jsx-runtime";
/**
* @ignore - internal component.
*/
const TreeItemContent = /*#__PURE__*/React.forwardRef(function TreeItemContent(props, ref) {
const {
classes,
className,
displayIcon,
expansionIcon,
icon: iconProp,
label,
nodeId,
onClick,
onMouseDown
} = props,
other = _objectWithoutPropertiesLoose(props, _excluded);
const {
disabled,
expanded,
selected,
focused,
handleExpansion,
handleSelection,
preventSelection
} = useTreeItem(nodeId);
const icon = iconProp || expansionIcon || displayIcon;
const handleMouseDown = event => {
preventSelection(event);
if (onMouseDown) {
onMouseDown(event);
}
};
const handleClick = event => {
handleExpansion(event);
handleSelection(event);
if (onClick) {
onClick(event);
}
};
return (
/*#__PURE__*/
/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions -- Key event is handled by the TreeView */
_jsxs("div", _extends({}, other, {
className: clsx(className, classes.root, expanded && classes.expanded, selected && classes.selected, focused && classes.focused, disabled && classes.disabled),
onClick: handleClick,
onMouseDown: handleMouseDown,
ref: ref,
children: [/*#__PURE__*/_jsx("div", {
className: classes.iconContainer,
children: icon
}), /*#__PURE__*/_jsx("div", {
className: classes.label,
children: label
})]
}))
);
});
process.env.NODE_ENV !== "production" ? TreeItemContent.propTypes = {
// ----------------------------- Warning --------------------------------
// | These PropTypes are generated from the TypeScript type definitions |
// | To update them edit the TypeScript types and run "yarn proptypes" |
// ----------------------------------------------------------------------
/**
* Override or extend the styles applied to the component.
*/
classes: PropTypes.object.isRequired,
/**
* className applied to the root element.
*/
className: PropTypes.string,
/**
* The icon to display next to the tree node's label. Either a parent or end icon.
*/
displayIcon: PropTypes.node,
/**
* The icon to display next to the tree node's label. Either an expansion or collapse icon.
*/
expansionIcon: PropTypes.node,
/**
* The icon to display next to the tree node's label.
*/
icon: PropTypes.node,
/**
* The tree node label.
*/
label: PropTypes.node,
/**
* The id of the node.
*/
nodeId: PropTypes.string.isRequired
} : void 0;
export { TreeItemContent };

6
TreeItem/index.d.ts vendored Normal file
View File

@ -0,0 +1,6 @@
export * from './TreeItem';
export type { TreeItemProps } from './TreeItem.types';
export * from './useTreeItem';
export * from './treeItemClasses';
export { TreeItemContent } from './TreeItemContent';
export type { TreeItemContentProps, TreeItemContentClassKey } from './TreeItemContent';

4
TreeItem/index.js Normal file
View File

@ -0,0 +1,4 @@
export * from './TreeItem';
export * from './useTreeItem';
export * from './treeItemClasses';
export { TreeItemContent } from './TreeItemContent';

6
TreeItem/package.json Normal file
View File

@ -0,0 +1,6 @@
{
"sideEffects": false,
"module": "./index.js",
"main": "../node/TreeItem/index.js",
"types": "./index.d.ts"
}

23
TreeItem/treeItemClasses.d.ts vendored Normal file
View File

@ -0,0 +1,23 @@
export interface TreeItemClasses {
/** Styles applied to the root element. */
root: string;
/** Styles applied to the transition component. */
group: string;
/** Styles applied to the content element. */
content: string;
/** State class applied to the content element when expanded. */
expanded: string;
/** State class applied to the content element when selected. */
selected: string;
/** State class applied to the content element when focused. */
focused: string;
/** State class applied to the element when disabled. */
disabled: string;
/** Styles applied to the tree node icon. */
iconContainer: string;
/** Styles applied to the label element. */
label: string;
}
export type TreeItemClassKey = keyof TreeItemClasses;
export declare function getTreeItemUtilityClass(slot: string): string;
export declare const treeItemClasses: TreeItemClasses;

View File

@ -0,0 +1,6 @@
import generateUtilityClass from '@mui/utils/generateUtilityClass';
import generateUtilityClasses from '@mui/utils/generateUtilityClasses';
export function getTreeItemUtilityClass(slot) {
return generateUtilityClass('MuiTreeItem', slot);
}
export const treeItemClasses = generateUtilityClasses('MuiTreeItem', ['root', 'group', 'content', 'expanded', 'selected', 'focused', 'disabled', 'iconContainer', 'label']);

10
TreeItem/useTreeItem.d.ts vendored Normal file
View File

@ -0,0 +1,10 @@
import * as React from 'react';
export declare function useTreeItem(nodeId: string): {
disabled: boolean;
expanded: boolean;
selected: boolean;
focused: boolean;
handleExpansion: (event: React.MouseEvent<HTMLDivElement>) => void;
handleSelection: (event: React.MouseEvent<HTMLDivElement>) => void;
preventSelection: (event: React.MouseEvent<HTMLDivElement>) => void;
};

59
TreeItem/useTreeItem.js Normal file
View File

@ -0,0 +1,59 @@
import { useTreeViewContext } from '../internals/TreeViewProvider/useTreeViewContext';
export function useTreeItem(nodeId) {
const {
instance,
multiSelect
} = useTreeViewContext();
const expandable = instance ? instance.isNodeExpandable(nodeId) : false;
const expanded = instance ? instance.isNodeExpanded(nodeId) : false;
const focused = instance ? instance.isNodeFocused(nodeId) : false;
const selected = instance ? instance.isNodeSelected(nodeId) : false;
const disabled = instance ? instance.isNodeDisabled(nodeId) : false;
const handleExpansion = event => {
if (instance && !disabled) {
if (!focused) {
instance.focusNode(event, nodeId);
}
const multiple = multiSelect && (event.shiftKey || event.ctrlKey || event.metaKey);
// If already expanded and trying to toggle selection don't close
if (expandable && !(multiple && instance.isNodeExpanded(nodeId))) {
instance.toggleNodeExpansion(event, nodeId);
}
}
};
const handleSelection = event => {
if (instance && !disabled) {
if (!focused) {
instance.focusNode(event, nodeId);
}
const multiple = multiSelect && (event.shiftKey || event.ctrlKey || event.metaKey);
if (multiple) {
if (event.shiftKey) {
instance.selectRange(event, {
end: nodeId
});
} else {
instance.selectNode(event, nodeId, true);
}
} else {
instance.selectNode(event, nodeId);
}
}
};
const preventSelection = event => {
if (event.shiftKey || event.ctrlKey || event.metaKey || disabled) {
// Prevent text selection
event.preventDefault();
}
};
return {
disabled,
expanded,
selected,
focused,
handleExpansion,
handleSelection,
preventSelection
};
}

17
TreeView/TreeView.d.ts vendored Normal file
View File

@ -0,0 +1,17 @@
import * as React from 'react';
import { TreeViewProps } from './TreeView.types';
type TreeViewComponent = (<Multiple extends boolean | undefined = undefined>(props: TreeViewProps<Multiple> & React.RefAttributes<HTMLUListElement>) => React.JSX.Element) & {
propTypes?: any;
};
/**
*
* Demos:
*
* - [Tree View](https://mui.com/x/react-tree-view/)
*
* API:
*
* - [TreeView API](https://mui.com/x/api/tree-view/tree-view/)
*/
declare const TreeView: TreeViewComponent;
export { TreeView };

211
TreeView/TreeView.js Normal file
View File

@ -0,0 +1,211 @@
import _extends from "@babel/runtime/helpers/esm/extends";
import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose";
const _excluded = ["disabledItemsFocusable", "expanded", "defaultExpanded", "onNodeToggle", "onNodeFocus", "disableSelection", "defaultSelected", "selected", "multiSelect", "onNodeSelect", "id", "defaultCollapseIcon", "defaultEndIcon", "defaultExpandIcon", "defaultParentIcon", "children"];
import * as React from 'react';
import PropTypes from 'prop-types';
import { styled, useThemeProps } from '@mui/material/styles';
import composeClasses from '@mui/utils/composeClasses';
import { useSlotProps } from '@mui/base/utils';
import { getTreeViewUtilityClass } from './treeViewClasses';
import { useTreeView } from '../internals/useTreeView';
import { TreeViewProvider } from '../internals/TreeViewProvider';
import { DEFAULT_TREE_VIEW_PLUGINS } from '../internals/plugins';
import { jsx as _jsx } from "react/jsx-runtime";
const useUtilityClasses = ownerState => {
const {
classes
} = ownerState;
const slots = {
root: ['root']
};
return composeClasses(slots, getTreeViewUtilityClass, classes);
};
const TreeViewRoot = styled('ul', {
name: 'MuiTreeView',
slot: 'Root',
overridesResolver: (props, styles) => styles.root
})({
padding: 0,
margin: 0,
listStyle: 'none',
outline: 0
});
/**
*
* Demos:
*
* - [Tree View](https://mui.com/x/react-tree-view/)
*
* API:
*
* - [TreeView API](https://mui.com/x/api/tree-view/tree-view/)
*/
const TreeView = /*#__PURE__*/React.forwardRef(function TreeView(inProps, ref) {
const themeProps = useThemeProps({
props: inProps,
name: 'MuiTreeView'
});
const ownerState = themeProps;
const _ref = themeProps,
{
// Headless implementation
disabledItemsFocusable,
expanded,
defaultExpanded,
onNodeToggle,
onNodeFocus,
disableSelection,
defaultSelected,
selected,
multiSelect,
onNodeSelect,
id,
defaultCollapseIcon,
defaultEndIcon,
defaultExpandIcon,
defaultParentIcon,
// Component implementation
children
} = _ref,
other = _objectWithoutPropertiesLoose(_ref, _excluded);
const {
getRootProps,
contextValue
} = useTreeView({
disabledItemsFocusable,
expanded,
defaultExpanded,
onNodeToggle,
onNodeFocus,
disableSelection,
defaultSelected,
selected,
multiSelect,
onNodeSelect,
id,
defaultCollapseIcon,
defaultEndIcon,
defaultExpandIcon,
defaultParentIcon,
plugins: DEFAULT_TREE_VIEW_PLUGINS,
rootRef: ref
});
const classes = useUtilityClasses(themeProps);
const rootProps = useSlotProps({
elementType: TreeViewRoot,
externalSlotProps: {},
externalForwardedProps: other,
className: classes.root,
getSlotProps: getRootProps,
ownerState
});
return /*#__PURE__*/_jsx(TreeViewProvider, {
value: contextValue,
children: /*#__PURE__*/_jsx(TreeViewRoot, _extends({}, rootProps, {
children: children
}))
});
});
process.env.NODE_ENV !== "production" ? TreeView.propTypes = {
// ----------------------------- Warning --------------------------------
// | These PropTypes are generated from the TypeScript type definitions |
// | To update them edit the TypeScript types and run "yarn proptypes" |
// ----------------------------------------------------------------------
/**
* The content of the component.
*/
children: PropTypes.node,
/**
* Override or extend the styles applied to the component.
*/
classes: PropTypes.object,
/**
* className applied to the root element.
*/
className: PropTypes.string,
/**
* The default icon used to collapse the node.
*/
defaultCollapseIcon: PropTypes.node,
/**
* The default icon displayed next to a end node. This is applied to all
* tree nodes and can be overridden by the TreeItem `icon` prop.
*/
defaultEndIcon: PropTypes.node,
/**
* Expanded node ids.
* Used when the item's expansion is not controlled.
* @default []
*/
defaultExpanded: PropTypes.arrayOf(PropTypes.string),
/**
* The default icon used to expand the node.
*/
defaultExpandIcon: PropTypes.node,
/**
* The default icon displayed next to a parent node. This is applied to all
* parent nodes and can be overridden by the TreeItem `icon` prop.
*/
defaultParentIcon: PropTypes.node,
/**
* Selected node ids. (Uncontrolled)
* When `multiSelect` is true this takes an array of strings; when false (default) a string.
* @default []
*/
defaultSelected: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([PropTypes.arrayOf(PropTypes.string), PropTypes.string]),
/**
* If `true`, will allow focus on disabled items.
* @default false
*/
disabledItemsFocusable: PropTypes.bool,
/**
* If `true` selection is disabled.
* @default false
*/
disableSelection: PropTypes.bool,
/**
* Expanded node ids.
* Used when the item's expansion is controlled.
*/
expanded: PropTypes.arrayOf(PropTypes.string),
/**
* This prop is used to help implement the accessibility logic.
* If you don't provide this prop. It falls back to a randomly generated id.
*/
id: PropTypes.string,
/**
* If true `ctrl` and `shift` will trigger multiselect.
* @default false
*/
multiSelect: PropTypes.bool,
/**
* Callback fired when tree items are focused.
* @param {React.SyntheticEvent} event The event source of the callback **Warning**: This is a generic event not a focus event.
* @param {string} nodeId The id of the node focused.
* @param {string} value of the focused node.
*/
onNodeFocus: PropTypes.func,
/**
* Callback fired when tree items are selected/unselected.
* @param {React.SyntheticEvent} event The event source of the callback
* @param {string[] | string} nodeIds Ids of the selected nodes. When `multiSelect` is true
* this is an array of strings; when false (default) a string.
*/
onNodeSelect: PropTypes.func,
/**
* Callback fired when tree items are expanded/collapsed.
* @param {React.SyntheticEvent} event The event source of the callback.
* @param {array} nodeIds The ids of the expanded nodes.
*/
onNodeToggle: PropTypes.func,
/**
* Selected node ids. (Controlled)
* When `multiSelect` is true this takes an array of strings; when false (default) a string.
*/
selected: PropTypes.any,
/**
* The system prop that allows defining system overrides as well as additional CSS styles.
*/
sx: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), PropTypes.func, PropTypes.object])
} : void 0;
export { TreeView };

27
TreeView/TreeView.types.d.ts vendored Normal file
View File

@ -0,0 +1,27 @@
import * as React from 'react';
import { Theme } from '@mui/material/styles';
import { SxProps } from '@mui/system';
import { TreeViewClasses } from './treeViewClasses';
import { DefaultTreeViewPluginParameters } from '../internals/plugins/defaultPlugins';
export interface TreeViewPropsBase extends React.HTMLAttributes<HTMLUListElement> {
/**
* The content of the component.
*/
children?: React.ReactNode;
/**
* className applied to the root element.
*/
className?: string;
/**
* Override or extend the styles applied to the component.
*/
classes?: Partial<TreeViewClasses>;
/**
* The system prop that allows defining system overrides as well as additional CSS styles.
*/
sx?: SxProps<Theme>;
}
export interface TreeViewProps<Multiple extends boolean | undefined> extends DefaultTreeViewPluginParameters<Multiple>, TreeViewPropsBase {
}
export type SingleSelectTreeViewProps = TreeViewProps<false>;
export type MultiSelectTreeViewProps = TreeViewProps<true>;

View File

@ -0,0 +1 @@
export {};

3
TreeView/index.d.ts vendored Normal file
View File

@ -0,0 +1,3 @@
export * from './TreeView';
export * from './treeViewClasses';
export type { TreeViewProps, SingleSelectTreeViewProps, MultiSelectTreeViewProps, TreeViewPropsBase, } from './TreeView.types';

3
TreeView/index.js Normal file
View File

@ -0,0 +1,3 @@
export * from './TreeView';
export * from './treeViewClasses';
export {};

6
TreeView/package.json Normal file
View File

@ -0,0 +1,6 @@
{
"sideEffects": false,
"module": "./index.js",
"main": "../node/TreeView/index.js",
"types": "./index.d.ts"
}

7
TreeView/treeViewClasses.d.ts vendored Normal file
View File

@ -0,0 +1,7 @@
export interface TreeViewClasses {
/** Styles applied to the root element. */
root: string;
}
export type TreeViewClassKey = keyof TreeViewClasses;
export declare function getTreeViewUtilityClass(slot: string): string;
export declare const treeViewClasses: TreeViewClasses;

View File

@ -0,0 +1,6 @@
import generateUtilityClass from '@mui/utils/generateUtilityClass';
import generateUtilityClasses from '@mui/utils/generateUtilityClasses';
export function getTreeViewUtilityClass(slot) {
return generateUtilityClass('MuiTreeView', slot);
}
export const treeViewClasses = generateUtilityClasses('MuiTreeView', ['root']);

3
index.d.ts vendored Normal file
View File

@ -0,0 +1,3 @@
export * from './TreeItem';
export * from './TreeView';
export { unstable_resetCleanupTracking } from './internals/hooks/useInstanceEventHandler';

9
index.js Normal file
View File

@ -0,0 +1,9 @@
/**
* @mui/x-tree-view v6.17.0
*
* @license MIT
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
export * from "./TreeItem";
export * from "./TreeView";

View File

@ -0,0 +1,38 @@
import * as React from 'react';
/**
* This hook registers our descendant by passing it into an array. We can then
* search that array by to find its index when registering it in the component.
* We use this for focus management, keyboard navigation, and typeahead
* functionality for some components.
*
* The hook accepts the element node
*
* Our main goals with this are:
* 1) maximum composability,
* 2) minimal API friction
* 3) SSR compatibility*
* 4) concurrent safe
* 5) index always up-to-date with the tree despite changes
* 6) works with memoization of any component in the tree (hopefully)
*
* * As for SSR, the good news is that we don't actually need the index on the
* server for most use-cases, as we are only using it to determine the order of
* composed descendants for keyboard navigation.
*/
export declare function useDescendant(descendant: TreeItemDescendant): {
parentId: string | null;
index: number;
};
interface DescendantProviderProps {
id?: string;
children: React.ReactNode;
}
export declare function DescendantProvider(props: DescendantProviderProps): React.JSX.Element;
export declare namespace DescendantProvider {
var propTypes: any;
}
export interface TreeItemDescendant {
element: HTMLLIElement;
id: string;
}
export {};

View File

@ -0,0 +1,189 @@
import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose";
import _extends from "@babel/runtime/helpers/esm/extends";
const _excluded = ["element"];
import * as React from 'react';
import PropTypes from 'prop-types';
import useEnhancedEffect from '@mui/utils/useEnhancedEffect';
/** Credit: https://github.com/reach/reach-ui/blob/86a046f54d53b6420e392b3fa56dd991d9d4e458/packages/descendants/README.md
* Modified slightly to suit our purposes.
*/
// To replace with .findIndex() once we stop IE11 support.
import { jsx as _jsx } from "react/jsx-runtime";
function findIndex(array, comp) {
for (let i = 0; i < array.length; i += 1) {
if (comp(array[i])) {
return i;
}
}
return -1;
}
function binaryFindElement(array, element) {
let start = 0;
let end = array.length - 1;
while (start <= end) {
const middle = Math.floor((start + end) / 2);
if (array[middle].element === element) {
return middle;
}
// eslint-disable-next-line no-bitwise
if (array[middle].element.compareDocumentPosition(element) & Node.DOCUMENT_POSITION_PRECEDING) {
end = middle - 1;
} else {
start = middle + 1;
}
}
return start;
}
const DescendantContext = /*#__PURE__*/React.createContext({});
if (process.env.NODE_ENV !== 'production') {
DescendantContext.displayName = 'DescendantContext';
}
function usePrevious(value) {
const ref = React.useRef(null);
React.useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
}
const noop = () => {};
/**
* This hook registers our descendant by passing it into an array. We can then
* search that array by to find its index when registering it in the component.
* We use this for focus management, keyboard navigation, and typeahead
* functionality for some components.
*
* The hook accepts the element node
*
* Our main goals with this are:
* 1) maximum composability,
* 2) minimal API friction
* 3) SSR compatibility*
* 4) concurrent safe
* 5) index always up-to-date with the tree despite changes
* 6) works with memoization of any component in the tree (hopefully)
*
* * As for SSR, the good news is that we don't actually need the index on the
* server for most use-cases, as we are only using it to determine the order of
* composed descendants for keyboard navigation.
*/
export function useDescendant(descendant) {
const [, forceUpdate] = React.useState();
const {
registerDescendant = noop,
unregisterDescendant = noop,
descendants = [],
parentId = null
} = React.useContext(DescendantContext);
// This will initially return -1 because we haven't registered the descendant
// on the first render. After we register, this will then return the correct
// index on the following render, and we will re-register descendants
// so that everything is up-to-date before the user interacts with a
// collection.
const index = findIndex(descendants, item => item.element === descendant.element);
const previousDescendants = usePrevious(descendants);
// We also need to re-register descendants any time ANY of the other
// descendants have changed. My brain was melting when I wrote this and it
// feels a little off, but checking in render and using the result in the
// effect's dependency array works well enough.
const someDescendantsHaveChanged = descendants.some((newDescendant, position) => {
return previousDescendants && previousDescendants[position] && previousDescendants[position].element !== newDescendant.element;
});
// Prevent any flashing
useEnhancedEffect(() => {
if (descendant.element) {
registerDescendant(_extends({}, descendant, {
index
}));
return () => {
unregisterDescendant(descendant.element);
};
}
forceUpdate({});
return undefined;
}, [registerDescendant, unregisterDescendant, index, someDescendantsHaveChanged, descendant]);
return {
parentId,
index
};
}
export function DescendantProvider(props) {
const {
children,
id
} = props;
const [items, set] = React.useState([]);
const registerDescendant = React.useCallback(_ref => {
let {
element
} = _ref,
other = _objectWithoutPropertiesLoose(_ref, _excluded);
set(oldItems => {
if (oldItems.length === 0) {
// If there are no items, register at index 0 and bail.
return [_extends({}, other, {
element,
index: 0
})];
}
const index = binaryFindElement(oldItems, element);
let newItems;
if (oldItems[index] && oldItems[index].element === element) {
// If the element is already registered, just use the same array
newItems = oldItems;
} else {
// When registering a descendant, we need to make sure we insert in
// into the array in the same order that it appears in the DOM. So as
// new descendants are added or maybe some are removed, we always know
// that the array is up-to-date and correct.
//
// So here we look at our registered descendants and see if the new
// element we are adding appears earlier than an existing descendant's
// DOM node via `node.compareDocumentPosition`. If it does, we insert
// the new element at this index. Because `registerDescendant` will be
// called in an effect every time the descendants state value changes,
// we should be sure that this index is accurate when descendent
// elements come or go from our component.
const newItem = _extends({}, other, {
element,
index
});
// If an index is not found we will push the element to the end.
newItems = oldItems.slice();
newItems.splice(index, 0, newItem);
}
newItems.forEach((item, position) => {
item.index = position;
});
return newItems;
});
}, []);
const unregisterDescendant = React.useCallback(element => {
set(oldItems => oldItems.filter(item => element !== item.element));
}, []);
const value = React.useMemo(() => ({
descendants: items,
registerDescendant,
unregisterDescendant,
parentId: id
}), [items, registerDescendant, unregisterDescendant, id]);
return /*#__PURE__*/_jsx(DescendantContext.Provider, {
value: value,
children: children
});
}
process.env.NODE_ENV !== "production" ? DescendantProvider.propTypes = {
children: PropTypes.node,
id: PropTypes.string
} : void 0;

View File

@ -0,0 +1,7 @@
import * as React from 'react';
import { TreeViewContextValue } from './TreeViewProvider.types';
export declare const DEFAULT_TREE_VIEW_CONTEXT_VALUE: TreeViewContextValue<any>;
/**
* @ignore - internal component.
*/
export declare const TreeViewContext: React.Context<TreeViewContextValue<any>>;

View File

@ -0,0 +1,21 @@
import * as React from 'react';
export const DEFAULT_TREE_VIEW_CONTEXT_VALUE = {
instance: null,
multiSelect: false,
disabledItemsFocusable: false,
treeId: undefined,
icons: {
defaultCollapseIcon: null,
defaultExpandIcon: null,
defaultParentIcon: null,
defaultEndIcon: null
}
};
/**
* @ignore - internal component.
*/
export const TreeViewContext = /*#__PURE__*/React.createContext(DEFAULT_TREE_VIEW_CONTEXT_VALUE);
if (process.env.NODE_ENV !== 'production') {
TreeViewContext.displayName = 'TreeViewContext';
}

View File

@ -0,0 +1,9 @@
import * as React from 'react';
import { TreeViewProviderProps } from './TreeViewProvider.types';
import { TreeViewAnyPluginSignature } from '../models';
/**
* Sets up the contexts for the underlying TreeItem components.
*
* @ignore - do not document.
*/
export declare function TreeViewProvider<TPlugins extends readonly TreeViewAnyPluginSignature[]>(props: TreeViewProviderProps<TPlugins>): React.JSX.Element;

View File

@ -0,0 +1,21 @@
import * as React from 'react';
import { TreeViewContext } from './TreeViewContext';
import { DescendantProvider } from './DescendantProvider';
import { jsx as _jsx } from "react/jsx-runtime";
/**
* Sets up the contexts for the underlying TreeItem components.
*
* @ignore - do not document.
*/
export function TreeViewProvider(props) {
const {
value,
children
} = props;
return /*#__PURE__*/_jsx(TreeViewContext.Provider, {
value: value,
children: /*#__PURE__*/_jsx(DescendantProvider, {
children: children
})
});
}

View File

@ -0,0 +1,18 @@
import * as React from 'react';
import { TreeViewAnyPluginSignature, TreeViewInstance } from '../models';
export interface TreeViewContextValue<TPlugins extends readonly TreeViewAnyPluginSignature[]> {
treeId: string | undefined;
instance: TreeViewInstance<TPlugins> | null;
multiSelect: boolean;
disabledItemsFocusable: boolean;
icons: {
defaultCollapseIcon: React.ReactNode;
defaultExpandIcon: React.ReactNode;
defaultParentIcon: React.ReactNode;
defaultEndIcon: React.ReactNode;
};
}
export interface TreeViewProviderProps<TPlugins extends readonly TreeViewAnyPluginSignature[]> {
value: TreeViewContextValue<TPlugins>;
children: React.ReactNode;
}

View File

@ -0,0 +1 @@
export {};

2
internals/TreeViewProvider/index.d.ts vendored Normal file
View File

@ -0,0 +1,2 @@
export { TreeViewProvider } from './TreeViewProvider';
export type { TreeViewProviderProps, TreeViewContextValue } from './TreeViewProvider.types';

View File

@ -0,0 +1 @@
export { TreeViewProvider } from './TreeViewProvider';

View File

@ -0,0 +1,3 @@
import { TreeViewAnyPluginSignature } from '../models';
import { TreeViewContextValue } from './TreeViewProvider.types';
export declare const useTreeViewContext: <TPlugins extends readonly TreeViewAnyPluginSignature[]>() => TreeViewContextValue<TPlugins>;

View File

@ -0,0 +1,3 @@
import * as React from 'react';
import { TreeViewContext } from './TreeViewContext';
export const useTreeViewContext = () => React.useContext(TreeViewContext);

View File

@ -0,0 +1,7 @@
import { ConvertPluginsIntoSignatures, MergePlugins } from '../models';
/**
* Internal plugins that creates the tools used by the other plugins.
* These plugins are used by the tree view components.
*/
export declare const TREE_VIEW_CORE_PLUGINS: readonly [import("../models").TreeViewPlugin<import("./useTreeViewInstanceEvents").UseTreeViewInstanceEventsSignature>];
export type TreeViewCorePluginsSignature = MergePlugins<ConvertPluginsIntoSignatures<typeof TREE_VIEW_CORE_PLUGINS>>;

View File

@ -0,0 +1,6 @@
import { useTreeViewInstanceEvents } from './useTreeViewInstanceEvents';
/**
* Internal plugins that creates the tools used by the other plugins.
* These plugins are used by the tree view components.
*/
export const TREE_VIEW_CORE_PLUGINS = [useTreeViewInstanceEvents];

2
internals/corePlugins/index.d.ts vendored Normal file
View File

@ -0,0 +1,2 @@
export { TREE_VIEW_CORE_PLUGINS } from './corePlugins';
export type { TreeViewCorePluginsSignature } from './corePlugins';

View File

@ -0,0 +1 @@
export { TREE_VIEW_CORE_PLUGINS } from './corePlugins';

View File

@ -0,0 +1,2 @@
export { useTreeViewInstanceEvents } from './useTreeViewInstanceEvents';
export type { UseTreeViewInstanceEventsSignature } from './useTreeViewInstanceEvents.types';

View File

@ -0,0 +1 @@
export { useTreeViewInstanceEvents } from './useTreeViewInstanceEvents';

View File

@ -0,0 +1,8 @@
import type { TreeViewPlugin } from '../../models';
import { UseTreeViewInstanceEventsSignature } from './useTreeViewInstanceEvents.types';
/**
* Plugin responsible for the registration of the nodes defined as JSX children of the TreeView.
* When we will have both a SimpleTreeView using JSX children and a TreeView using a data prop,
* this plugin will only be used by SimpleTreeView.
*/
export declare const useTreeViewInstanceEvents: TreeViewPlugin<UseTreeViewInstanceEventsSignature>;

View File

@ -0,0 +1,35 @@
import * as React from 'react';
import { EventManager } from '../../utils/EventManager';
import { populateInstance } from '../../useTreeView/useTreeView.utils';
const isSyntheticEvent = event => {
return event.isPropagationStopped !== undefined;
};
/**
* Plugin responsible for the registration of the nodes defined as JSX children of the TreeView.
* When we will have both a SimpleTreeView using JSX children and a TreeView using a data prop,
* this plugin will only be used by SimpleTreeView.
*/
export const useTreeViewInstanceEvents = ({
instance
}) => {
const [eventManager] = React.useState(() => new EventManager());
const publishEvent = React.useCallback((...args) => {
const [name, params, event = {}] = args;
event.defaultMuiPrevented = false;
if (isSyntheticEvent(event) && event.isPropagationStopped()) {
return;
}
eventManager.emit(name, params, event);
}, [eventManager]);
const subscribeEvent = React.useCallback((event, handler) => {
eventManager.on(event, handler);
return () => {
eventManager.removeListener(event, handler);
};
}, [eventManager]);
populateInstance(instance, {
$$publishEvent: publishEvent,
$$subscribeEvent: subscribeEvent
});
};

View File

@ -0,0 +1,21 @@
import { TreeViewPluginSignature } from '../../models';
import { TreeViewEventListener } from '../../models/events';
export interface UseTreeViewInstanceEventsInstance {
/**
* Should never be used directly.
* Please use `useInstanceEventHandler` instead.
* @param {string} eventName Name of the event to subscribe to.
* @param {TreeViewEventListener<any>} handler Event handler to call when the event is published.
* @returns {() => void} Cleanup function.
*/
$$subscribeEvent: (eventName: string, handler: TreeViewEventListener<any>) => () => void;
/**
* Should never be used directly.
* Please use `publishTreeViewEvent` instead.
* @param {string} eventName Name of the event to publish.
* @param {any} params The params to publish with the event.
*/
$$publishEvent: (eventName: string, params: any) => void;
}
export type UseTreeViewInstanceEventsSignature = TreeViewPluginSignature<{}, {}, UseTreeViewInstanceEventsInstance, {}, {}, never, [
]>;

View File

@ -0,0 +1,15 @@
import { CleanupTracking } from '../utils/cleanupTracking/CleanupTracking';
import { TreeViewAnyPluginSignature, TreeViewUsedEvents } from '../models';
import { TreeViewEventListener } from '../models/events';
import { UseTreeViewInstanceEventsInstance } from '../corePlugins/useTreeViewInstanceEvents/useTreeViewInstanceEvents.types';
interface RegistryContainer {
registry: CleanupTracking | null;
}
export declare function createUseInstanceEventHandler(registryContainer: RegistryContainer): <Instance extends UseTreeViewInstanceEventsInstance & {
$$signature: TreeViewAnyPluginSignature;
}, E extends keyof Instance["$$signature"]["events"] | keyof import("../models").MergePluginsProperty<[import("../corePlugins").TreeViewCorePluginsSignature, ...Instance["$$signature"]["dependantPlugins"]], "events">>(instance: Instance, eventName: E, handler: TreeViewEventListener<TreeViewUsedEvents<Instance["$$signature"]>[E]>) => void;
export declare const unstable_resetCleanupTracking: () => void;
export declare const useInstanceEventHandler: <Instance extends UseTreeViewInstanceEventsInstance & {
$$signature: TreeViewAnyPluginSignature;
}, E extends keyof Instance["$$signature"]["events"] | keyof import("../models").MergePluginsProperty<[import("../corePlugins").TreeViewCorePluginsSignature, ...Instance["$$signature"]["dependantPlugins"]], "events">>(instance: Instance, eventName: E, handler: TreeViewEventListener<TreeViewUsedEvents<Instance["$$signature"]>[E]>) => void;
export {};

View File

@ -0,0 +1,82 @@
import * as React from 'react';
import { TimerBasedCleanupTracking } from '../utils/cleanupTracking/TimerBasedCleanupTracking';
import { FinalizationRegistryBasedCleanupTracking } from '../utils/cleanupTracking/FinalizationRegistryBasedCleanupTracking';
// We use class to make it easier to detect in heap snapshots by name
class ObjectToBeRetainedByReact {}
// Based on https://github.com/Bnaya/use-dispose-uncommitted/blob/main/src/finalization-registry-based-impl.ts
// Check https://github.com/facebook/react/issues/15317 to get more information
export function createUseInstanceEventHandler(registryContainer) {
let cleanupTokensCounter = 0;
return function useInstanceEventHandler(instance, eventName, handler) {
if (registryContainer.registry === null) {
registryContainer.registry = typeof FinalizationRegistry !== 'undefined' ? new FinalizationRegistryBasedCleanupTracking() : new TimerBasedCleanupTracking();
}
const [objectRetainedByReact] = React.useState(new ObjectToBeRetainedByReact());
const subscription = React.useRef(null);
const handlerRef = React.useRef();
handlerRef.current = handler;
const cleanupTokenRef = React.useRef(null);
if (!subscription.current && handlerRef.current) {
const enhancedHandler = (params, event) => {
if (!event.defaultMuiPrevented) {
var _handlerRef$current;
(_handlerRef$current = handlerRef.current) == null || _handlerRef$current.call(handlerRef, params, event);
}
};
subscription.current = instance.$$subscribeEvent(eventName, enhancedHandler);
cleanupTokensCounter += 1;
cleanupTokenRef.current = {
cleanupToken: cleanupTokensCounter
};
registryContainer.registry.register(objectRetainedByReact,
// The callback below will be called once this reference stops being retained
() => {
var _subscription$current;
(_subscription$current = subscription.current) == null || _subscription$current.call(subscription);
subscription.current = null;
cleanupTokenRef.current = null;
}, cleanupTokenRef.current);
} else if (!handlerRef.current && subscription.current) {
subscription.current();
subscription.current = null;
if (cleanupTokenRef.current) {
registryContainer.registry.unregister(cleanupTokenRef.current);
cleanupTokenRef.current = null;
}
}
React.useEffect(() => {
if (!subscription.current && handlerRef.current) {
const enhancedHandler = (params, event) => {
if (!event.defaultMuiPrevented) {
var _handlerRef$current2;
(_handlerRef$current2 = handlerRef.current) == null || _handlerRef$current2.call(handlerRef, params, event);
}
};
subscription.current = instance.$$subscribeEvent(eventName, enhancedHandler);
}
if (cleanupTokenRef.current && registryContainer.registry) {
// If the effect was called, it means that this render was committed
// so we can trust the cleanup function to remove the listener.
registryContainer.registry.unregister(cleanupTokenRef.current);
cleanupTokenRef.current = null;
}
return () => {
var _subscription$current2;
(_subscription$current2 = subscription.current) == null || _subscription$current2.call(subscription);
subscription.current = null;
};
}, [instance, eventName]);
};
}
const registryContainer = {
registry: null
};
// eslint-disable-next-line @typescript-eslint/naming-convention
export const unstable_resetCleanupTracking = () => {
var _registryContainer$re;
(_registryContainer$re = registryContainer.registry) == null || _registryContainer$re.reset();
registryContainer.registry = null;
};
export const useInstanceEventHandler = createUseInstanceEventHandler(registryContainer);

9
internals/models/events.d.ts vendored Normal file
View File

@ -0,0 +1,9 @@
import * as React from 'react';
export interface TreeViewEventLookupElement {
params: object;
}
export type TreeViewEventListener<E extends TreeViewEventLookupElement> = (params: E['params'], event: MuiEvent<{}>) => void;
export type MuiBaseEvent = React.SyntheticEvent<HTMLElement> | DocumentEventMap[keyof DocumentEventMap] | {};
export type MuiEvent<E extends MuiBaseEvent = MuiBaseEvent> = E & {
defaultMuiPrevented?: boolean;
};

View File

@ -0,0 +1 @@
export {};

13
internals/models/helpers.d.ts vendored Normal file
View File

@ -0,0 +1,13 @@
import { TreeViewAnyPluginSignature, TreeViewPlugin } from './plugin';
export type DefaultizedProps<P extends {}, RequiredProps extends keyof P, AdditionalProps extends {} = {}> = Omit<P, RequiredProps | keyof AdditionalProps> & Required<Pick<P, RequiredProps>> & AdditionalProps;
export type MergePluginsProperty<TPlugins extends readonly any[], TProperty extends keyof TreeViewAnyPluginSignature> = TPlugins extends readonly [plugin: infer P, ...otherPlugin: infer R] ? P extends TreeViewAnyPluginSignature ? P[TProperty] & MergePluginsProperty<R, TProperty> : {} : {};
export type ConvertPluginsIntoSignatures<TPlugins extends readonly any[]> = TPlugins extends readonly [plugin: infer P, ...otherPlugin: infer R] ? P extends TreeViewPlugin<infer TSignature> ? [TSignature, ...ConvertPluginsIntoSignatures<R>] : ConvertPluginsIntoSignatures<R> : [];
export interface MergePlugins<TPlugins extends readonly any[]> {
state: MergePluginsProperty<TPlugins, 'state'>;
instance: MergePluginsProperty<TPlugins, 'instance'>;
params: MergePluginsProperty<TPlugins, 'params'>;
defaultizedParams: MergePluginsProperty<TPlugins, 'defaultizedParams'>;
dependantPlugins: MergePluginsProperty<TPlugins, 'dependantPlugins'>;
events: MergePluginsProperty<TPlugins, 'events'>;
models: MergePluginsProperty<TPlugins, 'models'>;
}

View File

@ -0,0 +1 @@
export {};

3
internals/models/index.d.ts vendored Normal file
View File

@ -0,0 +1,3 @@
export * from './helpers';
export * from './plugin';
export * from './treeView';

View File

@ -0,0 +1,3 @@
export * from './helpers';
export * from './plugin';
export * from './treeView';

69
internals/models/plugin.d.ts vendored Normal file
View File

@ -0,0 +1,69 @@
import * as React from 'react';
import { EventHandlers } from '@mui/base/utils';
import { TreeViewModel } from './treeView';
import type { TreeViewContextValue } from '../TreeViewProvider';
import type { MergePluginsProperty } from './helpers';
import { TreeViewEventLookupElement } from './events';
import type { TreeViewCorePluginsSignature } from '../corePlugins';
export interface TreeViewPluginOptions<TSignature extends TreeViewAnyPluginSignature> {
instance: TreeViewUsedInstance<TSignature>;
params: TreeViewUsedDefaultizedParams<TSignature>;
state: TreeViewUsedState<TSignature>;
models: TreeViewUsedModels<TSignature>;
setState: React.Dispatch<React.SetStateAction<TreeViewUsedState<TSignature>>>;
rootRef: React.RefObject<HTMLUListElement>;
}
type TreeViewModelsInitializer<TSignature extends TreeViewAnyPluginSignature> = {
[TControlled in keyof TSignature['models']]: {
controlledProp: TControlled;
defaultProp: keyof TSignature['params'];
};
};
interface TreeViewResponse {
getRootProps?: <TOther extends EventHandlers = {}>(otherHandlers: TOther) => React.HTMLAttributes<HTMLUListElement>;
contextValue?: TreeViewContextValue<any>;
}
export type TreeViewPluginSignature<TParams extends {}, TDefaultizedParams extends {}, TInstance extends {}, TEvents extends {
[key in keyof TEvents]: TreeViewEventLookupElement;
}, TState extends {}, TModelNames extends keyof TDefaultizedParams, TDependantPlugins extends readonly TreeViewAnyPluginSignature[]> = {
params: TParams;
defaultizedParams: TDefaultizedParams;
instance: TInstance;
state: TState;
models: {
[TControlled in TModelNames]-?: TreeViewModel<Exclude<TDefaultizedParams[TControlled], undefined>>;
};
events: TEvents;
dependantPlugins: TDependantPlugins;
};
export type TreeViewAnyPluginSignature = {
state: any;
instance: any;
params: any;
defaultizedParams: any;
dependantPlugins: any;
events: any;
models: any;
};
type TreeViewUsedPlugins<TSignature extends TreeViewAnyPluginSignature> = [
TreeViewCorePluginsSignature,
...TSignature['dependantPlugins']
];
type TreeViewUsedParams<TSignature extends TreeViewAnyPluginSignature> = TSignature['params'] & MergePluginsProperty<TreeViewUsedPlugins<TSignature>, 'params'>;
type TreeViewUsedDefaultizedParams<TSignature extends TreeViewAnyPluginSignature> = TSignature['defaultizedParams'] & MergePluginsProperty<TreeViewUsedPlugins<TSignature>, 'defaultizedParams'>;
export type TreeViewUsedInstance<TSignature extends TreeViewAnyPluginSignature> = TSignature['instance'] & MergePluginsProperty<TreeViewUsedPlugins<TSignature>, 'instance'> & {
/**
* Private property only defined in TypeScript to be able to access the plugin signature from the instance object.
*/
$$signature: TSignature;
};
type TreeViewUsedState<TSignature extends TreeViewAnyPluginSignature> = TSignature['state'] & MergePluginsProperty<TreeViewUsedPlugins<TSignature>, 'state'>;
export type TreeViewUsedModels<TSignature extends TreeViewAnyPluginSignature> = TSignature['models'] & MergePluginsProperty<TreeViewUsedPlugins<TSignature>, 'models'>;
export type TreeViewUsedEvents<TSignature extends TreeViewAnyPluginSignature> = TSignature['events'] & MergePluginsProperty<TreeViewUsedPlugins<TSignature>, 'events'>;
export type TreeViewPlugin<TSignature extends TreeViewAnyPluginSignature> = {
(options: TreeViewPluginOptions<TSignature>): void | TreeViewResponse;
getDefaultizedParams?: (params: TreeViewUsedParams<TSignature>) => TSignature['defaultizedParams'];
getInitialState?: (params: TreeViewUsedDefaultizedParams<TSignature>) => TSignature['state'];
models?: TreeViewModelsInitializer<TSignature>;
};
export {};

View File

@ -0,0 +1 @@
export {};

23
internals/models/treeView.d.ts vendored Normal file
View File

@ -0,0 +1,23 @@
import * as React from 'react';
import type { TreeViewAnyPluginSignature } from './plugin';
import type { MergePluginsProperty } from './helpers';
export interface TreeViewNode {
id: string;
idAttribute: string | undefined;
index: number;
parentId: string | null;
expandable: boolean;
disabled: boolean | undefined;
}
export interface TreeViewItemRange {
start?: string | null;
end?: string | null;
next?: string | null;
current?: string;
}
export interface TreeViewModel<TValue> {
name: string;
value: TValue;
setValue: React.Dispatch<React.SetStateAction<TValue>>;
}
export type TreeViewInstance<TSignatures extends readonly TreeViewAnyPluginSignature[]> = MergePluginsProperty<TSignatures, 'instance'>;

View File

@ -0,0 +1 @@
export {};

10
internals/plugins/defaultPlugins.d.ts vendored Normal file
View File

@ -0,0 +1,10 @@
import { UseTreeViewNodesParameters } from './useTreeViewNodes';
import { UseTreeViewExpansionParameters } from './useTreeViewExpansion';
import { UseTreeViewSelectionParameters } from './useTreeViewSelection';
import { UseTreeViewFocusParameters } from './useTreeViewFocus';
import { UseTreeViewContextValueBuilderParameters } from './useTreeViewContextValueBuilder';
import { ConvertPluginsIntoSignatures } from '../models';
export declare const DEFAULT_TREE_VIEW_PLUGINS: readonly [import("../models").TreeViewPlugin<import("./useTreeViewNodes").UseTreeViewNodesSignature>, import("../models").TreeViewPlugin<import("./useTreeViewExpansion").UseTreeViewExpansionSignature>, import("../models").TreeViewPlugin<import("./useTreeViewSelection").UseTreeViewSelectionSignature<any>>, import("../models").TreeViewPlugin<import("./useTreeViewFocus").UseTreeViewFocusSignature>, import("../models").TreeViewPlugin<import("./useTreeViewKeyboardNavigation").UseTreeViewKeyboardNavigationSignature>, import("../models").TreeViewPlugin<import("./useTreeViewContextValueBuilder").UseTreeViewContextValueBuilderSignature>];
export type DefaultTreeViewPlugins = ConvertPluginsIntoSignatures<typeof DEFAULT_TREE_VIEW_PLUGINS>;
export interface DefaultTreeViewPluginParameters<Multiple extends boolean | undefined> extends UseTreeViewNodesParameters, UseTreeViewExpansionParameters, UseTreeViewFocusParameters, UseTreeViewSelectionParameters<Multiple>, UseTreeViewContextValueBuilderParameters {
}

View File

@ -0,0 +1,9 @@
import { useTreeViewNodes } from './useTreeViewNodes';
import { useTreeViewExpansion } from './useTreeViewExpansion';
import { useTreeViewSelection } from './useTreeViewSelection';
import { useTreeViewFocus } from './useTreeViewFocus';
import { useTreeViewKeyboardNavigation } from './useTreeViewKeyboardNavigation';
import { useTreeViewContextValueBuilder } from './useTreeViewContextValueBuilder';
export const DEFAULT_TREE_VIEW_PLUGINS = [useTreeViewNodes, useTreeViewExpansion, useTreeViewSelection, useTreeViewFocus, useTreeViewKeyboardNavigation, useTreeViewContextValueBuilder];
// We can't infer this type from the plugin, otherwise we would lose the generics.

2
internals/plugins/index.d.ts vendored Normal file
View File

@ -0,0 +1,2 @@
export { DEFAULT_TREE_VIEW_PLUGINS } from './defaultPlugins';
export type { DefaultTreeViewPlugins } from './defaultPlugins';

View File

@ -0,0 +1 @@
export { DEFAULT_TREE_VIEW_PLUGINS } from './defaultPlugins';

View File

@ -0,0 +1,2 @@
export { useTreeViewContextValueBuilder } from './useTreeViewContextValueBuilder';
export type { UseTreeViewContextValueBuilderSignature, UseTreeViewContextValueBuilderParameters, UseTreeViewContextValueBuilderDefaultizedParameters, } from './useTreeViewContextValueBuilder.types';

View File

@ -0,0 +1 @@
export { useTreeViewContextValueBuilder } from './useTreeViewContextValueBuilder';

View File

@ -0,0 +1,3 @@
import { TreeViewPlugin } from '../../models';
import { UseTreeViewContextValueBuilderSignature } from './useTreeViewContextValueBuilder.types';
export declare const useTreeViewContextValueBuilder: TreeViewPlugin<UseTreeViewContextValueBuilderSignature>;

View File

@ -0,0 +1,24 @@
import useId from '@mui/utils/useId';
export const useTreeViewContextValueBuilder = ({
instance,
params
}) => {
const treeId = useId(params.id);
return {
getRootProps: () => ({
id: treeId
}),
contextValue: {
treeId,
instance: instance,
multiSelect: params.multiSelect,
disabledItemsFocusable: params.disabledItemsFocusable,
icons: {
defaultCollapseIcon: params.defaultCollapseIcon,
defaultEndIcon: params.defaultEndIcon,
defaultExpandIcon: params.defaultExpandIcon,
defaultParentIcon: params.defaultParentIcon
}
}
};
};

View File

@ -0,0 +1,34 @@
import * as React from 'react';
import { TreeViewPluginSignature } from '../../models';
import { UseTreeViewNodesSignature } from '../useTreeViewNodes';
import { UseTreeViewSelectionSignature } from '../useTreeViewSelection';
export interface UseTreeViewContextValueBuilderParameters {
/**
* This prop is used to help implement the accessibility logic.
* If you don't provide this prop. It falls back to a randomly generated id.
*/
id?: string;
/**
* The default icon used to collapse the node.
*/
defaultCollapseIcon?: React.ReactNode;
/**
* The default icon displayed next to a end node. This is applied to all
* tree nodes and can be overridden by the TreeItem `icon` prop.
*/
defaultEndIcon?: React.ReactNode;
/**
* The default icon used to expand the node.
*/
defaultExpandIcon?: React.ReactNode;
/**
* The default icon displayed next to a parent node. This is applied to all
* parent nodes and can be overridden by the TreeItem `icon` prop.
*/
defaultParentIcon?: React.ReactNode;
}
export type UseTreeViewContextValueBuilderDefaultizedParameters = UseTreeViewContextValueBuilderParameters;
export type UseTreeViewContextValueBuilderSignature = TreeViewPluginSignature<UseTreeViewContextValueBuilderParameters, UseTreeViewContextValueBuilderDefaultizedParameters, {}, {}, {}, never, [
UseTreeViewNodesSignature,
UseTreeViewSelectionSignature<any>
]>;

View File

@ -0,0 +1,2 @@
export { useTreeViewExpansion } from './useTreeViewExpansion';
export type { UseTreeViewExpansionSignature, UseTreeViewExpansionParameters, UseTreeViewExpansionDefaultizedParameters, } from './useTreeViewExpansion.types';

View File

@ -0,0 +1 @@
export { useTreeViewExpansion } from './useTreeViewExpansion';

View File

@ -0,0 +1,3 @@
import { TreeViewPlugin } from '../../models';
import { UseTreeViewExpansionSignature } from './useTreeViewExpansion.types';
export declare const useTreeViewExpansion: TreeViewPlugin<UseTreeViewExpansionSignature>;

View File

@ -0,0 +1,63 @@
import _extends from "@babel/runtime/helpers/esm/extends";
import * as React from 'react';
import useEventCallback from '@mui/utils/useEventCallback';
import { populateInstance } from '../../useTreeView/useTreeView.utils';
export const useTreeViewExpansion = ({
instance,
params,
models
}) => {
const isNodeExpanded = React.useCallback(nodeId => {
return Array.isArray(models.expanded.value) ? models.expanded.value.indexOf(nodeId) !== -1 : false;
}, [models.expanded.value]);
const isNodeExpandable = React.useCallback(nodeId => {
var _instance$getNode;
return !!((_instance$getNode = instance.getNode(nodeId)) != null && _instance$getNode.expandable);
}, [instance]);
const toggleNodeExpansion = useEventCallback((event, nodeId) => {
if (nodeId == null) {
return;
}
let newExpanded;
if (models.expanded.value.indexOf(nodeId) !== -1) {
newExpanded = models.expanded.value.filter(id => id !== nodeId);
} else {
newExpanded = [nodeId].concat(models.expanded.value);
}
if (params.onNodeToggle) {
params.onNodeToggle(event, newExpanded);
}
models.expanded.setValue(newExpanded);
});
const expandAllSiblings = (event, nodeId) => {
const node = instance.getNode(nodeId);
const siblings = instance.getChildrenIds(node.parentId);
const diff = siblings.filter(child => instance.isNodeExpandable(child) && !instance.isNodeExpanded(child));
const newExpanded = models.expanded.value.concat(diff);
if (diff.length > 0) {
models.expanded.setValue(newExpanded);
if (params.onNodeToggle) {
params.onNodeToggle(event, newExpanded);
}
}
};
populateInstance(instance, {
isNodeExpanded,
isNodeExpandable,
toggleNodeExpansion,
expandAllSiblings
});
};
useTreeViewExpansion.models = {
expanded: {
controlledProp: 'expanded',
defaultProp: 'defaultExpanded'
}
};
const DEFAULT_EXPANDED = [];
useTreeViewExpansion.getDefaultizedParams = params => {
var _params$defaultExpand;
return _extends({}, params, {
defaultExpanded: (_params$defaultExpand = params.defaultExpanded) != null ? _params$defaultExpand : DEFAULT_EXPANDED
});
};

View File

@ -0,0 +1,32 @@
import * as React from 'react';
import { DefaultizedProps, TreeViewPluginSignature } from '../../models';
import { UseTreeViewNodesSignature } from '../useTreeViewNodes';
export interface UseTreeViewExpansionInstance {
isNodeExpanded: (nodeId: string) => boolean;
isNodeExpandable: (nodeId: string) => boolean;
toggleNodeExpansion: (event: React.SyntheticEvent, value: string) => void;
expandAllSiblings: (event: React.KeyboardEvent<HTMLUListElement>, nodeId: string) => void;
}
export interface UseTreeViewExpansionParameters {
/**
* Expanded node ids.
* Used when the item's expansion is controlled.
*/
expanded?: string[];
/**
* Expanded node ids.
* Used when the item's expansion is not controlled.
* @default []
*/
defaultExpanded?: string[];
/**
* Callback fired when tree items are expanded/collapsed.
* @param {React.SyntheticEvent} event The event source of the callback.
* @param {array} nodeIds The ids of the expanded nodes.
*/
onNodeToggle?: (event: React.SyntheticEvent, nodeIds: string[]) => void;
}
export type UseTreeViewExpansionDefaultizedParameters = DefaultizedProps<UseTreeViewExpansionParameters, 'defaultExpanded'>;
export type UseTreeViewExpansionSignature = TreeViewPluginSignature<UseTreeViewExpansionParameters, UseTreeViewExpansionDefaultizedParameters, UseTreeViewExpansionInstance, {}, {}, 'expanded', [
UseTreeViewNodesSignature
]>;

View File

@ -0,0 +1 @@
export {};

View File

@ -0,0 +1,2 @@
export { useTreeViewFocus } from './useTreeViewFocus';
export type { UseTreeViewFocusSignature, UseTreeViewFocusParameters, UseTreeViewFocusDefaultizedParameters, } from './useTreeViewFocus.types';

View File

@ -0,0 +1 @@
export { useTreeViewFocus } from './useTreeViewFocus';

View File

@ -0,0 +1,3 @@
import { TreeViewPlugin } from '../../models';
import { UseTreeViewFocusSignature } from './useTreeViewFocus.types';
export declare const useTreeViewFocus: TreeViewPlugin<UseTreeViewFocusSignature>;

View File

@ -0,0 +1,89 @@
import _extends from "@babel/runtime/helpers/esm/extends";
import * as React from 'react';
import useEventCallback from '@mui/utils/useEventCallback';
import ownerDocument from '@mui/utils/ownerDocument';
import { populateInstance } from '../../useTreeView/useTreeView.utils';
import { useInstanceEventHandler } from '../../hooks/useInstanceEventHandler';
export const useTreeViewFocus = ({
instance,
params,
state,
setState,
models,
rootRef
}) => {
const setFocusedNodeId = useEventCallback(nodeId => {
const cleanNodeId = typeof nodeId === 'function' ? nodeId(state.focusedNodeId) : nodeId;
setState(prevState => _extends({}, prevState, {
focusedNodeId: cleanNodeId
}));
});
const isNodeFocused = React.useCallback(nodeId => state.focusedNodeId === nodeId, [state.focusedNodeId]);
const focusNode = useEventCallback((event, nodeId) => {
if (nodeId) {
setFocusedNodeId(nodeId);
if (params.onNodeFocus) {
params.onNodeFocus(event, nodeId);
}
}
});
populateInstance(instance, {
isNodeFocused,
focusNode
});
useInstanceEventHandler(instance, 'removeNode', ({
id
}) => {
setFocusedNodeId(oldFocusedNodeId => {
if (oldFocusedNodeId === id && rootRef.current === ownerDocument(rootRef.current).activeElement) {
return instance.getChildrenIds(null)[0];
}
return oldFocusedNodeId;
});
});
const createHandleFocus = otherHandlers => event => {
var _otherHandlers$onFocu;
(_otherHandlers$onFocu = otherHandlers.onFocus) == null || _otherHandlers$onFocu.call(otherHandlers, event);
// if the event bubbled (which is React specific) we don't want to steal focus
if (event.target === event.currentTarget) {
const isNodeVisible = nodeId => {
const node = instance.getNode(nodeId);
return node && (node.parentId == null || instance.isNodeExpanded(node.parentId));
};
let nodeToFocusId;
if (Array.isArray(models.selected.value)) {
nodeToFocusId = models.selected.value.find(isNodeVisible);
} else if (models.selected.value != null && isNodeVisible(models.selected.value)) {
nodeToFocusId = models.selected.value;
}
if (nodeToFocusId == null) {
nodeToFocusId = instance.getNavigableChildrenIds(null)[0];
}
instance.focusNode(event, nodeToFocusId);
}
};
const createHandleBlur = otherHandlers => event => {
var _otherHandlers$onBlur;
(_otherHandlers$onBlur = otherHandlers.onBlur) == null || _otherHandlers$onBlur.call(otherHandlers, event);
setFocusedNodeId(null);
};
const focusedNode = instance.getNode(state.focusedNodeId);
const activeDescendant = focusedNode ? focusedNode.idAttribute : null;
return {
getRootProps: otherHandlers => ({
onFocus: createHandleFocus(otherHandlers),
onBlur: createHandleBlur(otherHandlers),
'aria-activedescendant': activeDescendant != null ? activeDescendant : undefined
})
};
};
useTreeViewFocus.getInitialState = () => ({
focusedNodeId: null
});
useTreeViewFocus.getDefaultizedParams = params => {
var _params$disabledItems;
return _extends({}, params, {
disabledItemsFocusable: (_params$disabledItems = params.disabledItemsFocusable) != null ? _params$disabledItems : false
});
};

View File

@ -0,0 +1,27 @@
import * as React from 'react';
import { TreeViewPluginSignature } from '../../models';
import type { UseTreeViewNodesSignature } from '../useTreeViewNodes';
import type { UseTreeViewSelectionSignature } from '../useTreeViewSelection';
import { UseTreeViewExpansionSignature } from '../useTreeViewExpansion';
export interface UseTreeViewFocusInstance {
isNodeFocused: (nodeId: string) => boolean;
focusNode: (event: React.SyntheticEvent, nodeId: string | null) => void;
}
export interface UseTreeViewFocusParameters {
/**
* Callback fired when tree items are focused.
* @param {React.SyntheticEvent} event The event source of the callback **Warning**: This is a generic event not a focus event.
* @param {string} nodeId The id of the node focused.
* @param {string} value of the focused node.
*/
onNodeFocus?: (event: React.SyntheticEvent, nodeId: string) => void;
}
export type UseTreeViewFocusDefaultizedParameters = UseTreeViewFocusParameters;
export interface UseTreeViewFocusState {
focusedNodeId: string | null;
}
export type UseTreeViewFocusSignature = TreeViewPluginSignature<UseTreeViewFocusParameters, UseTreeViewFocusParameters, UseTreeViewFocusInstance, {}, UseTreeViewFocusState, never, [
UseTreeViewNodesSignature,
UseTreeViewSelectionSignature<any>,
UseTreeViewExpansionSignature
]>;

View File

@ -0,0 +1 @@
export {};

View File

@ -0,0 +1,2 @@
export { useTreeViewKeyboardNavigation } from './useTreeViewKeyboardNavigation';
export type { UseTreeViewKeyboardNavigationSignature } from './useTreeViewKeyboardNavigation.types';

View File

@ -0,0 +1 @@
export { useTreeViewKeyboardNavigation } from './useTreeViewKeyboardNavigation';

View File

@ -0,0 +1,3 @@
import { TreeViewPlugin } from '../../models';
import { UseTreeViewKeyboardNavigationSignature } from './useTreeViewKeyboardNavigation.types';
export declare const useTreeViewKeyboardNavigation: TreeViewPlugin<UseTreeViewKeyboardNavigationSignature>;

View File

@ -0,0 +1,223 @@
import _extends from "@babel/runtime/helpers/esm/extends";
import * as React from 'react';
import { useTheme } from '@mui/material/styles';
import useEventCallback from '@mui/utils/useEventCallback';
import { getFirstNode, getLastNode, getNextNode, getPreviousNode, populateInstance } from '../../useTreeView/useTreeView.utils';
function isPrintableCharacter(string) {
return string && string.length === 1 && string.match(/\S/);
}
function findNextFirstChar(firstChars, startIndex, char) {
for (let i = startIndex; i < firstChars.length; i += 1) {
if (char === firstChars[i]) {
return i;
}
}
return -1;
}
export const useTreeViewKeyboardNavigation = ({
instance,
params,
state
}) => {
const theme = useTheme();
const isRtl = theme.direction === 'rtl';
const firstCharMap = React.useRef({});
const mapFirstChar = useEventCallback((nodeId, firstChar) => {
firstCharMap.current[nodeId] = firstChar;
return () => {
const newMap = _extends({}, firstCharMap.current);
delete newMap[nodeId];
firstCharMap.current = newMap;
};
});
populateInstance(instance, {
mapFirstChar
});
const handleNextArrow = event => {
if (state.focusedNodeId != null && instance.isNodeExpandable(state.focusedNodeId)) {
if (instance.isNodeExpanded(state.focusedNodeId)) {
instance.focusNode(event, getNextNode(instance, state.focusedNodeId));
} else if (!instance.isNodeDisabled(state.focusedNodeId)) {
instance.toggleNodeExpansion(event, state.focusedNodeId);
}
}
return true;
};
const handlePreviousArrow = event => {
if (state.focusedNodeId == null) {
return false;
}
if (instance.isNodeExpanded(state.focusedNodeId) && !instance.isNodeDisabled(state.focusedNodeId)) {
instance.toggleNodeExpansion(event, state.focusedNodeId);
return true;
}
const parent = instance.getNode(state.focusedNodeId).parentId;
if (parent) {
instance.focusNode(event, parent);
return true;
}
return false;
};
const focusByFirstCharacter = (event, nodeId, firstChar) => {
let start;
let index;
const lowercaseChar = firstChar.toLowerCase();
const firstCharIds = [];
const firstChars = [];
// This really only works since the ids are strings
Object.keys(firstCharMap.current).forEach(mapNodeId => {
const map = instance.getNode(mapNodeId);
const visible = map.parentId ? instance.isNodeExpanded(map.parentId) : true;
const shouldBeSkipped = params.disabledItemsFocusable ? false : instance.isNodeDisabled(mapNodeId);
if (visible && !shouldBeSkipped) {
firstCharIds.push(mapNodeId);
firstChars.push(firstCharMap.current[mapNodeId]);
}
});
// Get start index for search based on position of currentItem
start = firstCharIds.indexOf(nodeId) + 1;
if (start >= firstCharIds.length) {
start = 0;
}
// Check remaining slots in the menu
index = findNextFirstChar(firstChars, start, lowercaseChar);
// If not found in remaining slots, check from beginning
if (index === -1) {
index = findNextFirstChar(firstChars, 0, lowercaseChar);
}
// If match was found...
if (index > -1) {
instance.focusNode(event, firstCharIds[index]);
}
};
const selectNextNode = (event, id) => {
if (!instance.isNodeDisabled(getNextNode(instance, id))) {
instance.selectRange(event, {
end: getNextNode(instance, id),
current: id
}, true);
}
};
const selectPreviousNode = (event, nodeId) => {
if (!instance.isNodeDisabled(getPreviousNode(instance, nodeId))) {
instance.selectRange(event, {
end: getPreviousNode(instance, nodeId),
current: nodeId
}, true);
}
};
const createHandleKeyDown = otherHandlers => event => {
var _otherHandlers$onKeyD;
(_otherHandlers$onKeyD = otherHandlers.onKeyDown) == null || _otherHandlers$onKeyD.call(otherHandlers, event);
let flag = false;
const key = event.key;
// If the tree is empty there will be no focused node
if (event.altKey || event.currentTarget !== event.target || state.focusedNodeId == null) {
return;
}
const ctrlPressed = event.ctrlKey || event.metaKey;
switch (key) {
case ' ':
if (!params.disableSelection && !instance.isNodeDisabled(state.focusedNodeId)) {
flag = true;
if (params.multiSelect && event.shiftKey) {
instance.selectRange(event, {
end: state.focusedNodeId
});
} else if (params.multiSelect) {
instance.selectNode(event, state.focusedNodeId, true);
} else {
instance.selectNode(event, state.focusedNodeId);
}
}
event.stopPropagation();
break;
case 'Enter':
if (!instance.isNodeDisabled(state.focusedNodeId)) {
if (instance.isNodeExpandable(state.focusedNodeId)) {
instance.toggleNodeExpansion(event, state.focusedNodeId);
flag = true;
} else if (!params.disableSelection) {
flag = true;
if (params.multiSelect) {
instance.selectNode(event, state.focusedNodeId, true);
} else {
instance.selectNode(event, state.focusedNodeId);
}
}
}
event.stopPropagation();
break;
case 'ArrowDown':
if (params.multiSelect && event.shiftKey && !params.disableSelection) {
selectNextNode(event, state.focusedNodeId);
}
instance.focusNode(event, getNextNode(instance, state.focusedNodeId));
flag = true;
break;
case 'ArrowUp':
if (params.multiSelect && event.shiftKey && !params.disableSelection) {
selectPreviousNode(event, state.focusedNodeId);
}
instance.focusNode(event, getPreviousNode(instance, state.focusedNodeId));
flag = true;
break;
case 'ArrowRight':
if (isRtl) {
flag = handlePreviousArrow(event);
} else {
flag = handleNextArrow(event);
}
break;
case 'ArrowLeft':
if (isRtl) {
flag = handleNextArrow(event);
} else {
flag = handlePreviousArrow(event);
}
break;
case 'Home':
if (params.multiSelect && ctrlPressed && event.shiftKey && !params.disableSelection && !instance.isNodeDisabled(state.focusedNodeId)) {
instance.rangeSelectToFirst(event, state.focusedNodeId);
}
instance.focusNode(event, getFirstNode(instance));
flag = true;
break;
case 'End':
if (params.multiSelect && ctrlPressed && event.shiftKey && !params.disableSelection && !instance.isNodeDisabled(state.focusedNodeId)) {
instance.rangeSelectToLast(event, state.focusedNodeId);
}
instance.focusNode(event, getLastNode(instance));
flag = true;
break;
default:
if (key === '*') {
instance.expandAllSiblings(event, state.focusedNodeId);
flag = true;
} else if (params.multiSelect && ctrlPressed && key.toLowerCase() === 'a' && !params.disableSelection) {
instance.selectRange(event, {
start: getFirstNode(instance),
end: getLastNode(instance)
});
flag = true;
} else if (!ctrlPressed && !event.shiftKey && isPrintableCharacter(key)) {
focusByFirstCharacter(event, state.focusedNodeId, key);
flag = true;
}
}
if (flag) {
event.preventDefault();
event.stopPropagation();
}
};
return {
getRootProps: otherHandlers => ({
onKeyDown: createHandleKeyDown(otherHandlers)
})
};
};

View File

@ -0,0 +1,14 @@
import { TreeViewPluginSignature } from '../../models';
import { UseTreeViewNodesSignature } from '../useTreeViewNodes';
import { UseTreeViewSelectionSignature } from '../useTreeViewSelection';
import { UseTreeViewFocusSignature } from '../useTreeViewFocus';
import { UseTreeViewExpansionSignature } from '../useTreeViewExpansion';
export interface UseTreeViewKeyboardNavigationInstance {
mapFirstChar: (nodeId: string, firstChar: string) => () => void;
}
export type UseTreeViewKeyboardNavigationSignature = TreeViewPluginSignature<{}, {}, UseTreeViewKeyboardNavigationInstance, {}, {}, never, [
UseTreeViewNodesSignature,
UseTreeViewSelectionSignature<any>,
UseTreeViewFocusSignature,
UseTreeViewExpansionSignature
]>;

View File

@ -0,0 +1,2 @@
export { useTreeViewNodes } from './useTreeViewNodes';
export type { UseTreeViewNodesSignature, UseTreeViewNodesParameters, UseTreeViewNodesDefaultizedParameters, } from './useTreeViewNodes.types';

View File

@ -0,0 +1 @@
export { useTreeViewNodes } from './useTreeViewNodes';

View File

@ -0,0 +1,3 @@
import { TreeViewPlugin } from '../../models';
import { UseTreeViewNodesSignature } from './useTreeViewNodes.types';
export declare const useTreeViewNodes: TreeViewPlugin<UseTreeViewNodesSignature>;

View File

@ -0,0 +1,60 @@
import _extends from "@babel/runtime/helpers/esm/extends";
import * as React from 'react';
import useEventCallback from '@mui/utils/useEventCallback';
import { populateInstance } from '../../useTreeView/useTreeView.utils';
import { publishTreeViewEvent } from '../../utils/publishTreeViewEvent';
export const useTreeViewNodes = ({
instance,
params
}) => {
const nodeMap = React.useRef({});
const getNode = React.useCallback(nodeId => nodeMap.current[nodeId], []);
const insertNode = React.useCallback(node => {
nodeMap.current[node.id] = node;
}, []);
const removeNode = React.useCallback(nodeId => {
const newMap = _extends({}, nodeMap.current);
delete newMap[nodeId];
nodeMap.current = newMap;
publishTreeViewEvent(instance, 'removeNode', {
id: nodeId
});
}, [instance]);
const isNodeDisabled = React.useCallback(nodeId => {
if (nodeId == null) {
return false;
}
let node = instance.getNode(nodeId);
// This can be called before the node has been added to the node map.
if (!node) {
return false;
}
if (node.disabled) {
return true;
}
while (node.parentId != null) {
node = instance.getNode(node.parentId);
if (node.disabled) {
return true;
}
}
return false;
}, [instance]);
const getChildrenIds = useEventCallback(nodeId => Object.values(nodeMap.current).filter(node => node.parentId === nodeId).sort((a, b) => a.index - b.index).map(child => child.id));
const getNavigableChildrenIds = nodeId => {
let childrenIds = instance.getChildrenIds(nodeId);
if (!params.disabledItemsFocusable) {
childrenIds = childrenIds.filter(node => !instance.isNodeDisabled(node));
}
return childrenIds;
};
populateInstance(instance, {
getNode,
updateNode: insertNode,
removeNode,
getChildrenIds,
getNavigableChildrenIds,
isNodeDisabled
});
};

Some files were not shown because too many files have changed in this diff Show More