UNPKG

@momentum-ui/react-collaboration

Version:

Cisco Momentum UI Framework for React Collaboration Applications

178 lines 9.8 kB
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