@itwin/itwinui-react
Version:
A react component library for iTwinUI
290 lines (289 loc) • 9.11 kB
JavaScript
;
Object.defineProperty(exports, '__esModule', {
value: true,
});
Object.defineProperty(exports, 'Tree', {
enumerable: true,
get: function () {
return Tree;
},
});
const _interop_require_wildcard = require('@swc/helpers/_/_interop_require_wildcard');
const _react = /*#__PURE__*/ _interop_require_wildcard._(require('react'));
const _index = require('../../utils/index.js');
const _TreeContext = require('./TreeContext.js');
const Tree = (props) => {
let {
data,
className,
nodeRenderer,
getNode,
size = 'default',
enableVirtualization = false,
style,
...rest
} = props;
let treeRef = _react.useRef(null);
let focusedIndex = _react.useRef(0);
_react.useEffect(() => {
focusedIndex.current = 0;
}, [data]);
let getFocusableNodes = _react.useCallback(() => {
let focusableItems = (0, _index.getFocusableElements)(treeRef.current);
return focusableItems.filter(
(i) => !focusableItems.some((p) => p.contains(i.parentElement)),
);
}, []);
let handleKeyDown = (event) => {
if (event.altKey) return;
let items = getFocusableNodes();
if (!items?.length) return;
let activeIndex = items.findIndex((el) =>
el.contains(treeRef.current?.ownerDocument.activeElement),
);
let currentIndex = activeIndex > -1 ? activeIndex : 0;
switch (event.key) {
case 'ArrowUp': {
event.preventDefault();
let newIndex = Math.max(0, currentIndex - 1);
items[newIndex].focus();
focusedIndex.current = newIndex;
break;
}
case 'ArrowDown': {
event.preventDefault();
let newIndex = Math.min(items.length - 1, currentIndex + 1);
items[newIndex].focus();
focusedIndex.current = newIndex;
break;
}
default:
break;
}
};
let [flatNodesList, firstLevelNodesList] = _react.useMemo(() => {
let flatList = [];
let firstLevelNodes = [];
let flattenNodes = (nodes = [], depth = 0, parentNode) => {
let nodeIdList = Array();
nodes.forEach((element, index) => {
let { subNodes, ...nodeProps } = getNode(element);
let flatNode = {
nodeProps,
depth,
parentNode,
indexInGroup: index,
};
nodeIdList.push(flatNode.nodeProps.nodeId);
flatList.push(flatNode);
if (0 === depth) firstLevelNodes.push(flatNode);
if (flatNode.nodeProps.isExpanded) {
let subNodeIds = flattenNodes(subNodes, depth + 1, flatNode);
flatNode.subNodeIds = subNodeIds;
}
});
return nodeIdList;
};
flattenNodes(data);
return [flatList, firstLevelNodes];
}, [data, getNode]);
let itemRenderer = _react.useCallback(
(index, virtualItem, virtualizer) => {
let node = flatNodesList[index];
return _react.createElement(
_TreeContext.TreeContext.Provider,
{
key: node.nodeProps.nodeId,
value: {
nodeDepth: node.depth,
subNodeIds: node.subNodeIds,
groupSize:
0 === node.depth
? firstLevelNodesList.length
: node.parentNode?.subNodeIds?.length ?? 0,
indexInGroup: node.indexInGroup,
parentNodeId: node.parentNode?.nodeProps.nodeId,
scrollToParent: node.parentNode
? () => {
let parentNodeId = node.parentNode?.nodeProps.nodeId;
let parentNodeIndex = flatNodesList.findIndex(
(n) => n.nodeProps.nodeId === parentNodeId,
);
setScrollToIndex(parentNodeIndex);
}
: void 0,
size,
},
},
virtualItem && virtualizer
? (0, _index.cloneElementWithRef)(
nodeRenderer(node.nodeProps),
(children) => ({
...children.props,
key: virtualItem.key,
'data-iui-index': virtualItem.index,
'data-iui-virtualizer': 'item',
ref: virtualizer.measureElement,
style: {
...children.props.style,
'--_iui-width': '100%',
transform: `translateY(${virtualItem.start}px)`,
},
}),
)
: nodeRenderer(node.nodeProps),
);
},
[firstLevelNodesList.length, flatNodesList, nodeRenderer, size],
);
let [scrollToIndex, setScrollToIndex] = _react.useState();
let flatNodesListRef = _react.useRef(flatNodesList);
_react.useEffect(() => {
flatNodesListRef.current = flatNodesList;
}, [flatNodesList]);
_react.useEffect(() => {
setTimeout(() => {
if (void 0 !== scrollToIndex) {
let nodeId = flatNodesListRef.current[scrollToIndex].nodeProps.nodeId;
let nodeElement = treeRef.current?.ownerDocument.querySelector(
`#${nodeId}`,
);
nodeElement?.focus();
setScrollToIndex(void 0);
}
});
}, [scrollToIndex]);
let handleFocus = (event) => {
if (treeRef.current?.contains(event.relatedTarget)) return;
let items = getFocusableNodes();
if (items.length > 0) items[focusedIndex.current]?.focus();
};
return _react.createElement(
_react.Fragment,
null,
enableVirtualization
? _react.createElement(VirtualizedTree, {
flatNodesList: flatNodesList,
itemRenderer: itemRenderer,
scrollToIndex: scrollToIndex,
onFocus: handleFocus,
onKeyDown: handleKeyDown,
ref: treeRef,
className: className,
'data-iui-size': 'small' === size ? 'small' : void 0,
style: style,
...rest,
})
: _react.createElement(
TreeElement,
{
onKeyDown: handleKeyDown,
onFocus: handleFocus,
className: className,
'data-iui-size': 'small' === size ? 'small' : void 0,
style: style,
ref: treeRef,
...rest,
},
flatNodesList.map((_, i) => itemRenderer(i)),
),
);
};
if ('development' === process.env.NODE_ENV) Tree.displayName = 'Tree';
const TreeElement = _index.polymorphic.div('iui-tree', {
role: 'tree',
tabIndex: 0,
});
const VirtualizedTree = _react.forwardRef(
({ flatNodesList, itemRenderer, scrollToIndex, ...rest }, ref) => {
let parentRef = _react.useRef(null);
let virtualizerRootRef = _react.useRef(null);
let getItemKey = _react.useCallback(
(index) => flatNodesList[index].nodeProps.nodeId,
[flatNodesList],
);
let onVirtualizerChange = _react.useMemo(
() =>
debounce((virtualizer) => {
if (!virtualizer || !virtualizerRootRef.current) return;
virtualizerRootRef.current.style.width = '';
let widestNodeWidth = 0;
virtualizer.elementsCache.forEach((el) => {
if (el.clientWidth > widestNodeWidth)
widestNodeWidth = el.clientWidth;
});
if (widestNodeWidth)
virtualizerRootRef.current.style.width = `${widestNodeWidth}px`;
}, 100),
[],
);
let { virtualizer, css: virtualizerCss } = (0, _index.useVirtualScroll)({
count: flatNodesList.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 39,
getItemKey,
onChange: onVirtualizerChange,
});
(0, _index.useLayoutEffect)(() => {
if (scrollToIndex) virtualizer.scrollToIndex(scrollToIndex);
}, [virtualizer, scrollToIndex]);
return _react.createElement(
TreeElement,
{
...rest,
ref: (0, _index.useMergedRefs)(ref, parentRef),
},
_react.createElement(
'div',
{
style: {
display: 'contents',
},
},
_react.createElement(
_index.ShadowRoot,
{
css: virtualizerCss,
},
_react.createElement(
'div',
{
'data-iui-virtualizer': 'root',
style: {
minBlockSize: virtualizer.getTotalSize(),
},
ref: virtualizerRootRef,
},
_react.createElement('slot', null),
),
),
_react.createElement(
_TreeContext.VirtualizedTreeContext.Provider,
{
value: _react.useMemo(
() => ({
virtualizer,
onVirtualizerChange,
}),
[virtualizer, onVirtualizerChange],
),
},
virtualizer
.getVirtualItems()
.map((virtualItem) =>
itemRenderer(virtualItem.index, virtualItem, virtualizer),
),
),
),
);
},
);
function debounce(callback, delay) {
let timeoutId;
return (...args) => {
if (timeoutId) window.clearTimeout(timeoutId);
timeoutId = window.setTimeout(() => {
callback(...args);
}, delay);
};
}