import React from "react"; import PropTypes from "prop-types"; import classNames from "classnames"; import { Checkbox, Icon } from "@alifd/next"; import CascaderMenu from "./CascaderMenu"; import { AddressTree, AddressNodeSet, AddressNode, NodeIDStrict, } from "./Types"; import { nodeChainFromNode, getParentSetOf, isDescendantOfOne } from "./Common"; enum CheckboxState { Checked, Indeterminate, Unchecked, } interface AddressEntryProps { node: AddressNode; value: CheckboxState; onChangeCheckbox: (checked: boolean, node: AddressNode) => void; onMenuClick: (checkedNow: boolean, node: AddressNode) => void; active: boolean; level: number; } class AddressEntry extends React.Component { static propTypes = { node: PropTypes.object, value: PropTypes.number, onChangeCheckbox: PropTypes.func, onMenuClick: PropTypes.func, active: PropTypes.bool, level: PropTypes.number, }; constructor(props: AddressEntryProps) { super(props); this.handleChangeCheckbox = this.handleChangeCheckbox.bind(this); this.handleMenuClick = this.handleMenuClick.bind(this); this.handleChangeHotarea = this.handleChangeHotarea.bind(this); } isChecked(props = this.props) { return props.value == CheckboxState.Checked; } handleChangeCheckbox(checked: boolean) { this.props.onChangeCheckbox && this.props.onChangeCheckbox(checked, this.props.node); } handleChangeHotarea() { return this.handleChangeCheckbox(this.isChecked()); } handleMenuClick() { this.props.onMenuClick && this.props.onMenuClick(this.isChecked(), this.props.node); } showArrow(props = this.props) { if (props.node.isLeaf) { return false; } if ( props.node.children && props.node.children.length && props.node.children[0].level > props.level ) { return false; } if ( props.level < 4 && (!props.node.children || !props.node.children.length) ) { return false; } return true; } render() { const { node, value, onChangeCheckbox, onMenuClick, active, level, ...rest } = this.props; return ( {/*
*/} {node.nameZh} {!this.showArrow() ? null : ( )} ); } } interface AddressCascaderProps { addressTree: AddressTree; allNodesSet: AddressNodeSet; expandedValue: NodeIDStrict; onExpand: (expandedValue: NodeIDStrict) => void; value: NodeIDStrict[]; onChange: (value: NodeIDStrict[]) => void; rootNodeId: NodeIDStrict; level: number; hiddenMap: { [id: string]: any }; } interface AddressCascaderState {} export default class AddressCascader extends React.Component< AddressCascaderProps, AddressCascaderState > { static propTypes = { addressTree: PropTypes.object, allNodesSet: PropTypes.object, expandedValue: PropTypes.string, onExpand: PropTypes.func, onLoadTown: PropTypes.func, value: PropTypes.arrayOf(PropTypes.string), onChange: PropTypes.func, rootNodeId: PropTypes.string, level: PropTypes.number, hiddenMap: PropTypes.object, }; static defaultProps = { // @ts-ignore value: [], }; valueSet: Set = new Set(); allParentSet: Set = new Set(); activeChainSet: Set = new Set(); constructor(props: AddressCascaderProps) { super(props); this.renderMenu = this.renderMenu.bind(this); this.handleMenuClick = this.handleMenuClick.bind(this); this.handleChangeCheckbox = this.handleChangeCheckbox.bind(this); } handleMenuClick(checkedNode: boolean, node: AddressNode) { this.props.onExpand(node.id); } handleChangeCheckbox(checked: boolean, node: AddressNode) { let changed = false; if (!checked && this.valueSet.has(node.id)) { this.valueSet.delete(node.id); changed = true; } else if (checked && !this.valueSet.has(node.id)) { this.valueSet.add(node.id); changed = true; } else if (!checked && !this.valueSet.has(node.id)) { let nodeToDestruct: AddressNode = this.props.allNodesSet[node.id.toString()]; while (true) { if (this.valueSet.has(nodeToDestruct.id)) { break; } if (nodeToDestruct.parentNode) { nodeToDestruct = nodeToDestruct.parentNode; } else { return; } } const parentSet = getParentSetOf(node); this.valueSet.delete(nodeToDestruct.id); if (nodeToDestruct.children && node.parentNode) { const filterAndAdd = (child: AddressNode) => { if (!parentSet.has(child.id)) { this.valueSet.add(child.id); } }; let nodeChain = nodeChainFromNode(node); nodeChain = nodeChain.slice(nodeChain.indexOf(nodeToDestruct)); nodeChain.pop(); for (const currentNode of nodeChain) { currentNode.children!.forEach(filterAndAdd); } changed = true; } } changed && this.props.onChange && this.props.onChange(Array.from(this.valueSet)); } renderMenu( node: AddressNode, zIndex: number, valueSet: Set, allParentSet: Set, activeChainSet: Set ) { if (node.isLeaf) { return null; } return ( {node.children!.map((child) => { if (this.props.hiddenMap[child.id.toString()]) { return null; } let c = CheckboxState.Unchecked; if ( valueSet.has(child.id) || isDescendantOfOne( this.props.allNodesSet, child.id, this.props.value ) ) { c = CheckboxState.Checked; } else if (allParentSet.has(child.id)) { c = CheckboxState.Indeterminate; } return ( ); })} ); } render() { let nodeChain = nodeChainFromNode( this.props.allNodesSet[this.props.expandedValue.toString()] ); const rootPos = nodeChain.findIndex((n) => n.id == this.props.rootNodeId); if (rootPos != -1) { nodeChain = nodeChain.slice(rootPos); } this.valueSet = new Set(); this.props.value.forEach((v) => this.valueSet.add(v)); this.allParentSet = new Set(); this.props.value.forEach((v) => { const parentNodeChain = nodeChainFromNode( this.props.allNodesSet[v.toString()] ); parentNodeChain.forEach((node) => this.allParentSet.add(node.id)); }); this.activeChainSet = new Set(); nodeChain.forEach((v) => this.activeChainSet.add(v.id)); const menus = nodeChain .filter( (node) => node.level < this.props.level && !( this.props.level < 4 && (node.isLeaf || !node.children || !node.children.length) ) ) .map((node, idx) => this.renderMenu( node, nodeChain.length - idx + 12, this.valueSet, this.allParentSet, this.activeChainSet ) ); return {menus}; } }