@momentum-ui/react-collaboration
Version:
Cisco Momentum UI Framework for React Collaboration Applications
178 lines • 9.8 kB
JavaScript
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
import React, { forwardRef, useRef, useCallback, useLayoutEffect, useEffect, useMemo, } from 'react';
import { usePress } from '@react-aria/interactions';
import classnames from 'classnames';
import { verifyTypes } from '../../helpers/verifyTypes';
import { useMutationObservable } from '../../hooks/useMutationObservable';
import ListItemBaseSection from '../ListItemBaseSection';
import { useTreeContext } from '../Tree';
import FocusRing from '../FocusRing';
import Text from '../Text';
import { DEFAULTS, KEYS, NODE_ID_ATTRIBUTE_NAME, SHAPES, SIZES, STYLE, } from './TreeNodeBase.constants';
import './TreeNodeBase.style.scss';
import { getKeyboardFocusableElements } from '../../utils/navigation';
import { usePrevious } from '../../hooks/usePrevious';
import { useDidUpdateEffect } from '../../hooks/useDidUpdateEffect';
var TreeNodeBase = function (props, providedRef) {
var _a;
var id = props.id, className = props.className, nodeId = props.nodeId, children = props.children, _b = props.shape, shape = _b === void 0 ? DEFAULTS.SHAPE : _b, _c = props.size, size = _c === void 0 ? DEFAULTS.SIZE(shape || DEFAULTS.SHAPE) : _c, _d = props.isPadded, isPadded = _d === void 0 ? DEFAULTS.IS_PADDED : _d, style = props.style, onPress = props.onPress, lang = props.lang, rest = __rest(props, ["id", "className", "nodeId", "children", "shape", "size", "isPadded", "style", "onPress", "lang"]);
if (!nodeId) {
console.warn('TreeNodeBase: nodeId prop is required.');
}
var treeContext = useTreeContext();
var nodeDetails = treeContext === null || treeContext === void 0 ? void 0 : treeContext.getNodeDetails(nodeId);
var internalRef = useRef();
var ref = providedRef && typeof providedRef !== 'function' ? providedRef : internalRef;
var isHidden = !nodeDetails || nodeDetails.isHidden;
// When used in a popover, the ref will be a callback.
// We need to update this callback ref, so the popover
// knows about the dom element, but we can't use the callback
// ref directly because we want it to be a useRef style ref
// We useLayoutEffect so that it happens in time for tippy
// to use the ref when adding event handlers
useLayoutEffect(function () {
if (providedRef && typeof providedRef === 'function') {
providedRef(ref.current);
}
});
if (shape === SHAPES.isPilled && (size === SIZES[40] || size === SIZES[70])) {
console.warn('TreeNodeBase: This variation is against the design spec. Rounded Tree Node can only be size 32 or 50.');
}
var content = useMemo(function () {
if (!isHidden && children) {
var childrenContent = children(nodeDetails);
if (Array.isArray(childrenContent)) {
if (childrenContent.length > 3) {
console.warn('TreeNodeBase: This component can only have at most 3 sections inside.');
}
else {
if (verifyTypes(childrenContent, ListItemBaseSection)) {
var start = childrenContent[0], middle = childrenContent[1], end = childrenContent[2];
return (React.createElement(React.Fragment, null,
start,
middle,
end));
}
else if (verifyTypes(childrenContent, Text)) {
return childrenContent;
}
else {
console.warn('TreeNodeBase: When there is more then one child component then use React.Fragment, ListItemBaseSection or Text components.');
}
}
}
else {
return childrenContent;
}
}
return null;
}, [children, isHidden, nodeDetails]);
// The keyboard press events are not propagated
var internalOnPress = useCallback(function (event) {
if (event.pointerType === 'keyboard') {
ref.current.click();
}
if (treeContext &&
treeContext.itemSelection.selectionMode !== 'none' &&
(treeContext.selectableNodes === 'any' || (nodeDetails === null || nodeDetails === void 0 ? void 0 : nodeDetails.isLeaf))) {
treeContext.itemSelection.toggle(nodeId);
}
onPress === null || onPress === void 0 ? void 0 : onPress(event);
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[treeContext, nodeDetails, nodeId, onPress]);
var _e = usePress({
preventFocusOnPress: true, // we handle it ourselves
onPress: internalOnPress,
ref: ref,
}), pressProps = _e.pressProps, isPressed = _e.isPressed;
// Prevent tree node update because it can cause state lost in the focused component e.g. Menu
var treeNodePressProps = __assign(__assign({}, pressProps), { onKeyDown: function (event) {
if (ref.current === document.activeElement || event.key === KEYS.TAB_KEY) {
pressProps.onKeyDown(event);
}
} });
/**
* Focus management
*/
var tabIndex = nodeId === (treeContext === null || treeContext === void 0 ? void 0 : treeContext.activeNodeId) ? 0 : -1;
// makes sure that whenever an item is pressed, the tree focus state gets updated as well
useEffect(function () {
if (!isHidden && treeContext && isPressed && nodeId !== undefined) {
ref.current.focus();
treeContext.setActiveNodeId(nodeId);
treeContext.toggleTreeNode(nodeId);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isPressed, isHidden]);
var updateTabIndexes = useCallback(function () {
if (!isHidden) {
getKeyboardFocusableElements(ref.current, {
includeTabbableOnly: false,
})
.filter(function (el) { return el.closest(".".concat(STYLE.wrapper)) === ref.current; })
.forEach(function (el) { return el.setAttribute('tabindex', tabIndex.toString()); });
}
}, [ref, tabIndex, isHidden]);
var lastActiveNode = usePrevious(treeContext === null || treeContext === void 0 ? void 0 : treeContext.activeNodeId);
useDidUpdateEffect(function () {
if (treeContext &&
ref.current &&
lastActiveNode !== undefined &&
lastActiveNode !== treeContext.activeNodeId &&
treeContext.activeNodeId === nodeId &&
treeContext.isFocusWithin) {
ref.current.focus();
}
}, [treeContext === null || treeContext === void 0 ? void 0 : treeContext.activeNodeId]);
// Update tab indexes of the node's element when the active node changes
useEffect(function () {
if ((treeContext === null || treeContext === void 0 ? void 0 : treeContext.activeNodeId) !== undefined) {
updateTabIndexes();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [treeContext === null || treeContext === void 0 ? void 0 : treeContext.activeNodeId]);
useMutationObservable(ref.current, updateTabIndexes);
if (isHidden) {
return null;
}
var _f = treeContext === null || treeContext === void 0 ? void 0 : treeContext.getNodeAriaProps(nodeId), nodeProps = _f.nodeProps, nodeContentProps = _f.nodeContentProps, groupProps = _f.groupProps;
var isSelected = (treeContext === null || treeContext === void 0 ? void 0 : treeContext.itemSelection.selectionMode) !== 'none'
? treeContext === null || treeContext === void 0 ? void 0 : treeContext.itemSelection.isSelected(nodeId)
: undefined;
return (React.createElement(FocusRing, { isInset: treeContext === null || treeContext === void 0 ? void 0 : treeContext.shouldNodeFocusBeInset },
React.createElement("div", __assign((_a = { tabIndex: tabIndex, id: id, style: style, ref: ref, "aria-selected": isSelected, "data-size": size, "data-padded": isPadded, "data-shape": shape, className: classnames(className, STYLE.wrapper, {
selected: isPressed || isSelected,
'active-node': nodeId === (treeContext === null || treeContext === void 0 ? void 0 : treeContext.activeNodeId),
}), lang: lang }, _a[NODE_ID_ATTRIBUTE_NAME] = nodeId, _a), treeNodePressProps, nodeProps, rest),
React.createElement("div", __assign({ className: STYLE.content }, nodeContentProps), content),
(treeContext === null || treeContext === void 0 ? void 0 : treeContext.isRenderedFlat) && !nodeDetails.isLeaf && (React.createElement("div", __assign({ className: STYLE.group }, groupProps))))));
};
/**
* Tree Node Base component that can be used inside Trees
*/
var _TreeNodeBase = forwardRef(TreeNodeBase);
_TreeNodeBase.displayName = 'TreeNodeBase';
export default _TreeNodeBase;
//# sourceMappingURL=TreeNodeBase.js.map