@nutui/nutui-react
Version:
京东风格的轻量级移动端 React 组件库,支持一套代码生成 H5 和小程序
423 lines (422 loc) • 14.9 kB
JavaScript
import { _ as __rest, a as __awaiter } from "./tslib.es6.js";
import React__default, { useState, useImperativeHandle, useEffect, isValidElement } from "react";
import classNames from "classnames";
import { Loading, Checklist } from "@nutui/icons-react";
import { P as Popup } from "./popup2.js";
import { T as Tabs } from "./tabs2.js";
import { C as ComponentDefaults } from "./typings.js";
import { u as usePropsValue } from "./use-props-value.js";
import { u as useConfig } from "./configprovider.taro.js";
const formatTree = (tree, parent, config) => tree.map((node) => {
const { value: valueKey = "value", text: textKey = "text", children: childrenKey = "children" } = config;
const _a = node, _b = valueKey, value = _a[_b], _c = textKey, text = _a[_c], _d = childrenKey, children = _a[_d], others = __rest(_a, [typeof _b === "symbol" ? _b : _b + "", typeof _c === "symbol" ? _c : _c + "", typeof _d === "symbol" ? _d : _d + ""]);
const newNode = Object.assign(Object.assign({ loading: false }, others), {
level: parent ? (parent && parent.level || 0) + 1 : 0,
value,
text,
children,
_parent: parent
});
if (newNode.children && newNode.children.length) {
newNode.children = formatTree(newNode.children, newNode, config);
}
return newNode;
});
const eachTree = (tree, cb) => {
let i = 0;
let node;
while (node = tree[i++]) {
if (cb(node) === true) {
break;
}
if (node.children && node.children.length) {
eachTree(node.children, cb);
}
}
};
const defaultConvertConfig = {
topId: null,
idKey: "id",
pidKey: "pid",
sortKey: ""
};
const convertListToOptions = (list, options) => {
const mergedOptions = Object.assign(Object.assign({}, defaultConvertConfig), options || {});
const { topId, idKey, pidKey, sortKey } = mergedOptions;
let result = [];
let map = {};
list.forEach((node) => {
node = Object.assign({}, node);
const { [idKey]: id, [pidKey]: pid } = node;
const children = map[pid] = map[pid] || [];
if (!result.length && pid === topId) {
result = children;
}
children.push(node);
node.children = map[id] || (map[id] = []);
});
if (sortKey) {
Object.keys(map).forEach((i) => {
if (map[i].length > 1) {
map[i].sort((a, b) => a[sortKey] - b[sortKey]);
}
});
}
map = null;
return result;
};
class Tree {
constructor(nodes, config) {
this.isLeaf = (node, lazy) => {
const { leaf, children } = node;
const hasChildren = Array.isArray(children) && Boolean(children.length);
return leaf || !hasChildren && !lazy;
};
this.hasChildren = (node, lazy) => {
const isLeaf = this.isLeaf(node, lazy);
if (isLeaf) {
return false;
}
const { children } = node;
return Array.isArray(children) && Boolean(children.length);
};
this.config = Object.assign({ value: "value", text: "text", children: "children" }, config || {});
this.nodes = formatTree(nodes, null, this.config);
}
updateChildren(nodes, parent) {
if (!parent) {
this.nodes = formatTree(nodes, null, this.config);
} else {
parent.children = formatTree(nodes, parent, this.config);
}
}
// for test
getNodeByValue(value) {
let foundNode;
eachTree(this.nodes, (node) => {
if (node.value === value) {
foundNode = node;
return true;
}
return null;
});
return foundNode;
}
getPathNodesByValue(value) {
if (!value.length) {
return [];
}
const pathNodes = [];
let currentNodes = this.nodes;
while (currentNodes && currentNodes.length) {
const foundNode = currentNodes.find((node) => node.value === value[node.level]);
if (!foundNode) {
break;
}
pathNodes.push(foundNode);
currentNodes = foundNode.children;
}
return pathNodes;
}
}
const defaultProps = Object.assign(Object.assign({}, ComponentDefaults), { activeColor: "", activeIcon: "checklist", popup: true, options: [], optionKey: { textKey: "text", valueKey: "value", childrenKey: "children" }, format: {}, closeable: false, closeIconPosition: "top-right", closeIcon: "close", lazy: false, onLoad: () => {
}, onClose: () => {
}, onChange: () => {
}, onPathChange: () => {
} });
const InternalCascader = (props, ref) => {
const { locale } = useConfig();
const { className, style, activeColor, activeIcon, popup, popupProps = {}, visible, options, value, defaultValue, optionKey, format, closeable, closeIconPosition, closeIcon, lazy, title, left, onLoad, onClose, onChange, onPathChange } = Object.assign(Object.assign({}, defaultProps), props);
const [tabvalue, setTabvalue] = useState("c1");
const [optionsData, setOptionsData] = useState([]);
const isLazy = () => state.configs.lazy && Boolean(state.configs.onLoad);
const [innerValue, setInnerValue] = usePropsValue({
value,
defaultValue,
finalValue: defaultValue
});
const [innerVisible, setInnerVisible] = usePropsValue({
value: visible,
defaultValue: void 0,
finalValue: false
});
const actions = {
open: () => {
setInnerVisible(true);
},
close: () => {
setInnerVisible(false);
}
};
useImperativeHandle(ref, () => actions);
const [state] = useState({
optionsData: [],
panes: [
{
nodes: [],
selectedNode: [],
paneKey: ""
}
],
tree: new Tree([], {}),
tabsCursor: 0,
// 选中的tab项
initLoading: false,
currentProcessNode: [],
configs: {
lazy,
onLoad,
optionKey,
format
},
lazyLoadMap: /* @__PURE__ */ new Map()
});
const classPrefix = classNames(`nut-cascader`);
const classesPane = classNames({
[`${classPrefix}-pane`]: true
});
useEffect(() => {
initData();
}, [options, format]);
useEffect(() => {
syncValue();
}, [value]);
const initData = () => __awaiter(void 0, void 0, void 0, function* () {
state.lazyLoadMap.clear();
if (format && Object.keys(format).length > 0) {
state.optionsData = convertListToOptions(options, format);
} else {
state.optionsData = options;
}
state.tree = new Tree(state.optionsData, {
value: state.configs.optionKey.valueKey,
text: state.configs.optionKey.textKey,
children: state.configs.optionKey.childrenKey
});
if (isLazy() && !state.tree.nodes.length) {
yield invokeLazyLoad({
root: true,
loading: true,
text: "",
value: ""
});
}
state.panes = [
{
nodes: state.tree.nodes,
selectedNode: null,
paneKey: "c1"
}
];
syncValue();
setOptionsData(state.panes);
});
const syncValue = () => __awaiter(void 0, void 0, void 0, function* () {
const currentValue = innerValue;
if (currentValue === void 0 || ![defaultValue, value].includes(currentValue) || !state.tree.nodes.length) {
return;
}
if (currentValue.length === 0) {
state.tabsCursor = 0;
return;
}
let needToSync = currentValue;
if (isLazy() && Array.isArray(currentValue) && currentValue.length) {
needToSync = [];
const parent = state.tree.nodes.find((node) => node.value === currentValue[0]);
if (parent) {
needToSync = [parent.value];
state.initLoading = true;
const last = yield currentValue.slice(1).reduce((p, value2) => __awaiter(void 0, void 0, void 0, function* () {
var _a;
const parent2 = yield p;
yield invokeLazyLoad(parent2);
const node = (_a = parent2 === null || parent2 === void 0 ? void 0 : parent2.children) === null || _a === void 0 ? void 0 : _a.find((item) => item.value === value2);
if (node) {
needToSync.push(value2);
}
return Promise.resolve(node);
}), Promise.resolve(parent));
yield invokeLazyLoad(last);
state.initLoading = false;
}
}
if (needToSync.length && [defaultValue, value].includes(currentValue)) {
const pathNodes = state.tree.getPathNodesByValue(needToSync);
pathNodes.forEach((node, index) => {
state.tabsCursor = index;
chooseItem(node, true);
});
}
});
const invokeLazyLoad = (node) => __awaiter(void 0, void 0, void 0, function* () {
if (!node) {
return;
}
if (!state.configs.onLoad) {
node.leaf = true;
return;
}
if (state.tree.isLeaf(node, isLazy()) || state.tree.hasChildren(node, isLazy())) {
return;
}
node.loading = true;
const parent = node.root ? null : node;
let lazyLoadPromise = state.lazyLoadMap.get(node);
if (!lazyLoadPromise) {
lazyLoadPromise = new Promise((resolve) => {
var _a, _b;
(_b = (_a = state.configs).onLoad) === null || _b === void 0 ? void 0 : _b.call(_a, node, resolve);
});
state.lazyLoadMap.set(node, lazyLoadPromise);
}
const nodes = yield lazyLoadPromise;
if (Array.isArray(nodes) && nodes.length > 0) {
state.tree.updateChildren(nodes, parent);
} else {
node.leaf = true;
}
node.loading = false;
state.lazyLoadMap.delete(node);
});
const close = () => {
setInnerVisible(false);
onClose && onClose();
};
const closePopup = () => {
close();
};
const chooseItem = (node, type) => __awaiter(void 0, void 0, void 0, function* () {
if (!type && node.disabled || !state.panes[state.tabsCursor]) {
return;
}
if (state.tree.isLeaf(node, isLazy())) {
node.leaf = true;
state.panes[state.tabsCursor].selectedNode = node;
state.panes = state.panes.slice(0, node.level + 1);
if (!type) {
const pathNodes = state.panes.map((item) => item.selectedNode);
const optionParams = pathNodes.map((item) => item.value);
onChange(optionParams, pathNodes);
onPathChange === null || onPathChange === void 0 ? void 0 : onPathChange(optionParams, pathNodes);
setInnerValue(optionParams);
}
setOptionsData(state.panes);
close();
return;
}
if (state.tree.hasChildren(node, isLazy())) {
const level = node.level + 1;
state.panes[state.tabsCursor].selectedNode = node;
state.panes = state.panes.slice(0, level);
state.tabsCursor = level;
state.panes.push({
nodes: node.children || [],
selectedNode: null,
paneKey: `c${state.tabsCursor + 1}`
});
setOptionsData(state.panes);
setTabvalue(`c${state.tabsCursor + 1}`);
if (!type) {
const pathNodes = state.panes.map((item) => item.selectedNode);
const optionParams = pathNodes.map((item) => item === null || item === void 0 ? void 0 : item.value);
onPathChange === null || onPathChange === void 0 ? void 0 : onPathChange(optionParams, pathNodes);
}
return;
}
state.currentProcessNode = node;
if (node.loading) {
return;
}
yield invokeLazyLoad(node);
if (state.currentProcessNode === node) {
state.panes[state.tabsCursor].selectedNode = node;
chooseItem(node, type);
}
setOptionsData(state.panes);
});
const renderItem = (pane, node, index) => {
var _a;
const classPrefix2 = "nut-cascader-item";
const checked = ((_a = pane.selectedNode) === null || _a === void 0 ? void 0 : _a.value) === node.value;
const classes = classNames({
active: checked,
disabled: node.disabled
}, classPrefix2);
const classesTitle = classNames({
[`${classPrefix2}-title`]: true
});
const renderIcon = () => {
if (checked) {
if (isValidElement(activeIcon)) {
return activeIcon;
}
return React__default.createElement(Checklist, { className: `${checked ? `${classPrefix}-icon-check` : ""}` });
}
return null;
};
return React__default.createElement(
"div",
{ style: { color: checked ? activeColor : "" }, className: classes, key: index, onClick: () => {
chooseItem(node, false);
} },
React__default.createElement("div", { className: classesTitle }, node.text),
node.loading ? React__default.createElement(Loading, { color: "#969799", className: "nut-cascader-item-icon-loading" }) : renderIcon()
);
};
const renderTabs = () => {
return React__default.createElement(
"div",
{ className: `${classPrefix} ${className}`, style },
React__default.createElement(Tabs, { value: tabvalue, title: () => {
return optionsData.map((pane, index) => {
var _a, _b;
return React__default.createElement(
"div",
{ onClick: () => {
setTabvalue(pane.paneKey);
state.tabsCursor = index;
}, className: `nut-tabs-titles-item ${tabvalue === pane.paneKey ? "nut-tabs-titles-item-active" : ""}`, key: pane.paneKey },
React__default.createElement(
"span",
{ className: "nut-tabs-titles-item-text" },
!state.initLoading && state.panes.length && ((_a = pane === null || pane === void 0 ? void 0 : pane.selectedNode) === null || _a === void 0 ? void 0 : _a.text),
!state.initLoading && state.panes.length && !((_b = pane === null || pane === void 0 ? void 0 : pane.selectedNode) === null || _b === void 0 ? void 0 : _b.text) && `${locale.select}`,
!(!state.initLoading && state.panes.length) && "Loading..."
),
React__default.createElement("span", { className: "nut-tabs-titles-item-line" })
);
});
} }, !state.initLoading && state.panes.length ? optionsData.map((pane) => {
var _a;
return React__default.createElement(
Tabs.TabPane,
{ key: pane.paneKey, value: pane.paneKey },
React__default.createElement("div", { className: classesPane }, (_a = pane.nodes) === null || _a === void 0 ? void 0 : _a.map((node, index) => renderItem(pane, node, index)))
);
}) : React__default.createElement(
Tabs.TabPane,
null,
React__default.createElement("div", { className: classesPane })
))
);
};
return React__default.createElement(React__default.Fragment, null, popup ? React__default.createElement(Popup, Object.assign({}, popupProps, {
visible: innerVisible,
position: "bottom",
round: true,
closeIcon,
closeable,
closeIconPosition,
title: popup && title,
left,
// todo 只关闭,不处理逻辑。和popup的逻辑不一致。关闭时需要增加是否要处理回调
onOverlayClick: closePopup,
onCloseIconClick: closePopup
}), renderTabs()) : renderTabs());
};
const Cascader = React__default.forwardRef(InternalCascader);
Cascader.displayName = "NutCascader";
export {
Cascader as C
};