Init dipal x tree
This commit is contained in:
commit
2c58446690
4544
CHANGELOG.md
Normal file
4544
CHANGELOG.md
Normal file
File diff suppressed because it is too large
Load Diff
72
Collapse/Collapse.d.ts
vendored
Normal file
72
Collapse/Collapse.d.ts
vendored
Normal 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
383
Collapse/Collapse.js
Normal 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
18
Collapse/collapseClasses.d.ts
vendored
Normal 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;
|
7
Collapse/collapseClasses.js
Normal file
7
Collapse/collapseClasses.js
Normal 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
5
Collapse/index.d.ts
vendored
Normal 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
5
Collapse/index.js
Normal 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
6
Collapse/package.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"sideEffects": false,
|
||||
"module": "./index.js",
|
||||
"main": "../node/Collapse/index.js",
|
||||
"types": "./index.d.ts"
|
||||
}
|
21
LICENSE
Normal file
21
LICENSE
Normal 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
27
README.md
Normal 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
13
TreeItem/TreeItem.d.ts
vendored
Normal 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
412
TreeItem/TreeItem.js
Normal 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
86
TreeItem/TreeItem.types.d.ts
vendored
Normal 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;
|
||||
}
|
1
TreeItem/TreeItem.types.js
Normal file
1
TreeItem/TreeItem.types.js
Normal file
@ -0,0 +1 @@
|
||||
export {};
|
52
TreeItem/TreeItemContent.d.ts
vendored
Normal file
52
TreeItem/TreeItemContent.d.ts
vendored
Normal 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
101
TreeItem/TreeItemContent.js
Normal 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
6
TreeItem/index.d.ts
vendored
Normal 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
4
TreeItem/index.js
Normal file
@ -0,0 +1,4 @@
|
||||
export * from './TreeItem';
|
||||
export * from './useTreeItem';
|
||||
export * from './treeItemClasses';
|
||||
export { TreeItemContent } from './TreeItemContent';
|
6
TreeItem/package.json
Normal file
6
TreeItem/package.json
Normal 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
23
TreeItem/treeItemClasses.d.ts
vendored
Normal 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;
|
6
TreeItem/treeItemClasses.js
Normal file
6
TreeItem/treeItemClasses.js
Normal 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
10
TreeItem/useTreeItem.d.ts
vendored
Normal 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
59
TreeItem/useTreeItem.js
Normal 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
17
TreeView/TreeView.d.ts
vendored
Normal 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
211
TreeView/TreeView.js
Normal 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
27
TreeView/TreeView.types.d.ts
vendored
Normal 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>;
|
1
TreeView/TreeView.types.js
Normal file
1
TreeView/TreeView.types.js
Normal file
@ -0,0 +1 @@
|
||||
export {};
|
3
TreeView/index.d.ts
vendored
Normal file
3
TreeView/index.d.ts
vendored
Normal 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
3
TreeView/index.js
Normal file
@ -0,0 +1,3 @@
|
||||
export * from './TreeView';
|
||||
export * from './treeViewClasses';
|
||||
export {};
|
6
TreeView/package.json
Normal file
6
TreeView/package.json
Normal 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
7
TreeView/treeViewClasses.d.ts
vendored
Normal 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;
|
6
TreeView/treeViewClasses.js
Normal file
6
TreeView/treeViewClasses.js
Normal 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
3
index.d.ts
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
export * from './TreeItem';
|
||||
export * from './TreeView';
|
||||
export { unstable_resetCleanupTracking } from './internals/hooks/useInstanceEventHandler';
|
9
index.js
Normal file
9
index.js
Normal 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";
|
38
internals/TreeViewProvider/DescendantProvider.d.ts
vendored
Normal file
38
internals/TreeViewProvider/DescendantProvider.d.ts
vendored
Normal 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 {};
|
189
internals/TreeViewProvider/DescendantProvider.js
Normal file
189
internals/TreeViewProvider/DescendantProvider.js
Normal 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;
|
7
internals/TreeViewProvider/TreeViewContext.d.ts
vendored
Normal file
7
internals/TreeViewProvider/TreeViewContext.d.ts
vendored
Normal 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>>;
|
21
internals/TreeViewProvider/TreeViewContext.js
Normal file
21
internals/TreeViewProvider/TreeViewContext.js
Normal 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';
|
||||
}
|
9
internals/TreeViewProvider/TreeViewProvider.d.ts
vendored
Normal file
9
internals/TreeViewProvider/TreeViewProvider.d.ts
vendored
Normal 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;
|
21
internals/TreeViewProvider/TreeViewProvider.js
Normal file
21
internals/TreeViewProvider/TreeViewProvider.js
Normal 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
|
||||
})
|
||||
});
|
||||
}
|
18
internals/TreeViewProvider/TreeViewProvider.types.d.ts
vendored
Normal file
18
internals/TreeViewProvider/TreeViewProvider.types.d.ts
vendored
Normal 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;
|
||||
}
|
1
internals/TreeViewProvider/TreeViewProvider.types.js
Normal file
1
internals/TreeViewProvider/TreeViewProvider.types.js
Normal file
@ -0,0 +1 @@
|
||||
export {};
|
2
internals/TreeViewProvider/index.d.ts
vendored
Normal file
2
internals/TreeViewProvider/index.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
export { TreeViewProvider } from './TreeViewProvider';
|
||||
export type { TreeViewProviderProps, TreeViewContextValue } from './TreeViewProvider.types';
|
1
internals/TreeViewProvider/index.js
Normal file
1
internals/TreeViewProvider/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { TreeViewProvider } from './TreeViewProvider';
|
3
internals/TreeViewProvider/useTreeViewContext.d.ts
vendored
Normal file
3
internals/TreeViewProvider/useTreeViewContext.d.ts
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
import { TreeViewAnyPluginSignature } from '../models';
|
||||
import { TreeViewContextValue } from './TreeViewProvider.types';
|
||||
export declare const useTreeViewContext: <TPlugins extends readonly TreeViewAnyPluginSignature[]>() => TreeViewContextValue<TPlugins>;
|
3
internals/TreeViewProvider/useTreeViewContext.js
Normal file
3
internals/TreeViewProvider/useTreeViewContext.js
Normal file
@ -0,0 +1,3 @@
|
||||
import * as React from 'react';
|
||||
import { TreeViewContext } from './TreeViewContext';
|
||||
export const useTreeViewContext = () => React.useContext(TreeViewContext);
|
7
internals/corePlugins/corePlugins.d.ts
vendored
Normal file
7
internals/corePlugins/corePlugins.d.ts
vendored
Normal 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>>;
|
6
internals/corePlugins/corePlugins.js
Normal file
6
internals/corePlugins/corePlugins.js
Normal 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
2
internals/corePlugins/index.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
export { TREE_VIEW_CORE_PLUGINS } from './corePlugins';
|
||||
export type { TreeViewCorePluginsSignature } from './corePlugins';
|
1
internals/corePlugins/index.js
Normal file
1
internals/corePlugins/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { TREE_VIEW_CORE_PLUGINS } from './corePlugins';
|
2
internals/corePlugins/useTreeViewInstanceEvents/index.d.ts
vendored
Normal file
2
internals/corePlugins/useTreeViewInstanceEvents/index.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
export { useTreeViewInstanceEvents } from './useTreeViewInstanceEvents';
|
||||
export type { UseTreeViewInstanceEventsSignature } from './useTreeViewInstanceEvents.types';
|
1
internals/corePlugins/useTreeViewInstanceEvents/index.js
Normal file
1
internals/corePlugins/useTreeViewInstanceEvents/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { useTreeViewInstanceEvents } from './useTreeViewInstanceEvents';
|
8
internals/corePlugins/useTreeViewInstanceEvents/useTreeViewInstanceEvents.d.ts
vendored
Normal file
8
internals/corePlugins/useTreeViewInstanceEvents/useTreeViewInstanceEvents.d.ts
vendored
Normal 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>;
|
@ -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
|
||||
});
|
||||
};
|
21
internals/corePlugins/useTreeViewInstanceEvents/useTreeViewInstanceEvents.types.d.ts
vendored
Normal file
21
internals/corePlugins/useTreeViewInstanceEvents/useTreeViewInstanceEvents.types.d.ts
vendored
Normal 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, [
|
||||
]>;
|
@ -0,0 +1 @@
|
||||
export {};
|
15
internals/hooks/useInstanceEventHandler.d.ts
vendored
Normal file
15
internals/hooks/useInstanceEventHandler.d.ts
vendored
Normal 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 {};
|
82
internals/hooks/useInstanceEventHandler.js
Normal file
82
internals/hooks/useInstanceEventHandler.js
Normal 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
9
internals/models/events.d.ts
vendored
Normal 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;
|
||||
};
|
1
internals/models/events.js
Normal file
1
internals/models/events.js
Normal file
@ -0,0 +1 @@
|
||||
export {};
|
13
internals/models/helpers.d.ts
vendored
Normal file
13
internals/models/helpers.d.ts
vendored
Normal 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'>;
|
||||
}
|
1
internals/models/helpers.js
Normal file
1
internals/models/helpers.js
Normal file
@ -0,0 +1 @@
|
||||
export {};
|
3
internals/models/index.d.ts
vendored
Normal file
3
internals/models/index.d.ts
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
export * from './helpers';
|
||||
export * from './plugin';
|
||||
export * from './treeView';
|
3
internals/models/index.js
Normal file
3
internals/models/index.js
Normal file
@ -0,0 +1,3 @@
|
||||
export * from './helpers';
|
||||
export * from './plugin';
|
||||
export * from './treeView';
|
69
internals/models/plugin.d.ts
vendored
Normal file
69
internals/models/plugin.d.ts
vendored
Normal 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 {};
|
1
internals/models/plugin.js
Normal file
1
internals/models/plugin.js
Normal file
@ -0,0 +1 @@
|
||||
export {};
|
23
internals/models/treeView.d.ts
vendored
Normal file
23
internals/models/treeView.d.ts
vendored
Normal 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'>;
|
1
internals/models/treeView.js
Normal file
1
internals/models/treeView.js
Normal file
@ -0,0 +1 @@
|
||||
export {};
|
10
internals/plugins/defaultPlugins.d.ts
vendored
Normal file
10
internals/plugins/defaultPlugins.d.ts
vendored
Normal 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 {
|
||||
}
|
9
internals/plugins/defaultPlugins.js
Normal file
9
internals/plugins/defaultPlugins.js
Normal 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
2
internals/plugins/index.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
export { DEFAULT_TREE_VIEW_PLUGINS } from './defaultPlugins';
|
||||
export type { DefaultTreeViewPlugins } from './defaultPlugins';
|
1
internals/plugins/index.js
Normal file
1
internals/plugins/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { DEFAULT_TREE_VIEW_PLUGINS } from './defaultPlugins';
|
2
internals/plugins/useTreeViewContextValueBuilder/index.d.ts
vendored
Normal file
2
internals/plugins/useTreeViewContextValueBuilder/index.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
export { useTreeViewContextValueBuilder } from './useTreeViewContextValueBuilder';
|
||||
export type { UseTreeViewContextValueBuilderSignature, UseTreeViewContextValueBuilderParameters, UseTreeViewContextValueBuilderDefaultizedParameters, } from './useTreeViewContextValueBuilder.types';
|
@ -0,0 +1 @@
|
||||
export { useTreeViewContextValueBuilder } from './useTreeViewContextValueBuilder';
|
3
internals/plugins/useTreeViewContextValueBuilder/useTreeViewContextValueBuilder.d.ts
vendored
Normal file
3
internals/plugins/useTreeViewContextValueBuilder/useTreeViewContextValueBuilder.d.ts
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
import { TreeViewPlugin } from '../../models';
|
||||
import { UseTreeViewContextValueBuilderSignature } from './useTreeViewContextValueBuilder.types';
|
||||
export declare const useTreeViewContextValueBuilder: TreeViewPlugin<UseTreeViewContextValueBuilderSignature>;
|
@ -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
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
34
internals/plugins/useTreeViewContextValueBuilder/useTreeViewContextValueBuilder.types.d.ts
vendored
Normal file
34
internals/plugins/useTreeViewContextValueBuilder/useTreeViewContextValueBuilder.types.d.ts
vendored
Normal 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>
|
||||
]>;
|
@ -0,0 +1 @@
|
||||
export {};
|
2
internals/plugins/useTreeViewExpansion/index.d.ts
vendored
Normal file
2
internals/plugins/useTreeViewExpansion/index.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
export { useTreeViewExpansion } from './useTreeViewExpansion';
|
||||
export type { UseTreeViewExpansionSignature, UseTreeViewExpansionParameters, UseTreeViewExpansionDefaultizedParameters, } from './useTreeViewExpansion.types';
|
1
internals/plugins/useTreeViewExpansion/index.js
Normal file
1
internals/plugins/useTreeViewExpansion/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { useTreeViewExpansion } from './useTreeViewExpansion';
|
3
internals/plugins/useTreeViewExpansion/useTreeViewExpansion.d.ts
vendored
Normal file
3
internals/plugins/useTreeViewExpansion/useTreeViewExpansion.d.ts
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
import { TreeViewPlugin } from '../../models';
|
||||
import { UseTreeViewExpansionSignature } from './useTreeViewExpansion.types';
|
||||
export declare const useTreeViewExpansion: TreeViewPlugin<UseTreeViewExpansionSignature>;
|
@ -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
|
||||
});
|
||||
};
|
32
internals/plugins/useTreeViewExpansion/useTreeViewExpansion.types.d.ts
vendored
Normal file
32
internals/plugins/useTreeViewExpansion/useTreeViewExpansion.types.d.ts
vendored
Normal 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
|
||||
]>;
|
@ -0,0 +1 @@
|
||||
export {};
|
2
internals/plugins/useTreeViewFocus/index.d.ts
vendored
Normal file
2
internals/plugins/useTreeViewFocus/index.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
export { useTreeViewFocus } from './useTreeViewFocus';
|
||||
export type { UseTreeViewFocusSignature, UseTreeViewFocusParameters, UseTreeViewFocusDefaultizedParameters, } from './useTreeViewFocus.types';
|
1
internals/plugins/useTreeViewFocus/index.js
Normal file
1
internals/plugins/useTreeViewFocus/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { useTreeViewFocus } from './useTreeViewFocus';
|
3
internals/plugins/useTreeViewFocus/useTreeViewFocus.d.ts
vendored
Normal file
3
internals/plugins/useTreeViewFocus/useTreeViewFocus.d.ts
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
import { TreeViewPlugin } from '../../models';
|
||||
import { UseTreeViewFocusSignature } from './useTreeViewFocus.types';
|
||||
export declare const useTreeViewFocus: TreeViewPlugin<UseTreeViewFocusSignature>;
|
89
internals/plugins/useTreeViewFocus/useTreeViewFocus.js
Normal file
89
internals/plugins/useTreeViewFocus/useTreeViewFocus.js
Normal 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
|
||||
});
|
||||
};
|
27
internals/plugins/useTreeViewFocus/useTreeViewFocus.types.d.ts
vendored
Normal file
27
internals/plugins/useTreeViewFocus/useTreeViewFocus.types.d.ts
vendored
Normal 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
|
||||
]>;
|
@ -0,0 +1 @@
|
||||
export {};
|
2
internals/plugins/useTreeViewKeyboardNavigation/index.d.ts
vendored
Normal file
2
internals/plugins/useTreeViewKeyboardNavigation/index.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
export { useTreeViewKeyboardNavigation } from './useTreeViewKeyboardNavigation';
|
||||
export type { UseTreeViewKeyboardNavigationSignature } from './useTreeViewKeyboardNavigation.types';
|
1
internals/plugins/useTreeViewKeyboardNavigation/index.js
Normal file
1
internals/plugins/useTreeViewKeyboardNavigation/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { useTreeViewKeyboardNavigation } from './useTreeViewKeyboardNavigation';
|
3
internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.d.ts
vendored
Normal file
3
internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.d.ts
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
import { TreeViewPlugin } from '../../models';
|
||||
import { UseTreeViewKeyboardNavigationSignature } from './useTreeViewKeyboardNavigation.types';
|
||||
export declare const useTreeViewKeyboardNavigation: TreeViewPlugin<UseTreeViewKeyboardNavigationSignature>;
|
@ -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)
|
||||
})
|
||||
};
|
||||
};
|
14
internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.types.d.ts
vendored
Normal file
14
internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.types.d.ts
vendored
Normal 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
|
||||
]>;
|
@ -0,0 +1 @@
|
||||
export {};
|
2
internals/plugins/useTreeViewNodes/index.d.ts
vendored
Normal file
2
internals/plugins/useTreeViewNodes/index.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
export { useTreeViewNodes } from './useTreeViewNodes';
|
||||
export type { UseTreeViewNodesSignature, UseTreeViewNodesParameters, UseTreeViewNodesDefaultizedParameters, } from './useTreeViewNodes.types';
|
1
internals/plugins/useTreeViewNodes/index.js
Normal file
1
internals/plugins/useTreeViewNodes/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { useTreeViewNodes } from './useTreeViewNodes';
|
3
internals/plugins/useTreeViewNodes/useTreeViewNodes.d.ts
vendored
Normal file
3
internals/plugins/useTreeViewNodes/useTreeViewNodes.d.ts
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
import { TreeViewPlugin } from '../../models';
|
||||
import { UseTreeViewNodesSignature } from './useTreeViewNodes.types';
|
||||
export declare const useTreeViewNodes: TreeViewPlugin<UseTreeViewNodesSignature>;
|
60
internals/plugins/useTreeViewNodes/useTreeViewNodes.js
Normal file
60
internals/plugins/useTreeViewNodes/useTreeViewNodes.js
Normal 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
Loading…
x
Reference in New Issue
Block a user