diff --git a/js/date-picker/utils.ts b/js/date-picker/utils.ts index 72c61bc4dc..20c04f3553 100644 --- a/js/date-picker/utils.ts +++ b/js/date-picker/utils.ts @@ -418,7 +418,7 @@ export function flagActive(data: any[], { ...args }: FlagActiveOptions) { const _item = item; if (multiple) { - _item.active = (value as DateValue[]).some((val) => isSame(dayjs(val).toDate(), _item.value, type) && !_item.additional); + _item.active = (value as DateValue[])?.some?.((val) => isSame(dayjs(val).toDate(), _item.value, type) && !_item.additional); } else { _item.active = start && isSame(item.value, start, type) && !_item.additional; } diff --git a/js/tree-v1/tree-node.ts b/js/tree-v1/tree-node.ts index ca0c31d8f9..e306f5f363 100644 --- a/js/tree-v1/tree-node.ts +++ b/js/tree-v1/tree-node.ts @@ -15,10 +15,7 @@ import { TypeTreeNodeModel, TypeTreeNodeData, } from './types'; -import { - createNodeModel, - updateNodeModel, -} from './tree-node-model'; +import { createNodeModel, updateNodeModel } from './tree-node-model'; import log from '../log'; const { hasOwnProperty } = Object.prototype; @@ -35,7 +32,12 @@ export const setableStatus: Record = { export const setableProps = Object.keys(setableStatus); -export const syncableProps = [...setableProps, 'actived', 'expanded', 'checked']; +export const syncableProps = [ + ...setableProps, + 'actived', + 'expanded', + 'checked', +]; export const privateKey = '__tdesign_id__'; @@ -109,6 +111,8 @@ export class TreeNode { // 节点在视图上实际的选中态 public checked: boolean; + public isIndeterminateManual: boolean; + // 节点实际是否为半选状态 public indeterminate: boolean; @@ -130,7 +134,7 @@ export class TreeNode { public constructor( tree: TreeStore, data?: TypeTreeNodeData, - parent?: TreeNode, + parent?: TreeNode ) { this.data = data; this.tree = tree; @@ -552,7 +556,11 @@ export class TreeNode { const { tree } = this; const keys = Object.keys(item); keys.forEach((key) => { - if (hasOwnProperty.call(setableStatus, key) || key === 'label' || key === 'disabled') { + if ( + hasOwnProperty.call(setableStatus, key) + || key === 'label' + || key === 'disabled' + ) { this[key] = item[key]; } }); @@ -737,7 +745,12 @@ export class TreeNode { const { tree } = this; const { hasFilter, config } = tree; const { disabled, allowFoldNodeOnFilter } = config; - if (hasFilter && !allowFoldNodeOnFilter && this.vmIsLocked && !this.vmIsRest) { + if ( + hasFilter + && !allowFoldNodeOnFilter + && this.vmIsLocked + && !this.vmIsRest + ) { return true; } let state = disabled; @@ -1124,6 +1137,7 @@ export class TreeNode { ...opts, }; let map = tree.checkedMap; + if (!options.directly) { map = new Map(tree.checkedMap); } @@ -1168,6 +1182,41 @@ export class TreeNode { }); } } + this.isIndeterminateManual = false; + + return tree.getChecked(map); + } + + public setIndeterminate(indeterminate: boolean, opts?: TypeSettingOptions) { + const { tree } = this; + const config = tree.config || {}; + const options: TypeSettingOptions = { + // 为 true, 为 UI 操作,状态扩散受 disabled 影响 + // 为 false, 为值操作, 状态扩散不受 disabled 影响 + isAction: true, + // 为 true, 直接操作节点状态 + // 为 false, 返回预期状态 + directly: false, + ...opts, + }; + let map = tree.checkedMap; + if (!options.directly) { + map = new Map(tree.checkedMap); + } + if (!this.isCheckable()) { + // 当前节点非可选节点,则不可设置选中态 + return tree.getChecked(map); + } + if (options.isAction && this.isDisabled()) { + // 对于 UI 动作,禁用时不可切换选中态 + return tree.getChecked(map); + } + if (indeterminate === this.isIndeterminate()) { + // 值没有变更,则选中态无变化 + return tree.getChecked(map); + } + this.indeterminate = indeterminate; + this.isIndeterminateManual = true; return tree.getChecked(map); } @@ -1183,7 +1232,6 @@ export class TreeNode { directly: false, ...opts, }; - // 碰到不可选节点,中断扩散 if (!this.isCheckable()) return; @@ -1252,8 +1300,12 @@ export class TreeNode { * 更新节点选中态 * @return void */ - public updateChecked(): void { - const { tree, value } = this; + public updateChecked(from?: string): void { + const { tree, value, isIndeterminateManual } = this; + if (isIndeterminateManual && ['refresh'].includes(from)) { + return; + } + const { checkedMap } = tree; this.checked = this.isChecked(); this.indeterminate = this.isIndeterminate(); @@ -1303,6 +1355,7 @@ export class TreeNode { const relatedNodes = tree.getRelatedNodes([this.value]); relatedNodes.forEach((node) => { node.update(); + if (node.isIndeterminateManual && node.indeterminate) return; node.updateChecked(); }); } diff --git a/js/tree-v1/tree-store.ts b/js/tree-v1/tree-store.ts index 72311a281f..c7a33f26fc 100644 --- a/js/tree-v1/tree-store.ts +++ b/js/tree-v1/tree-store.ts @@ -81,6 +81,9 @@ export class TreeStore { // 选中节点集合 public checkedMap: TypeIdMap; + // 设置半选集合 + public indeterminateMap: TypeIdMap; + // 展开节点的集合 public expandedMap: TypeIdMap; @@ -138,6 +141,7 @@ export class TreeStore { this.expandedMap = new Map(); this.checkedMap = new Map(); this.updatedMap = new Map(); + this.indeterminateMap = new Map(); this.filterMap = new Map(); this.prevFilter = null; // 这个计时器确保频繁的 update 事件被归纳为1次完整数据更新后的触发 @@ -272,7 +276,7 @@ export class TreeStore { */ public getNodes( item?: TypeTargetNode, - options?: TypeTreeFilterOptions, + options?: TypeTreeFilterOptions ): TreeNode[] { let nodes: TreeNode[] = []; let val: TreeNodeValue = ''; @@ -355,7 +359,7 @@ export class TreeStore { */ private parseNodeData( para: TreeNodeValue | TreeNode | TypeTreeNodeData, - item: TypeTreeNodeData | TreeNode, + item: TypeTreeNodeData | TreeNode ) { let value: TreeNodeValue = ''; let node = null; @@ -398,7 +402,7 @@ export class TreeStore { */ public appendNodes( para: TypeTargetNode | TypeTreeNodeData, - item?: TypeTreeNodeData | TreeNode, + item?: TypeTreeNodeData | TreeNode ): void { const spec = this.parseNodeData(para, item); if (spec.data) { @@ -476,7 +480,7 @@ export class TreeStore { // 所以遍历 nodeMap 确保初始化阶段 refreshState 方法也可以触发全部节点的更新 nodeMap.forEach((node) => { node.update(); - node.updateChecked(); + node.updateChecked('refresh'); }); } @@ -577,6 +581,21 @@ export class TreeStore { this.setActived(list); } + public replaceIndeterminate(list: TreeNodeValue[]): void { + this.setIndeterminate(list); + } + + public setIndeterminate(indeterminate: TreeNodeValue[]): void { + indeterminate.forEach((val) => { + this.indeterminateMap.set(val, true); + const node = this.getNode(val); + if (node) { + node.setIndeterminate(true); + node.update(); + } + }); + } + /** * 设置激活态 * @param {string[]} list 目标节点值数组 @@ -796,7 +815,7 @@ export class TreeStore { public updateAll(): void { this.nodeMap.forEach((node) => { node.update(); - node.updateChecked(); + node.updateChecked('refresh'); }); } @@ -834,7 +853,7 @@ export class TreeStore { */ public getRelatedNodes( list: TreeNodeValue[], - options?: TypeRelatedNodesOptions, + options?: TypeRelatedNodesOptions ): TreeNode[] { const conf = { // 默认倒序排列,从底层节点开始遍历