UNPKG

@fluentui/react-northstar

Version:
241 lines (232 loc) 11.2 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); exports.__esModule = true; exports.useTree = useTree; var _uniq2 = _interopRequireDefault(require("lodash/uniq")); var _invoke2 = _interopRequireDefault(require("lodash/invoke")); var _without2 = _interopRequireDefault(require("lodash/without")); var React = _interopRequireWildcard(require("react")); var _reactBindings = require("@fluentui/react-bindings"); var _flattenTree = require("./flattenTree"); function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } /** * This hook returns a stable `getItemById()` function that will lookup in latest `flatTree`. * This is used to have stable callbacks that can be passed to React's Context. */ function useGetItemById(flatTree) { // An exception is thrown there to ensure that a proper callback will assigned to ref var callbackRef = React.useRef(function () { throw new Error('Callback is not assigned yet'); }); // We are assigning a callback during render as it can be used during render and in event handlers. In dev mode we // are freezing objects to prevent their mutations callbackRef.current = function (itemId) { return process.env.NODE_ENV === 'production' ? flatTree[itemId] : Object.freeze(flatTree[itemId]); }; return React.useCallback(function () { return callbackRef.current.apply(callbackRef, arguments); }, []); } function useStableProps(props) { var stableProps = React.useRef(props); React.useEffect(function () { stableProps.current = props; }); return stableProps; } function useTree(options) { // We need this because we want to handle `expanded` prop on `items`, should be deprecated and removed var deprecated_initialActiveItemIds = React.useMemo(function () { return deprecated_getInitialActiveItemIds(options.items); }, // eslint-disable-next-line react-hooks/exhaustive-deps [] // initialValue only needs to be computed on mount ); var _useAutoControlled = (0, _reactBindings.useAutoControlled)({ defaultValue: options.defaultActiveItemIds, value: options.activeItemIds, initialValue: deprecated_initialActiveItemIds // will become [] }), activeItemIds = _useAutoControlled[0], setActiveItemIdsState = _useAutoControlled[1]; // selectedItemIds is only valid for leaf nodes. // For non-leaf nodes, their 'selected' states are defered from all their descendents var _useAutoControlled2 = (0, _reactBindings.useAutoControlled)({ defaultValue: options.defaultSelectedItemIds, value: options.selectedItemIds, initialValue: [] }), selectedItemIds = _useAutoControlled2[0], setSelectedItemIdsState = _useAutoControlled2[1]; // We want to set `visibleItemIds` to simplify rendering later var _React$useMemo = React.useMemo(function () { return (0, _flattenTree.flattenTree)(options.items, activeItemIds, selectedItemIds); }, [activeItemIds, options.items, selectedItemIds]), flatTree = _React$useMemo.flatTree, visibleItemIds = _React$useMemo.visibleItemIds; var getItemById = useGetItemById(flatTree); var stableProps = useStableProps(options); var toggleItemActive = React.useCallback(function (e, idToToggle) { var item = getItemById(idToToggle); if (!item || !item.hasSubtree) { // leaf node does not have the concept of active/inactive return; } setActiveItemIdsState(function (activeItemIds) { var nextActiveItemIds; var isActiveId = activeItemIds.indexOf(idToToggle) !== -1; if (isActiveId) { nextActiveItemIds = (0, _without2.default)(activeItemIds, idToToggle); } else { nextActiveItemIds = [].concat(activeItemIds, [idToToggle]); if (options.exclusive) { var _getItemById, _getItemById2, _getItemById2$childre; // remove active siblings, if any, from activeItemIds var parent = (_getItemById = getItemById(idToToggle)) == null ? void 0 : _getItemById.parent; var activeSibling = (_getItemById2 = getItemById(parent)) == null ? void 0 : (_getItemById2$childre = _getItemById2.childrenIds) == null ? void 0 : _getItemById2$childre.find(function (id) { return id !== idToToggle && nextActiveItemIds.indexOf(id) >= 0; }); if (activeSibling != null) { nextActiveItemIds = (0, _without2.default)(nextActiveItemIds, activeSibling); } } } (0, _invoke2.default)(stableProps.current, 'onActiveItemIdsChange', e, Object.assign({}, stableProps.current, { activeItemIds: nextActiveItemIds })); return nextActiveItemIds; }); }, [getItemById, options.exclusive, setActiveItemIdsState, stableProps]); var expandSiblings = React.useCallback(function (e, focusedItemId) { if (options.exclusive) { return; } var focusedItem = getItemById(focusedItemId); if (!focusedItem) { return; } var parentItem = getItemById(focusedItem == null ? void 0 : focusedItem.parent); var siblingsIds = parentItem == null ? void 0 : parentItem.childrenIds; if (!siblingsIds) { return; } setActiveItemIdsState(function (activeItemIds) { var nextActiveItemIds = (0, _uniq2.default)(activeItemIds.concat(siblingsIds)); (0, _invoke2.default)(stableProps.current, 'onActiveItemIdsChange', e, Object.assign({}, stableProps.current, { activeItemIds: nextActiveItemIds })); return nextActiveItemIds; }); }, [getItemById, options.exclusive, setActiveItemIdsState, stableProps]); var toggleItemSelect = React.useCallback(function (e, idToToggle) { var item = getItemById(idToToggle); if (!item) { return; } var leafs = getLeafNodes(getItemById, idToToggle); setSelectedItemIdsState(function (selectedItemIds) { var nextSelectedItemIds = item.selected === true ? _without2.default.apply(void 0, [selectedItemIds].concat(leafs)) // remove all leaves from selected : (0, _uniq2.default)(selectedItemIds.concat(leafs)); // add all leaves to selected (0, _invoke2.default)(stableProps.current, 'onSelectedItemIdsChange', e, Object.assign({}, stableProps.current, { selectedItemIds: nextSelectedItemIds })); return nextSelectedItemIds; }); }, [getItemById, setSelectedItemIdsState, stableProps]); // Maintains stable collection of refs to avoid unnecessary React context updates var nodes = React.useRef({}); var registerItemRef = React.useCallback(function (id, node) { nodes.current[id] = node; }, []); var getItemRef = React.useCallback(function (id) { return nodes.current[id]; }, []); // can be used for keyboard navigation === var focusItemById = React.useCallback(function (id) { var itemRef = getItemRef(id); if (itemRef instanceof HTMLElement) { var _getItemById3; if ((_getItemById3 = getItemById(id)) != null && _getItemById3.hasSubtree) { itemRef.focus(); } else { var _itemRef$firstElement; // when node is leaf, need to focus on the inner treeTitle (_itemRef$firstElement = itemRef.firstElementChild) == null ? void 0 : _itemRef$firstElement.focus(); } } }, [getItemById, getItemRef]); var searchByFirstChar = React.useCallback(function (startIndex, endIndex, char) { for (var i = startIndex; i < endIndex; ++i) { var _getItemRef, _getItemRef$textConte, _getItemRef$textConte2, _getItemRef$textConte3; // get first charater of tree node using the same way aria does (https://www.w3.org/TR/wai-aria-practices-1.1/examples/treeview/treeview-2/js/treeitemLinks.js) var itemFirstChar = (_getItemRef = getItemRef(visibleItemIds[i])) == null ? void 0 : (_getItemRef$textConte = _getItemRef.textContent) == null ? void 0 : (_getItemRef$textConte2 = _getItemRef$textConte.trim()) == null ? void 0 : (_getItemRef$textConte3 = _getItemRef$textConte2.charAt(0)) == null ? void 0 : _getItemRef$textConte3.toLowerCase(); if (itemFirstChar === char.toLowerCase()) { return i; } } return -1; }, [getItemRef, visibleItemIds]); var getToFocusIDByFirstCharacter = React.useCallback(function (e, idToStartSearch) { // Get start index for search var starIndex = visibleItemIds.indexOf(idToStartSearch) + 1; if (starIndex === visibleItemIds.length) { starIndex = 0; } // Check following nodes in tree var toFocusIndex = searchByFirstChar(starIndex, visibleItemIds.length, e.key); // If not found in following nodes, check from beginning if (toFocusIndex === -1) { toFocusIndex = searchByFirstChar(0, starIndex - 1, e.key); } if (toFocusIndex === -1) { return idToStartSearch; } return visibleItemIds[toFocusIndex]; }, [searchByFirstChar, visibleItemIds]); return { flatTree: flatTree, getItemById: getItemById, activeItemIds: activeItemIds, visibleItemIds: visibleItemIds, registerItemRef: registerItemRef, getItemRef: getItemRef, toggleItemActive: toggleItemActive, focusItemById: focusItemById, expandSiblings: expandSiblings, toggleItemSelect: toggleItemSelect, getToFocusIDByFirstCharacter: getToFocusIDByFirstCharacter }; } function deprecated_getInitialActiveItemIds(items) { if (!items) { return []; } var result = []; items.forEach(function (item) { if (item.expanded) { result.push(item.id); } if (item.items) { result = result.concat(deprecated_getInitialActiveItemIds(item.items)); } }); return result; } function getLeafNodes(getItemById, rootId) { var leafs = []; var traverseDown = function traverseDown(id) { var _getItemById4; if ((_getItemById4 = getItemById(id)) != null && _getItemById4.childrenIds) { var _getItemById5; (_getItemById5 = getItemById(id)) == null ? void 0 : _getItemById5.childrenIds.forEach(function (child) { traverseDown(child); }); } else { leafs.push(id); } }; traverseDown(rootId); return leafs; } //# sourceMappingURL=useTree.js.map