| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079 |
- import { computed, ref, shallowRef, toRaw, watch } from "vue";
- import { CHECK_STATUS_MAP, DefaultTreeProps } from "./constants";
- import type {
- CheckStatus,
- TreeCheckChangePayload,
- TreeDataItem,
- TreeKey,
- TreeModelValue,
- TreeNode,
- TreeProps,
- UniTreeViewProps
- } from "./types";
- export type TreeViewStateProps = Pick<
- UniTreeViewProps,
- | "data"
- | "treeProps"
- | "filterValue"
- | "filterMethod"
- | "modelValue"
- | "defaultCheckedKeys"
- | "multiple"
- | "checkStrictly"
- | "accordion"
- | "onlyRadioLeaf"
- | "defaultExpandAll"
- | "defaultExpandedKeys"
- | "defaultExpandParent"
- | "expandChecked"
- | "cacheExpandedKeys"
- | "loadMode"
- | "loadApi"
- | "isLeafFn"
- | "alwaysFirstLoad"
- | "checkedDisabled"
- | "packDisabledKey"
- | "packDisabledkey"
- >;
- export function useTreeViewState(props: TreeViewStateProps) {
- const treeList = ref<TreeNode[]>([]);
- const visibleTreeList = ref<TreeNode[]>([]);
- const matchedTreeList = ref<TreeNode[]>([]);
- const treeVersion = ref(0);
- const reconciledModelValue = shallowRef<{ value: TreeModelValue } | null>(null);
- const pendingCheckChangePayload = shallowRef<TreeCheckChangePayload | null>(null);
- const childrenMap = ref<Map<TreeKey, TreeNode[]>>(new Map());
- const nodeMap = ref<Map<TreeKey, TreeNode>>(new Map());
- const cachedExpandedKeys = ref<Set<TreeKey>>(new Set());
- let pendingCheckedKeys = new Set<TreeKey>();
- let pendingImperativeCheckedKeys = new Set<TreeKey>();
- let warnedInvalidKeys = new Set<string>();
- let initialized = false;
- const resolvedTreeProps = computed<TreeProps>(() => {
- return {
- id: props.treeProps?.id ?? DefaultTreeProps.id,
- label: props.treeProps?.label ?? DefaultTreeProps.label,
- children: props.treeProps?.children ?? DefaultTreeProps.children,
- disabled: props.treeProps?.disabled ?? DefaultTreeProps.disabled,
- leaf: props.treeProps?.leaf ?? DefaultTreeProps.leaf,
- append: props.treeProps?.append ?? DefaultTreeProps.append,
- icon: props.treeProps?.icon ?? DefaultTreeProps.icon
- };
- });
- const isMultiple = computed(() => Boolean(props.multiple));
- const resolvedPackDisabledKey = computed(() => {
- return props.packDisabledKey ?? props.packDisabledkey ?? true;
- });
- watch(
- () => [
- props.data,
- getTreeConfigSignature()
- ] as const,
- () => {
- initializeTree(toRaw(props.data ?? []));
- },
- {
- immediate: true
- }
- );
- watch(
- () => getExpansionConfigSignature(),
- () => {
- applyExpandedState();
- }
- );
- watch(
- () => [getCheckedValueSignature(), getCheckedBehaviorSignature()] as const,
- ([, behaviorSignature], [, previousBehaviorSignature]) => {
- const checkedKeys = getInitialCheckedKeys();
- if (behaviorSignature === previousBehaviorSignature && isConfiguredCheckedStateCurrent(checkedKeys)) {
- return;
- }
- applyConfiguredCheckedState(checkedKeys);
- }
- );
- watch(
- () => [props.filterValue, props.filterMethod] as const,
- () => {
- updateVisibility();
- }
- );
- function initializeTree(treeData: TreeDataItem[] = []) {
- const wasInitialized = initialized;
- const preserveRuntimeChecked = initialized && props.modelValue === undefined;
- const checkedKeys = props.modelValue !== undefined
- ? getInitialCheckedKeys()
- : preserveRuntimeChecked
- ? isMultiple.value
- ? [...getRawCheckedKeys(), ...pendingCheckedKeys]
- : [...pendingImperativeCheckedKeys, ...pendingCheckedKeys, ...getRawCheckedKeys()]
- : getInitialCheckedKeys();
- const pendingKeys = preserveRuntimeChecked ? [...pendingCheckedKeys] : checkedKeys;
- const imperativeKeys = [...pendingImperativeCheckedKeys];
- syncCachedExpandedKeys();
- childrenMap.value = new Map();
- nodeMap.value = new Map();
- warnedInvalidKeys = new Set();
- treeList.value = flattenTree(treeData);
- treeVersion.value += 1;
- initialized = true;
- applyCheckedState(checkedKeys);
- pendingCheckedKeys = new Set(pendingKeys.filter((key) => !nodeMap.value.has(key)));
- const resolvedImperativeKeys = imperativeKeys.filter((key) => nodeMap.value.has(key));
- pendingImperativeCheckedKeys = new Set(
- imperativeKeys.filter((key) => pendingCheckedKeys.has(key))
- );
- applyExpandedState();
- publishPendingSelectionChange(resolvedImperativeKeys);
- reconcileMissingControlledKeys(wasInitialized, checkedKeys);
- }
- function toggleExpand(node: TreeNode) {
- if (!isExpandable(node)) {
- return null;
- }
- const nextExpanded = !node.expanded;
- if (nextExpanded && props.accordion) {
- const siblings = node.parentId === undefined
- ? treeList.value.filter((item) => item.level === 0)
- : childrenMap.value.get(node.parentId) ?? [];
- for (const sibling of siblings) {
- if (sibling !== node && sibling.expanded) {
- sibling.expanded = false;
- syncExpandedCacheForNode(sibling);
- }
- }
- }
- node.expanded = nextExpanded;
- if (props.cacheExpandedKeys) {
- if (node.expanded) {
- cachedExpandedKeys.value.add(node.id);
- } else {
- cachedExpandedKeys.value.delete(node.id);
- }
- }
- updateVisibility();
- return {
- expanded: node.expanded,
- node
- };
- }
- function checkNode(node: TreeNode) {
- if (!canSelectNode(node)) {
- return null;
- }
- if (isMultiple.value) {
- const newStatus = node.checked === CHECK_STATUS_MAP.checked
- ? CHECK_STATUS_MAP.unchecked
- : CHECK_STATUS_MAP.checked;
- if (props.checkStrictly) {
- node.checked = newStatus;
- } else {
- updateNodeAndDescendantsStatus(node.id, newStatus);
- updateParentNodesStatus(node.id);
- }
- } else {
- const newStatus = node.checked === CHECK_STATUS_MAP.checked
- ? CHECK_STATUS_MAP.unchecked
- : CHECK_STATUS_MAP.checked;
- clearCheckedStatus();
- if (newStatus === CHECK_STATUS_MAP.checked) {
- node.checked = CHECK_STATUS_MAP.checked;
- }
- }
- return buildCheckChangePayload(node);
- }
- function flattenTree(
- list: TreeDataItem[] = [],
- level = 0,
- parentIds: TreeKey[] = [],
- parents: TreeDataItem[] = []
- ) {
- const nodes: TreeNode[] = [];
- const {
- id: idKey,
- label: labelKey,
- children: childrenKey,
- disabled: disabledKey = "disabled",
- append: appendKey = "append",
- icon: iconKey = "icon"
- } = resolvedTreeProps.value;
- list.forEach((item) => {
- const id = item[idKey] as TreeKey;
- const children = item[childrenKey];
- const label = String(item[labelKey] ?? "");
- warnAboutInvalidKey(id, label);
- const treeNode: TreeNode = {
- id,
- label,
- append: String(item[appendKey] ?? ""),
- icon: String(item[iconKey] ?? ""),
- path: [...parents.map((parent) => String(parent[labelKey] ?? "")), label],
- source: item,
- parentId: parentIds[parentIds.length - 1],
- parentIds,
- parents,
- level,
- expanded: false,
- visible: level === 0,
- disabled: Boolean(item[disabledKey]),
- checked: CHECK_STATUS_MAP.unchecked,
- isLeaf: false,
- loaded: false,
- loading: false,
- loadError: null
- };
- treeNode.isLeaf = resolveIsLeaf(item, treeNode);
- treeNode.loaded = treeNode.isLeaf || !props.loadMode || (Array.isArray(children) && children.length > 0 && !props.alwaysFirstLoad);
- nodes.push(treeNode);
- if (id !== undefined && id !== null && nodeMap.value.has(id)) {
- warnOnce(
- `duplicate:${String(id)}`,
- `[uni-tree-view] 检测到重复节点 key:${String(id)}。请确保 tree-props.id 映射的值在整棵树中唯一。`
- );
- }
- nodeMap.value.set(id, treeNode);
- const parentId = parentIds.slice(-1)[0];
- if (parentId !== undefined) {
- if (!childrenMap.value.has(parentId)) {
- childrenMap.value.set(parentId, []);
- }
- childrenMap.value.get(parentId)!.push(treeNode);
- }
- if (Array.isArray(children) && children.length > 0) {
- nodes.push(...flattenTree(children, level + 1, [...parentIds, id], [...parents, item]));
- }
- });
- return nodes;
- }
- function applyCheckedState(keys: TreeKey[]) {
- clearCheckedStatus();
- if (keys.length === 0) {
- return;
- }
- if (isMultiple.value) {
- if (props.checkStrictly) {
- for (const key of keys) {
- const node = nodeMap.value.get(key);
- if (node && canSelectDisabledNode(node)) {
- node.checked = CHECK_STATUS_MAP.checked;
- }
- }
- return;
- }
- updateNodeAndDescendantsStatus(keys, CHECK_STATUS_MAP.checked, Boolean(props.checkedDisabled));
- updateParentNodesStatus(keys);
- return;
- }
- const firstSelectableKey = keys.find((key) => {
- const node = nodeMap.value.get(key);
- return node && canSelectDisabledNode(node) && (!props.onlyRadioLeaf || node.isLeaf);
- });
- if (firstSelectableKey !== undefined) {
- const node = nodeMap.value.get(firstSelectableKey);
- if (node) {
- node.checked = CHECK_STATUS_MAP.checked;
- }
- }
- }
- function applyConfiguredCheckedState(keys: TreeKey[]) {
- applyCheckedState(keys);
- pendingCheckedKeys = new Set(keys.filter((key) => !nodeMap.value.has(key)));
- pendingImperativeCheckedKeys = new Set();
- }
- function applyPendingCheckedState() {
- const resolvedKeys = [...pendingCheckedKeys].filter((key) => nodeMap.value.has(key));
- if (resolvedKeys.length === 0) {
- return;
- }
- const resolvedImperativeKeys = resolvedKeys.filter((key) => pendingImperativeCheckedKeys.has(key));
- for (const key of resolvedKeys) {
- pendingCheckedKeys.delete(key);
- pendingImperativeCheckedKeys.delete(key);
- }
- if (isMultiple.value) {
- if (props.checkStrictly) {
- for (const key of resolvedKeys) {
- const node = nodeMap.value.get(key);
- if (node && canSelectDisabledNode(node)) {
- node.checked = CHECK_STATUS_MAP.checked;
- }
- }
- } else {
- updateNodeAndDescendantsStatus(resolvedKeys, CHECK_STATUS_MAP.checked, Boolean(props.checkedDisabled));
- updateParentNodesStatus(resolvedKeys);
- }
- publishPendingSelectionChange(resolvedImperativeKeys);
- return;
- }
- const node = resolvedKeys
- .map((key) => nodeMap.value.get(key))
- .find((item): item is TreeNode => item !== undefined && canSelectNode(item));
- if (node) {
- clearCheckedStatus();
- node.checked = CHECK_STATUS_MAP.checked;
- }
- publishPendingSelectionChange(resolvedImperativeKeys);
- }
- function applyExpandedState() {
- const defaultExpandedKeys = normalizeKeys(props.defaultExpandedKeys);
- const expandedKeySet = new Set<TreeKey>(defaultExpandedKeys);
- if (props.defaultExpandParent !== false) {
- for (const key of defaultExpandedKeys) {
- const node = nodeMap.value.get(key);
- if (!node) {
- continue;
- }
- for (const parentId of node.parentIds) {
- expandedKeySet.add(parentId);
- }
- }
- }
- for (const node of treeList.value) {
- node.expanded = Boolean(props.defaultExpandAll)
- || expandedKeySet.has(node.id)
- || (Boolean(props.cacheExpandedKeys) && cachedExpandedKeys.value.has(node.id));
- }
- applyExpandCheckedState();
- updateVisibility();
- }
- function applyExpandCheckedState() {
- if (!props.expandChecked) {
- return;
- }
- for (const node of getCheckedNodes()) {
- for (const parentId of node.parentIds) {
- const parent = nodeMap.value.get(parentId);
- if (parent) {
- parent.expanded = true;
- }
- }
- }
- }
- function updateNodeAndDescendantsStatus(
- targetIds: TreeKey | TreeKey[],
- newStatus: Exclude<CheckStatus, "indeterminate">,
- includeDisabled = false
- ) {
- const pendingIds = [...(Array.isArray(targetIds) ? targetIds : [targetIds])];
- while (pendingIds.length > 0) {
- const targetId = pendingIds.pop();
- if (targetId === undefined) {
- continue;
- }
- const node = nodeMap.value.get(targetId);
- if (!node || (node.disabled && !includeDisabled && !props.checkedDisabled)) {
- continue;
- }
- node.checked = newStatus;
- const children = childrenMap.value.get(targetId);
- if (children && children.length > 0) {
- for (const child of children) {
- pendingIds.push(child.id);
- }
- }
- }
- }
- function hasChildren(nodeId: TreeKey) {
- const children = childrenMap.value.get(nodeId);
- return Array.isArray(children) && children.length > 0;
- }
- function isExpandable(node: TreeNode) {
- return !node.isLeaf && (hasChildren(node.id) || Boolean(props.loadMode));
- }
- function updateParentNodesStatus(targetIds?: TreeKey | TreeKey[]) {
- if (props.checkStrictly || !isMultiple.value) {
- return;
- }
- if (targetIds === undefined) {
- for (let index = treeList.value.length - 1; index >= 0; index -= 1) {
- updateNodeFromChildren(treeList.value[index]);
- }
- return;
- }
- const affectedNodes = new Map<TreeKey, TreeNode>();
- const ids = Array.isArray(targetIds) ? targetIds : [targetIds];
- for (const id of ids) {
- const node = nodeMap.value.get(id);
- if (!node) {
- continue;
- }
- if (hasChildren(node.id)) {
- affectedNodes.set(node.id, node);
- }
- for (const parentId of node.parentIds) {
- const parent = nodeMap.value.get(parentId);
- if (parent) {
- affectedNodes.set(parent.id, parent);
- }
- }
- }
- [...affectedNodes.values()]
- .sort((a, b) => b.level - a.level)
- .forEach(updateNodeFromChildren);
- }
- function updateNodeFromChildren(node: TreeNode) {
- const children = childrenMap.value.get(node.id);
- if (!children?.length) {
- return;
- }
- const allChecked = children.every((child) => child.checked === CHECK_STATUS_MAP.checked);
- const allUnchecked = children.every((child) => child.checked === CHECK_STATUS_MAP.unchecked);
- if (allChecked) {
- node.checked = CHECK_STATUS_MAP.checked;
- } else if (allUnchecked) {
- node.checked = CHECK_STATUS_MAP.unchecked;
- } else {
- node.checked = CHECK_STATUS_MAP.indeterminate;
- }
- }
- function updateVisibility() {
- const rawFilterValue = String(props.filterValue ?? "").trim();
- const normalizedFilterValue = rawFilterValue.toLowerCase();
- const visibleNodes: TreeNode[] = [];
- if (rawFilterValue) {
- const visibleKeySet = new Set<TreeKey>();
- const matchedNodes: TreeNode[] = [];
- for (const node of treeList.value) {
- const matched = props.filterMethod
- ? props.filterMethod(rawFilterValue, node)
- : node.label.toLowerCase().includes(normalizedFilterValue);
- if (!matched) {
- continue;
- }
- matchedNodes.push(node);
- visibleKeySet.add(node.id);
- for (const parentId of node.parentIds) {
- visibleKeySet.add(parentId);
- }
- addDescendantVisibleKeys(node.id, visibleKeySet);
- }
- for (const node of treeList.value) {
- node.visible = visibleKeySet.has(node.id);
- if (node.visible) {
- visibleNodes.push(node);
- }
- }
- visibleTreeList.value = visibleNodes;
- matchedTreeList.value = matchedNodes;
- return buildFilterPayload(visibleNodes, matchedNodes);
- }
- matchedTreeList.value = [];
- // treeList is pre-order flattened, so parent visibility is already resolved here.
- for (const node of treeList.value) {
- const parent = node.parentId === undefined ? undefined : nodeMap.value.get(node.parentId);
- node.visible = node.level === 0 || Boolean(parent?.visible && parent.expanded);
- if (node.visible) {
- visibleNodes.push(node);
- }
- }
- visibleTreeList.value = visibleNodes;
- return buildFilterPayload(visibleNodes);
- }
- function buildFilterPayload(
- nodes = visibleTreeList.value,
- matchedNodes = matchedTreeList.value
- ) {
- return {
- value: String(props.filterValue ?? ""),
- keys: nodes.map((node) => node.id),
- nodes,
- matchedKeys: matchedNodes.map((node) => node.id),
- matchedNodes
- };
- }
- function syncCachedExpandedKeys() {
- if (!props.cacheExpandedKeys) {
- return;
- }
- for (const node of treeList.value) {
- if (node.expanded) {
- cachedExpandedKeys.value.add(node.id);
- } else {
- cachedExpandedKeys.value.delete(node.id);
- }
- }
- }
- function canSelectNode(node: TreeNode) {
- if (!canSelectDisabledNode(node)) {
- return false;
- }
- if (!isMultiple.value && props.onlyRadioLeaf && !node.isLeaf) {
- return false;
- }
- return true;
- }
- function canSelectDisabledNode(node: TreeNode) {
- return !node.disabled || Boolean(props.checkedDisabled);
- }
- function clearCheckedStatus() {
- for (const node of treeList.value) {
- if (!canSelectDisabledNode(node)) {
- continue;
- }
- node.checked = CHECK_STATUS_MAP.unchecked;
- }
- }
- function getRawCheckedKeys() {
- return treeList.value
- .filter((node) => node.checked === CHECK_STATUS_MAP.checked)
- .map((node) => node.id);
- }
- function normalizeKeys(value: TreeKey | TreeKey[] | null | undefined): TreeKey[] {
- if (value === null || value === undefined) {
- return [];
- }
- return Array.isArray(value) ? value : [value];
- }
- function getTreeConfigSignature() {
- return JSON.stringify({
- treeProps: resolvedTreeProps.value,
- loadMode: props.loadMode,
- alwaysFirstLoad: props.alwaysFirstLoad
- });
- }
- function getExpansionConfigSignature() {
- return JSON.stringify({
- defaultExpandAll: props.defaultExpandAll,
- defaultExpandedKeys: normalizeKeys(props.defaultExpandedKeys),
- defaultExpandParent: props.defaultExpandParent,
- expandChecked: props.expandChecked,
- cacheExpandedKeys: props.cacheExpandedKeys
- });
- }
- function getCheckedValueSignature() {
- return JSON.stringify({
- controlled: props.modelValue !== undefined,
- value: props.modelValue !== undefined ? props.modelValue : props.defaultCheckedKeys
- });
- }
- function getCheckedBehaviorSignature() {
- return JSON.stringify({
- multiple: props.multiple,
- checkStrictly: props.checkStrictly,
- onlyRadioLeaf: props.onlyRadioLeaf,
- checkedDisabled: props.checkedDisabled,
- packDisabledKey: resolvedPackDisabledKey.value
- });
- }
- function isConfiguredCheckedStateCurrent(keys: TreeKey[]) {
- const expectedKeySet = new Set(keys);
- const currentKeySet = new Set([...getCheckedKeys(), ...pendingCheckedKeys]);
- if (expectedKeySet.size !== currentKeySet.size) {
- return false;
- }
- return [...expectedKeySet].every((key) => currentKeySet.has(key));
- }
- function getInitialCheckedKeys() {
- if (props.modelValue !== undefined) {
- return normalizeKeys(props.modelValue);
- }
- return normalizeKeys(props.defaultCheckedKeys);
- }
- function getSelectionIconClass(node: TreeNode) {
- if (isMultiple.value) {
- if (node.checked === CHECK_STATUS_MAP.checked) {
- return "utv-tree-checkbox-checked";
- }
- if (node.checked === CHECK_STATUS_MAP.indeterminate) {
- return "utv-tree-checkbox-indeterminate";
- }
- return "utv-tree-checkbox-outline";
- }
- if (node.checked === CHECK_STATUS_MAP.checked) {
- return "utv-tree-radio-checked";
- }
- return "utv-tree-radio-outline";
- }
- function getCheckedKeys() {
- return getCheckedNodes().map((node) => node.id);
- }
- function getHalfCheckedKeys() {
- return getHalfCheckedNodes().map((node) => node.id);
- }
- function getUncheckedKeys() {
- return getUncheckedNodes().map((node) => node.id);
- }
- function getCheckedNodes() {
- return treeList.value.filter((node) => {
- if (node.checked !== CHECK_STATUS_MAP.checked) {
- return false;
- }
- return resolvedPackDisabledKey.value || !node.disabled;
- });
- }
- function getHalfCheckedNodes() {
- return treeList.value.filter((node) => node.checked === CHECK_STATUS_MAP.indeterminate);
- }
- function getUncheckedNodes() {
- return treeList.value.filter((node) => node.checked === CHECK_STATUS_MAP.unchecked);
- }
- function getExpandedKeys() {
- return getExpandedNodes().map((node) => node.id);
- }
- function getUnexpandedKeys() {
- return getUnexpandedNodes().map((node) => node.id);
- }
- function getVisibleKeys() {
- return getVisibleNodes().map((node) => node.id);
- }
- function getMatchedKeys() {
- return getMatchedNodes().map((node) => node.id);
- }
- function getExpandedNodes() {
- return treeList.value.filter((node) => !node.isLeaf && node.expanded);
- }
- function getUnexpandedNodes() {
- return treeList.value.filter((node) => !node.isLeaf && !node.expanded);
- }
- function getVisibleNodes() {
- return visibleTreeList.value;
- }
- function getMatchedNodes() {
- return matchedTreeList.value;
- }
- function getNode(key: TreeKey) {
- return nodeMap.value.get(key);
- }
- function getNodePath(keyOrNode: TreeKey | TreeNode) {
- const targetNode = typeof keyOrNode === "object" ? keyOrNode : nodeMap.value.get(keyOrNode);
- if (!targetNode) {
- return [];
- }
- return [...targetNode.parentIds, targetNode.id]
- .map((key) => nodeMap.value.get(key))
- .filter((node): node is TreeNode => Boolean(node));
- }
- function buildCheckChangePayload(node: TreeNode): TreeCheckChangePayload {
- const nodes = getCheckedNodes();
- const keys = nodes.map((checkedNode) => checkedNode.id);
- return {
- value: isMultiple.value ? keys : (keys[0] ?? null),
- keys,
- nodes,
- halfCheckedKeys: getHalfCheckedKeys(),
- halfCheckedNodes: getHalfCheckedNodes(),
- node
- };
- }
- function publishPendingSelectionChange(keys: TreeKey[]) {
- const changedNode = keys
- .map((key) => nodeMap.value.get(key))
- .find((node): node is TreeNode => node !== undefined && canSelectNode(node));
- if (changedNode) {
- pendingCheckChangePayload.value = buildCheckChangePayload(changedNode);
- }
- }
- function reconcileMissingControlledKeys(wasInitialized: boolean, configuredKeys: TreeKey[]) {
- if (!wasInitialized || props.modelValue === undefined || props.loadMode) {
- return;
- }
- if (!configuredKeys.some((key) => !nodeMap.value.has(key))) {
- return;
- }
- const keys = getCheckedKeys();
- reconciledModelValue.value = {
- value: isMultiple.value ? keys : (keys[0] ?? null)
- };
- }
- function warnAboutInvalidKey(id: TreeKey, label: string) {
- if (id !== undefined && id !== null) {
- return;
- }
- warnOnce(
- "missing",
- `[uni-tree-view] 检测到缺失节点 key。请检查 tree-props.id 映射;问题节点 label:${label || "(空)"}`
- );
- }
- function warnOnce(key: string, message: string) {
- if (!(import.meta as ImportMeta & { env?: { DEV?: boolean } }).env?.DEV || warnedInvalidKeys.has(key)) {
- return;
- }
- warnedInvalidKeys.add(key);
- console.warn(message);
- }
- function setCheckedKeys(keys: TreeKey | TreeKey[], checked = true) {
- const normalizedKeys = normalizeKeys(keys);
- if (!checked) {
- for (const key of normalizedKeys) {
- pendingCheckedKeys.delete(key);
- pendingImperativeCheckedKeys.delete(key);
- }
- const changedNode = normalizedKeys
- .map((key) => nodeMap.value.get(key))
- .find((node): node is TreeNode => node !== undefined && canSelectNode(node));
- if (!changedNode) {
- return null;
- }
- if (isMultiple.value) {
- if (props.checkStrictly) {
- for (const key of normalizedKeys) {
- const node = nodeMap.value.get(key);
- if (node && canSelectDisabledNode(node)) {
- node.checked = CHECK_STATUS_MAP.unchecked;
- }
- }
- } else {
- updateNodeAndDescendantsStatus(normalizedKeys, CHECK_STATUS_MAP.unchecked, Boolean(props.checkedDisabled));
- updateParentNodesStatus(normalizedKeys);
- }
- } else {
- for (const key of normalizedKeys) {
- const node = nodeMap.value.get(key);
- if (node && canSelectNode(node)) {
- node.checked = CHECK_STATUS_MAP.unchecked;
- }
- }
- }
- return buildCheckChangePayload(changedNode);
- }
- if (!isMultiple.value) {
- const targetKey = normalizedKeys.find((key) => {
- const node = nodeMap.value.get(key);
- return node === undefined || canSelectNode(node);
- });
- if (targetKey === undefined) {
- return null;
- }
- const targetNode = nodeMap.value.get(targetKey);
- pendingCheckedKeys = new Set();
- pendingImperativeCheckedKeys = new Set();
- if (!targetNode) {
- pendingCheckedKeys.add(targetKey);
- pendingImperativeCheckedKeys.add(targetKey);
- return null;
- }
- clearCheckedStatus();
- targetNode.checked = CHECK_STATUS_MAP.checked;
- return buildCheckChangePayload(targetNode);
- }
- const unresolvedKeys = normalizedKeys.filter((key) => !nodeMap.value.has(key));
- for (const key of unresolvedKeys) {
- pendingCheckedKeys.add(key);
- pendingImperativeCheckedKeys.add(key);
- }
- const changedNode = normalizedKeys
- .map((key) => nodeMap.value.get(key))
- .find((node): node is TreeNode => node !== undefined && canSelectNode(node));
- if (!changedNode) {
- return null;
- }
- if (props.checkStrictly) {
- for (const key of normalizedKeys) {
- const node = nodeMap.value.get(key);
- if (node && canSelectDisabledNode(node)) {
- node.checked = CHECK_STATUS_MAP.checked;
- }
- }
- } else {
- updateNodeAndDescendantsStatus(normalizedKeys, CHECK_STATUS_MAP.checked, Boolean(props.checkedDisabled));
- updateParentNodesStatus(normalizedKeys);
- }
- return buildCheckChangePayload(changedNode);
- }
- function setExpandedKeys(keys: TreeKey[] | "all", expanded = true) {
- if (keys === "all") {
- for (const node of treeList.value) {
- if (!node.isLeaf) {
- node.expanded = expanded;
- syncExpandedCacheForNode(node);
- }
- }
- updateVisibility();
- return;
- }
- for (const key of keys) {
- const node = nodeMap.value.get(key);
- if (node && !node.isLeaf) {
- node.expanded = expanded;
- syncExpandedCacheForNode(node);
- }
- }
- updateVisibility();
- }
- function syncExpandedCacheForNode(node: TreeNode) {
- if (!props.cacheExpandedKeys) {
- return;
- }
- if (node.expanded) {
- cachedExpandedKeys.value.add(node.id);
- } else {
- cachedExpandedKeys.value.delete(node.id);
- }
- }
- function expandAll() {
- setExpandedKeys("all", true);
- }
- function collapseAll() {
- setExpandedKeys("all", false);
- }
- function resolveIsLeaf(item: TreeDataItem, node: TreeNode) {
- if (props.isLeafFn) {
- return props.isLeafFn(item, node);
- }
- const { children: childrenKey, leaf: leafKey = "leaf" } = resolvedTreeProps.value;
- const children = item[childrenKey];
- if (props.loadMode && item[leafKey] !== undefined) {
- return Boolean(item[leafKey]);
- }
- if (props.loadMode) {
- return false;
- }
- return !(Array.isArray(children) && children.length > 0);
- }
- function addDescendantVisibleKeys(nodeId: TreeKey, visibleKeySet: Set<TreeKey>) {
- const pendingNodes = [...(childrenMap.value.get(nodeId) ?? [])];
- while (pendingNodes.length > 0) {
- const child = pendingNodes.pop();
- if (!child) {
- continue;
- }
- visibleKeySet.add(child.id);
- pendingNodes.push(...(childrenMap.value.get(child.id) ?? []));
- }
- }
- function removeDescendants(nodeId: TreeKey) {
- const children = childrenMap.value.get(nodeId) ?? [];
- const descendantIds = new Set<TreeKey>();
- for (const child of children) {
- collectDescendantIds(child.id, descendantIds);
- }
- if (descendantIds.size === 0) {
- return;
- }
- treeList.value = treeList.value.filter((node) => !descendantIds.has(node.id));
- for (const id of descendantIds) {
- nodeMap.value.delete(id);
- childrenMap.value.delete(id);
- }
- childrenMap.value.delete(nodeId);
- }
- function collectDescendantIds(nodeId: TreeKey, ids: Set<TreeKey>) {
- ids.add(nodeId);
- const children = childrenMap.value.get(nodeId);
- if (!children?.length) {
- return;
- }
- for (const child of children) {
- collectDescendantIds(child.id, ids);
- }
- }
- function replaceNodeChildren(node: TreeNode, children: TreeDataItem[]) {
- const shouldInheritChecked = isMultiple.value
- && !props.checkStrictly
- && node.checked === CHECK_STATUS_MAP.checked;
- removeDescendants(node.id);
- const childNodes = flattenTree(children, node.level + 1, [...node.parentIds, node.id], [...node.parents, node.source]);
- treeVersion.value += 1;
- const nodeIndex = treeList.value.findIndex((item) => item.id === node.id);
- if (nodeIndex === -1) {
- treeList.value.push(...childNodes);
- } else {
- treeList.value.splice(nodeIndex + 1, 0, ...childNodes);
- }
- node.isLeaf = children.length === 0 && Boolean(props.loadMode);
- node.loaded = true;
- if (shouldInheritChecked) {
- updateNodeAndDescendantsStatus(childNodes.map((child) => child.id), CHECK_STATUS_MAP.checked);
- }
- applyPendingCheckedState();
- updateParentNodesStatus(node.id);
- updateVisibility();
- }
- async function loadNode(node: TreeNode) {
- if (!props.loadMode || !props.loadApi || node.isLeaf || node.loading || node.loaded) {
- return [];
- }
- node.loading = true;
- node.loadError = null;
- try {
- const children = await props.loadApi(node);
- const normalizedChildren = Array.isArray(children) ? children : [];
- if (nodeMap.value.get(node.id) !== node) {
- return normalizedChildren;
- }
- replaceNodeChildren(node, normalizedChildren);
- return normalizedChildren;
- } catch (error) {
- if (nodeMap.value.get(node.id) !== node) {
- return [];
- }
- node.loadError = error;
- throw error;
- } finally {
- node.loading = false;
- }
- }
- return {
- treeList,
- childrenMap,
- nodeMap,
- resolvedTreeProps,
- isMultiple,
- visibleTreeList,
- matchedTreeList,
- treeVersion,
- reconciledModelValue,
- pendingCheckChangePayload,
- initializeTree,
- toggleExpand,
- checkNode,
- applyCheckedState,
- applyExpandedState,
- hasChildren,
- isExpandable,
- getSelectionIconClass,
- setCheckedKeys,
- getCheckedKeys,
- getHalfCheckedKeys,
- getUncheckedKeys,
- getCheckedNodes,
- getHalfCheckedNodes,
- getUncheckedNodes,
- setExpandedKeys,
- getExpandedKeys,
- getUnexpandedKeys,
- getVisibleKeys,
- getMatchedKeys,
- getExpandedNodes,
- getUnexpandedNodes,
- getVisibleNodes,
- getMatchedNodes,
- getNode,
- getNodePath,
- expandAll,
- collapseAll,
- loadNode
- };
- }
|