UNPKG

@sgnl-pro/react-tree

Version:

A tree view component for React

558 lines (477 loc) 17.7 kB
import React, { createContext, useReducer, useMemo, useEffect, useContext } from 'react'; function toVal(mix) { var k, y, str = ''; if (typeof mix === 'string' || typeof mix === 'number') { str += mix; } else if (typeof mix === 'object') { if (Array.isArray(mix)) { for (k = 0; k < mix.length; k++) { if (mix[k]) { if (y = toVal(mix[k])) { str && (str += ' '); str += y; } } } } else { for (k in mix) { if (mix !== null && mix[k]) { str && (str += ' '); str += k; } } } } return str; } function cn () { var i = 0, tmp, x, str = ''; var classesCount = arguments.length; while (i < classesCount) { var _i; if (tmp = (_i = i++, _i < 0 || arguments.length <= _i ? undefined : arguments[_i])) { if (x = toVal(tmp)) { str && (str += ' '); str += x; } } } return str; } var SelectionType; (function (SelectionType) { SelectionType["Child"] = "child"; SelectionType["Parent"] = "parent"; SelectionType["All"] = "all"; SelectionType["None"] = "none"; })(SelectionType || (SelectionType = {})); var noop = function noop() {}; var isSelectableItem = function isSelectableItem(selectionType, isParent) { return selectionType === SelectionType.All || selectionType === SelectionType.Parent && isParent === true || selectionType === SelectionType.Child && isParent === false; }; var TreeEventEmitter = /*#__PURE__*/function () { function TreeEventEmitter(_events) { if (_events === void 0) { _events = Object.create(null); } this._events = _events; } var _proto = TreeEventEmitter.prototype; _proto.on = function on(event, listener) { var _this = this; if (!this._events[event]) { this._events[event] = []; } this._events[event].push(listener); return function () { var _this$_events$event; _this._events[event] = ((_this$_events$event = _this._events[event]) != null ? _this$_events$event : []).filter(function (l) { return l !== listener; }); }; }; _proto.emit = function emit(event) { var _this$_events$event2; for (var _len = arguments.length, listenerArgs = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { listenerArgs[_key - 1] = arguments[_key]; } ((_this$_events$event2 = this._events[event]) != null ? _this$_events$event2 : []).forEach(function (cb) { return cb.apply(void 0, listenerArgs); } // TODO ); }; return TreeEventEmitter; }(); function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } var getInitialTreeState = function getInitialTreeState() { return { expandedIds: Object.create(null), selectedNodes: Object.create(null) }; }; var actions = { toggleExpanded: function toggleExpanded(node) { return { type: 'TOGGLE_EXPANDED', node: node }; }, toggleSelected: function toggleSelected(node, allowMultiple) { return { type: 'TOGGLE_SELECTED', node: node, allowMultiple: allowMultiple }; }, setSelected: function setSelected(nodes, fullUpdate) { return { type: 'SET_SELECTED', nodes: nodes, fullUpdate: fullUpdate }; } }; var treeReducer = function treeReducer(state, action) { switch (action.type) { case 'TOGGLE_EXPANDED': { var _extends2; var nodeId = action.node.id; var expanded = !state.expandedIds[nodeId]; return _extends({}, state, { expandedIds: _extends({}, state.expandedIds, (_extends2 = {}, _extends2[nodeId] = expanded, _extends2)) }); } case 'TOGGLE_SELECTED': { var node = action.node; var selected = state.selectedNodes[node.id] !== undefined; var selectedNodes; if (action.allowMultiple) { var _extends3; selectedNodes = _extends({}, state.selectedNodes, (_extends3 = {}, _extends3[node.id] = selected === true ? undefined : node, _extends3)); } else { var _selectedNodes; selectedNodes = (_selectedNodes = {}, _selectedNodes[node.id] = selected === true ? undefined : node, _selectedNodes); } return _extends({}, state, { selectedNodes: selectedNodes }); } case 'SET_SELECTED': { var nodes = action.nodes, fullUpdate = action.fullUpdate; var _selectedNodes2 = fullUpdate ? {} : _extends({}, state.selectedNodes); nodes.forEach(function (n) { _selectedNodes2[n.id] = n; }); return _extends({}, state, { selectedNodes: _selectedNodes2 }); } default: return state; } }; var treeStateContext = /*#__PURE__*/createContext( /*#__PURE__*/getInitialTreeState()); var treeActionsContext = /*#__PURE__*/createContext({ toggleExpanded: noop, toggleSelected: noop }); var TreeContextProvider = function TreeContextProvider(_ref) { var selectionType = _ref.selectionType, multiSelect = _ref.multiSelect, disabledIds = _ref.disabledIds, eventEmitter = _ref.eventEmitter, onExpand = _ref.onExpand, onSelect = _ref.onSelect, children = _ref.children; var _useReducer = useReducer(treeReducer, getInitialTreeState()), state = _useReducer[0], dispatch = _useReducer[1]; var treeActions = useMemo(function () { return { toggleExpanded: function toggleExpanded(item, isExpanded) { if (item.children === void 0) return; dispatch(actions.toggleExpanded(item)); if (!isExpanded && typeof onExpand === 'function') onExpand(item); }, toggleSelected: function toggleSelected(item) { if (selectionType === SelectionType.None) return; if (!isSelectableItem(selectionType, item.children !== void 0)) return; if (disabledIds != null && disabledIds.includes(item.id)) return; dispatch(actions.toggleSelected(item, multiSelect)); } }; }, [dispatch, onExpand, selectionType, multiSelect, disabledIds]); useEffect(function () { if (typeof onSelect === 'function') { onSelect(Object.values(state.selectedNodes).filter(function (item) { return item !== undefined; })); } }, [state.selectedNodes, onSelect]); useEffect(function () { if (eventEmitter === undefined || eventEmitter instanceof TreeEventEmitter === false) { return; } var selectUnsubscribe = eventEmitter.on('select', function (items, type) { if (type === void 0) { type = 'merge'; } if (selectionType === SelectionType.None) return; var validItems = []; for (var i = 0; i < items.length; i++) { if (multiSelect !== true && validItems.length > 0) break; if (!items[i].id || typeof items[i].label !== 'string') continue; if (!isSelectableItem(selectionType, items[i].children !== void 0)) continue; if (disabledIds != null && disabledIds.includes(items[i].id)) continue; validItems.push(items[i]); } dispatch(actions.setSelected(validItems, type === 'update')); }); return function () { selectUnsubscribe(); }; }, [eventEmitter, selectionType, disabledIds, multiSelect]); return React.createElement(treeActionsContext.Provider, { value: treeActions }, React.createElement(treeStateContext.Provider, { value: state }, children)); }; var useTreeActions = function useTreeActions() { return useContext(treeActionsContext); }; var useTreeState = function useTreeState() { return useContext(treeStateContext); }; function styleInject(css, ref) { if ( ref === void 0 ) ref = {}; var insertAt = ref.insertAt; if (!css || typeof document === 'undefined') { return; } var head = document.head || document.getElementsByTagName('head')[0]; var style = document.createElement('style'); style.type = 'text/css'; if (insertAt === 'top') { if (head.firstChild) { head.insertBefore(style, head.firstChild); } else { head.appendChild(style); } } else { head.appendChild(style); } if (style.styleSheet) { style.styleSheet.cssText = css; } else { style.appendChild(document.createTextNode(css)); } } var css_248z = ".S-Tree-container,.S-Tree-node{position:relative}.S-Tree-container{font-size:1rem}.S-Tree-container,.S-Tree-container *{box-sizing:border-box}.S-Tree-node{margin-left:0;cursor:pointer}.S-Tree-node[disabled]{cursor:not-allowed}.S-Tree-node>.S-Tree-node{margin-left:.5em}.S-Tree-node_selected>.S-Tree-node__content .S-Tree-node__label{font-weight:700}.S-Tree-node__content,.S-Tree-node__label{display:flex;flex-direction:row;justify-content:flex-start}.S-Tree-node__content{width:100%}.S-Tree-node__label{align-items:center;align-self:center;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.S-Tree-node__icon{flex-shrink:0;max-width:20%;overflow:hidden;margin-right:1em}.S-Tree-svg{display:inline-block;width:.5em;height:auto}.S-Tree-svg_expanded{transform:rotate(90deg)}"; styleInject(css_248z); var NodeContent = function NodeContent(_ref) { var className = _ref.className, children = _ref.children, attrs = _objectWithoutPropertiesLoose(_ref, ["className", "children"]); return React.createElement("div", Object.assign({ className: cn('S-Tree-node__content', className) }, attrs), children); }; var NodeLabel = function NodeLabel(_ref) { var className = _ref.className, children = _ref.children, attrs = _objectWithoutPropertiesLoose(_ref, ["className", "children"]); return React.createElement("div", Object.assign({ className: cn('S-Tree-node__label', className) }, attrs), children); }; var CarretRight = function CarretRight(props) { return React.createElement("svg", Object.assign({ width: "8", viewBox: "0 0 8 9", fill: "none", xmlns: "http://www.w3.org/2000/svg" }, props), React.createElement("path", { d: "M1.087 8.77261C0.65721 8.77261 0.227417 8.45477 0.227417 7.84634L0.227417 1.68933C0.227417 0.826625 1.0297 0.581434 1.63141 0.917436L7.23782 3.99594C7.58166 4.17756 7.77267 4.45908 7.77267 4.76784C7.77267 5.07659 7.58166 5.35811 7.24737 5.53973L1.63141 8.61823C1.44994 8.71813 1.26847 8.77261 1.087 8.77261ZM1.19206 1.71658L1.18251 7.81001L6.73162 4.77692L1.19206 1.71658Z", fill: "currentColor" })); }; var NodeIcon = function NodeIcon(_ref) { var isParent = _ref.isParent, expanded = _ref.expanded, iconClassName = _ref.iconClassName, className = _ref.className, children = _ref.children, attrs = _objectWithoutPropertiesLoose(_ref, ["isParent", "expanded", "iconClassName", "className", "children"]); if (!children && !isParent) { return null; } return React.createElement("div", Object.assign({ className: cn('S-Tree-node__icon', className) }, attrs), children || React.createElement(CarretRight, { className: cn('S-Tree-svg', expanded && 'S-Tree-svg_expanded', iconClassName) })); }; var TreeNode = function TreeNode(_ref) { var item = _ref.item, selectionType = _ref.selectionType, selectOn = _ref.selectOn, className = _ref.className, activeClassName = _ref.activeClassName, contentClassName = _ref.contentClassName, iconBoxClassName = _ref.iconBoxClassName, iconClassName = _ref.iconClassName, labelClassName = _ref.labelClassName, renderCheckbox = _ref.renderCheckbox, renderData = _ref.renderData, renderIcon = _ref.renderIcon, loader = _ref.loader, children = _ref.children; var _useTreeActions = useTreeActions(), toggleExpanded = _useTreeActions.toggleExpanded, toggleSelected = _useTreeActions.toggleSelected; var _useTreeState = useTreeState(), expandedIds = _useTreeState.expandedIds, selectedNodes = _useTreeState.selectedNodes; var isParent = item.children !== void 0; var expanded = (expandedIds == null ? void 0 : expandedIds[item.id]) === true; var selected = (selectedNodes == null ? void 0 : selectedNodes[item.id]) !== undefined; var withCheckbox = selectOn === 'check'; var selectable = isSelectableItem(selectionType, isParent); var onNodeClick = function onNodeClick(e) { e.stopPropagation(); if (withCheckbox || selectable === false) { toggleExpanded(item, expanded); } else { toggleSelected(item); } }; var onIconClick = function onIconClick(e) { e.stopPropagation(); toggleExpanded(item, expanded); }; var renderChecker = function renderChecker() { if (withCheckbox && selectable === true) { var onCheck = function onCheck() { toggleSelected(item); }; if (typeof renderCheckbox === 'function') { return renderCheckbox(selected, onCheck); } else { return React.createElement("input", { type: "checkbox", checked: selected, onChange: onCheck, onClick: function onClick(e) { return e.stopPropagation(); } }); } } return null; }; var renderNode = function renderNode(n) { return React.createElement(TreeNode, { key: n.id, item: n, selectionType: selectionType, selectOn: selectOn, className: className, activeClassName: activeClassName, contentClassName: contentClassName, iconBoxClassName: iconBoxClassName, iconClassName: iconClassName, labelClassName: labelClassName, renderCheckbox: renderCheckbox, renderData: renderData, renderIcon: renderIcon, loader: loader }); }; return React.createElement("div", { className: cn('S-Tree-node', className, selected === true && ['S-Tree-node_selected', activeClassName]) }, React.createElement(NodeContent, { className: contentClassName, onClick: onNodeClick }, React.createElement(NodeIcon, { isParent: isParent, expanded: expanded, className: iconBoxClassName, iconClassName: iconClassName, onClick: onIconClick }, typeof renderIcon === 'function' && renderIcon(expanded, selected, isParent, item)), renderChecker(), React.createElement(NodeLabel, { className: labelClassName }, item.label), typeof renderData === 'function' && renderData(item, selected)), children, isParent && expanded && (Array.isArray(item.children) ? item.children.map(renderNode) : loader || React.createElement("div", { className: "S-Tree-info S-Tree-info_loading" }, "..."))); }; var Tree = function Tree(_ref) { var nodes = _ref.nodes, _ref$selectionType = _ref.selectionType, selectionType = _ref$selectionType === void 0 ? SelectionType.None : _ref$selectionType, _ref$multipleSelectio = _ref.multipleSelection, multipleSelection = _ref$multipleSelectio === void 0 ? false : _ref$multipleSelectio, _ref$selectAction = _ref.selectAction, selectAction = _ref$selectAction === void 0 ? 'click' : _ref$selectAction, disabledIds = _ref.disabledIds, containerClassName = _ref.containerClassName, nodeClassName = _ref.nodeClassName, nodeActiveClassName = _ref.nodeActiveClassName, nodeContentClassName = _ref.nodeContentClassName, nodeIconBoxClassName = _ref.nodeIconBoxClassName, nodeIconClassName = _ref.nodeIconClassName, nodeLabelClassName = _ref.nodeLabelClassName, onNodeExpand = _ref.onNodeExpand, onSelect = _ref.onSelect, renderCustomCheckbox = _ref.renderCustomCheckbox, renderNodeData = _ref.renderNodeData, renderNodeIcon = _ref.renderNodeIcon, loader = _ref.loader, noData = _ref.noData, eventEmitter = _ref.eventEmitter, children = _ref.children; var renderNode = function renderNode(n) { return React.createElement(TreeNode, { key: n.id, item: n, selectionType: selectionType, selectOn: selectAction, className: nodeClassName, activeClassName: nodeActiveClassName, contentClassName: nodeContentClassName, iconBoxClassName: nodeIconBoxClassName, iconClassName: nodeIconClassName, labelClassName: nodeLabelClassName, renderCheckbox: renderCustomCheckbox, renderData: renderNodeData, renderIcon: renderNodeIcon, loader: loader }); }; return React.createElement(TreeContextProvider, { selectionType: selectionType, multiSelect: multipleSelection, disabledIds: disabledIds, eventEmitter: eventEmitter, onSelect: onSelect, onExpand: onNodeExpand }, React.createElement("div", { className: cn('S-Tree-container', containerClassName) }, children, !(nodes != null && nodes.length) ? !children && (noData || React.createElement("div", { className: "S-Tree-info S-Tree-info_noData" }, "No data")) : nodes.map(renderNode))); }; export { NodeContent, NodeIcon, NodeLabel, SelectionType, Tree, TreeEventEmitter, TreeNode }; //# sourceMappingURL=react-tree.esm.js.map