omii-ui
Version:
使用OMII开发的WebComponents库,样式采用bootstrap。可以作为标准的esmodule使用
384 lines (361 loc) • 10 kB
JSX
import uiBase from "../uiBase";
import css from "./node.scss";
import sortable from "../sortable";
const { h, classNames, define, getHost, throttle } = omii;
/**
* node {
* key,
* label,
* element,
* expanded
* checked
* allowCheck
* }
*/
class TreeNode extends uiBase {
static css = css;
static defaultProps = {
cssss: null,
nodeLevel: 0,
ignoreAttrs: true,
expander: null,
};
static propTypes = {
nodeLevel: Number,
ignoreAttrs: Boolean,
};
constructor() {
super();
}
get tree() {
return this.$props.tree;
}
get checkboxTree() {
return this.tree.$props.checkboxTree;
}
get radioTree() {
return this.tree.$props.radioTree;
}
get sortable() {
return this.tree.$props.sortable;
}
get selected() {
return this.node.selected ?? this.tree.isSelected(this.key);
}
get checked() {
return this.node.checked ?? this.tree.isChecked(this.key);
}
get radioed() {
return this.node.radioed ?? this.tree.isRadioed(this.key);
}
get expanded() {
return this.node.expanded ?? this.tree.isExpanded(this.key);
}
get expander() {
return (
this.node.expander ?? this.$props.expander ?? this.tree.$props.expander
);
}
get node() {
return this.$props.node ?? {};
}
//children nodes
#nodes;
/**
* children nodes
* 排序时用来确定数组范围
*/
get nodes() {
return this.#nodes;
}
get noChildren() {
return !this.nodes || this.nodes.length == 0;
}
get path() {
const pnode = this.parentTreeNode;
const ppath = pnode ? pnode.path : "";
const myPath = ppath + "/" + this.key;
return myPath;
}
get parentTreeNode() {
const pnode = getHost(this);
return pnode instanceof TreeNode ? pnode : null;
}
get key() {
return this.node.key ?? `node-${this.elementId}`;
}
get level() {
return this.$props.nodeLevel ?? 0;
}
// #isloading = false;
expand() {
this.tree.expand(this.key, false);
this.node.expanded = true;
this.updateSelf();
this.tree.fire("nodeExpand", { node: this.node, treeNode: this });
}
collapse() {
this.tree.collapse(this.key, false);
this.node.expanded = false;
this.updateSelf();
this.tree.fire("nodeCollapse", { node: this.node, treeNode: this });
}
select() {
this.tree.select(this.key, false);
if (this.tree.$props.multiSelect) {
this.updateSelf();
} else {
if (this.tree.__selectedNode) {
this.tree.__selectedNode.updateSelf();
}
this.tree.__selectedNode = this;
this.updateSelf();
}
this.tree.fire("nodeSelect", { node: this.node, treeNode: this });
}
unselect() {
return this.unSelect();
}
unSelect() {
this.tree.unselect(this.key, false);
this.updateSelf();
this.tree.fire("nodeUnSelect", { node: this.node, treeNode: this });
}
check() {
this.tree.check(this.key, false);
this.tree.fire("nodeCheck", { node: this.node, treeNode: this });
}
uncheck() {
this.tree.uncheck(this.key, false);
this.tree.fire("nodeUnCheck", { node: this.node, treeNode: this });
}
radio() {
this.tree.radio(this.key, false);
this.tree.fire("nodeRadio", { node: this.node, treeNode: this });
if (this.tree.__radioedNode) {
this.tree.__radioedNode.updateSelf();
}
this.tree.__radioedNode = this;
}
unradio() {
this.tree.unradio(this.key, false);
this.tree.fire("nodeUnRadio", { node: this.node, treeNode: this });
}
toggle() {
if (this.expanded) this.collapse();
else this.expand();
}
get $checkbox() {
return this.checkboxTree ? (
this.node.allowCheck !== false ? (
<input
type="checkbox"
class="form-check-input"
disabled={this.node.disabled === true}
checked={this.checked}
onClick={(evt) => {
evt.stopPropagation();
}}
onChange={(evt) => {
if (evt.currentTarget.checked) {
this.check();
} else {
this.uncheck();
}
}}
/>
) : (
<div class="checkbox-placeholder" />
)
) : null;
}
get $radio() {
if (this.radioed) this.tree.__radioedNode = this;
return this.radioTree ? (
this.node.allowCheck !== false ? (
<input
type="radio"
disabled={this.node.disabled === true}
checked={this.radioed}
onClick={(evt) => {
evt.stopPropagation();
}}
onChange={(evt) => {
if (evt.currentTarget.checked) {
this.radio();
} else {
this.unradio();
}
}}
/>
) : (
<div class="radio-placeholder" />
)
) : null;
}
install() {}
#Sortable;
get Sortable() {
return this.#Sortable;
}
async installed() {
super.installed();
if (this.sortable) {
const Sortable = await sortable.use();
this.#Sortable = Sortable.create(this.$(".children"), {
delay: 150,
handle: ".element",
// swapThreshold: 0.65,
fallbackOnBody: true,
invertSwap: true,
group: this.tree.sortGroup,
// onAdd: (evt) => {
// // const toHost = getHost(evt.to);
// // evt.item.update$Props(
// // {
// // nodeLevel: toHost.level + 1,
// // },
// // true,
// // true
// // );
// //toHost.updateSelf();
// },
onEnd: (evt) => {
const fromHost = getHost(evt.from);
const toHost = getHost(evt.to);
const fromNodes = fromHost.nodes;
let toNodes = toHost.nodes;
if (!toNodes) {
toNodes = [];
toHost.node.children = toNodes;
}
this.tree.fire("sorted", {
fromNode: fromHost.node,
fromNodes,
toNode: toHost.node,
toNodes,
fromIndex: evt.oldIndex,
toIndex: evt.newIndex,
});
},
});
// const $node = this.$(".node");
// $node.addEventListener(
// "dragover",
// throttle(
// 2000,
// (evt) => {
// $node.classList.add("hover");
// },
// { noLeading: true }
// )
// );
// $node.addEventListener("dragleave", (evt) => {
// $node.classList.remove("hover");
// });
}
}
async render(props) {
//已Node设置的值优先
//每次渲染时,如果Node设置的值与当前值不一致,则更新
if (this.node.selected === true) {
this.tree.select(this.key, false);
}
if (this.node.checked === true) {
this.tree.check(this.key, false);
}
if (this.node.radioed === true) {
this.tree.radio(this.key, false);
}
if (this.node.expanded === true) {
this.tree.expand(this.key, false);
}
if (this.node.selected === false) {
this.tree.unselect(this.key, false);
}
if (this.node.checked === false) {
this.tree.uncheck(this.key, false);
}
if (this.node.radioed === false) {
this.tree.unradio(this.key, false);
}
if (this.node.expanded === false) {
this.tree.collapse(this.key, false);
}
let { node, cssss } = props;
if (!this.tree.$props.multiSelect && this.selected) {
this.tree.__selectedNode = this;
}
let $element = node.element ?? node.label ?? node.key;
if (typeof $element == "function") {
$element = await $element(node, props, this);
}
let $children = null;
let children = (this.#nodes = node.children);
if (typeof children === "function") {
if (this.expanded) {
this.#nodes = children = await children(node, props, this);
}
}
$children = (
<div
class={classNames("children", {
expanded: this.expanded,
sortable: this.sortable,
empty: this.noChildren,
})}
>
{this.expanded &&
(children instanceof Array
? children.map((child) => (
<oi-tree-node
node={child}
node-level={this.level + 1}
tree={this.tree}
cssss={cssss}
/>
))
: children)}
</div>
);
return (
<div
class={classNames("node", {
expanded: this.expanded,
// "loading-children": this.#isloading,
"no-children": this.noChildren,
})}
>
<div
class={classNames("element", { selected: this.selected })}
style={`padding-left:calc(${this.level} * var(--oi-tree-node-indent,1rem))`}
onClick={(evt) => {
// console.log(evt)
evt.stopPropagation();
if (this.selected && evt.ctrlKey === true) this.unselect();
else this.select();
}}
>
<div
onClick={(evt) => {
evt.stopPropagation();
this.toggle();
}}
class={classNames("expander-wrap", {
"no-children": this.noChildren,
})}
>
{!this.noChildren && this.expander}
</div>
{this.$checkbox}
{this.$radio}
<div class="label">{$element}</div>
{/* {this.#isloading ? <oi-loading /> : null} */}
</div>
{$children}
</div>
);
}
}
define("oi-tree-node", TreeNode);
export default TreeNode;