-
Notifications
You must be signed in to change notification settings - Fork 491
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(tree): support virtual scroll; support tree nodes drag; and make…
… code same as Vue2 (#3410) * docs(tree): 添加调试协助文档 * refactor(tree): tree 组件,移植 vue2 项目的代码 * docs(tree): 完善调试文档 * refactor(tree): 解决 vue2,vue3 的 useVModel 使用差异问题 * refactor(tree): tree 组件解决 vue3 类型警告问题 * refactor(tree): 解决类型告警问题 * refactor(tree): 解决组件引用方式告警的问题 * refactor(tree): tree 组件解决 usVModel 使用错误的问题 * refactor(tree): 更新 tree 组件示例,与 vue2 同步 * refactor(tree): 解决与 vue2 示例的状态一致性问题 * refactor(tree): 解决与 vue2 的适配问题 * refactor(tree): 解决 vue3 h 方法差异问题 * refactor(tree): tree 组件,修正与 vue2 slots 获取方式存在差异的问题 * refactor(tree): 示例消除 vue2, vue3 差异 * refactor(tree): 解决与 vue2 受控代码一致性问题 * refactor(tree): tree 组件统一受控示例 * refactor(tree): 解决数据变更示例vue3下需要变更的问题 * refactor(tree): tree 组件完善示例样式 * refactor(tree): tree 组件,完善示例样式 * refactor(tree): tree 组件,示例代码与vue2 统一 * refactor(tree): tree 组件,解决图标引用问题 * refactor(tree): 改进结构,使用共享对象传递公共数据 * refactor(tree): 改进代码结构 * refactor(tree): 解决 vue3 未能触发 item render 的问题 * refactor(tree): 改进组件结构 * refactor(tree): 解决 vue3 props.disableCheck 未能正常工作的问题 * refactor(tree): 解决 empty 示例废弃 api 未生效的问题 * refactor(tree): tree 组件 解决与 vue2 的示例差异 * refactor(tree): tree 组件完善节点操作示例 * fix(tree): tree 组件,解决 setData 方法未触发节点更新的问题 * refactor(tree): tree 组件,解决 setData 方法未能触发新增属性变更的问题 * refactor(tree): tree 组件完善属性同步示例 * refactor(tree): tree 组件虚拟滚动示例,降低数量便于调试 * feat(tree): tree 组件添加虚拟滚动能力 * docs(tree): 完善调试文档 * refactor(tree): tree 组件,改进代码结构,实现tree结构的立即渲染 * test(unit): tree 组件,同步 vue2 项目下的单元测试 * test(unit): tree 组件,测试代码适配 vue2 版本 * refactor(tree): tree 组件虚拟滚动事件绑定适配 vue3 * test(unit): tree 组件统一vue2,vue3测试用例形式 * test(unit): tree 组件测试用例适配 vue2 项目 * test(unit): tree 组件,vue2,vue3 统一测试用例 * test(unit): tree 组件,vue2,vue3 统一测试用例 * fix(tree): tree 组件虚拟滚动解决滚动条样式异常问题 * refactor(tree): tree 组件文件按规范重命名 * refactor(tree): tree 组件完善 expand-all 示例 * refactor(tree): tree 组件示例样式改进 * refactor(tree): tree 组件改进示例 * refactor(tree): 完善组件示例 * refactor(tree): tree 组件完善示例,分拆不同形式虚拟滚动示例 * fix(tree): tree 组件实现正确的受控呈现 * refactor(tree): 完善虚拟滚动示例 * fix(tree): tree 组件修正受控表现 * fix(tree): tree 组件修正事件触发规则 * test(unit): tree 组件,完善单元测试 * chore(tree): tree 组件变更,更新 src/_common * docs(tree): update documents * chore(tree): 更新 common * refactor(tree): src/hooks/useVModel.ts 文件还原为之前的版本 * refactor(tree): 实现对 modelValue 属性的兼容 * feat(tree): update api * feat(tree): update api * chore(tree): 更新 common * refactor(tree): tree 组件,适配器文件移除冗余代码 * fix(tree): remove unused code * fix(tree): ts error * refactor(tree-select): 修正类型错误 * test(unit): tree, update snapshot --------- Co-authored-by: chaishi <[email protected]>
- Loading branch information
Showing
79 changed files
with
22,032 additions
and
11,114 deletions.
There are no files selected for viewing
Submodule _common
updated
15 files
+23 −1 | docs/web/api/tag.en-US.md | |
+23 −7 | docs/web/api/tag.md | |
+7 −0 | docs/web/api/tree.md | |
+2 −1 | js/common.ts | |
+1 −1 | js/tree/tree-node-model.ts | |
+3 −0 | js/tree/tree-node.ts | |
+38 −32 | js/tree/tree-store.ts | |
+7 −5 | js/tree/types.ts | |
+5 −0 | style/mobile/components/textarea/v2/_index.less | |
+4 −0 | style/web/components/breadcrumb/_index.less | |
+2 −1 | style/web/components/cascader/_var.less | |
+0 −16 | style/web/components/menu/_docs.less | |
+26 −6 | style/web/components/menu/_index.less | |
+44 −21 | style/web/components/tag/_index.less | |
+7 −2 | style/web/components/tag/_var.less |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import { ref, onMounted, computed, nextTick, Ref, UnwrapRef } from 'vue'; | ||
import observe from '../_common/js/utils/observe'; | ||
import { isServer } from '../utils/dom'; | ||
|
||
export type UseLazyLoadParams = UnwrapRef<{ | ||
type: 'lazy' | 'virtual'; | ||
rowHeight?: number; | ||
bufferSize?: number; | ||
}>; | ||
|
||
export default function useLazyLoad( | ||
containerRef: Ref<HTMLElement>, | ||
childRef: Ref<HTMLElement>, | ||
params: UseLazyLoadParams, | ||
) { | ||
const tRowHeight = computed(() => Math.max(params.rowHeight || 48, 48)); | ||
const isInit = ref(false); | ||
const hasLazyLoadHolder = computed(() => params?.type === 'lazy' && !isInit.value); | ||
|
||
const requestAnimationFrame = (!isServer && window.requestAnimationFrame) || ((cb) => setTimeout(cb, 16.6)); | ||
|
||
const init = () => { | ||
if (!isInit.value) { | ||
requestAnimationFrame(() => { | ||
isInit.value = true; | ||
}); | ||
} | ||
}; | ||
|
||
onMounted(() => { | ||
if (params?.type !== 'lazy') return; | ||
nextTick(() => { | ||
const bufferSize = Math.max(10, params.bufferSize || 10); | ||
const height = tRowHeight.value * bufferSize; | ||
observe(childRef.value, containerRef.value, init, height); | ||
}); | ||
}); | ||
|
||
return { | ||
hasLazyLoadHolder, | ||
tRowHeight, | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,315 @@ | ||
import { mount } from '@vue/test-utils'; | ||
import Tree from '@/src/tree/index.ts'; | ||
import { delay } from './kit'; | ||
|
||
describe('Tree:activable', () => { | ||
vi.useRealTimers(); | ||
describe('props.activable', () => { | ||
it('设置 activable 为 true, 节点可以具有激活态', async () => { | ||
const data = [ | ||
{ | ||
value: 't1', | ||
children: [ | ||
{ | ||
value: 't1.1', | ||
}, | ||
{ | ||
value: 't1.2', | ||
}, | ||
], | ||
}, | ||
]; | ||
const wrapper = mount({ | ||
render() { | ||
return <Tree ref="tree" data={data} activable expand-all transition={false}></Tree>; | ||
}, | ||
}); | ||
await delay(1); | ||
const { tree } = wrapper.vm.$refs; | ||
tree.setItem('t1.1', { | ||
actived: true, | ||
}); | ||
await delay(1); | ||
expect(wrapper.find('[data-value="t1"]').classes('t-is-active')).toBe(false); | ||
expect(wrapper.find('[data-value="t1.1"]').classes('t-is-active')).toBe(true); | ||
expect(wrapper.find('[data-value="t1.2"]').classes('t-is-active')).toBe(false); | ||
tree.setItem('t1.1', { | ||
actived: false, | ||
}); | ||
await delay(1); | ||
expect(wrapper.find('[data-value="t1"]').classes('t-is-active')).toBe(false); | ||
expect(wrapper.find('[data-value="t1.1"]').classes('t-is-active')).toBe(false); | ||
expect(wrapper.find('[data-value="t1.2"]').classes('t-is-active')).toBe(false); | ||
}); | ||
|
||
it('在 data 中可以预先设置激活态', async () => { | ||
const data = [ | ||
{ | ||
value: 't1', | ||
children: [ | ||
{ | ||
value: 't1.1', | ||
actived: true, | ||
}, | ||
{ | ||
value: 't1.2', | ||
actived: true, | ||
}, | ||
], | ||
}, | ||
]; | ||
const wrapper = mount({ | ||
render() { | ||
return <Tree ref="tree" data={data} activable expand-all transition={false}></Tree>; | ||
}, | ||
}); | ||
await delay(1); | ||
expect(wrapper.find('[data-value="t1"]').classes('t-is-active')).toBe(false); | ||
expect(wrapper.find('[data-value="t1.1"]').classes('t-is-active')).toBe(true); | ||
expect(wrapper.find('[data-value="t1.2"]').classes('t-is-active')).toBe(true); | ||
}); | ||
}); | ||
|
||
describe('props.defaultActived', () => { | ||
it('defaultActived 可以用来设置默认激活状态', async () => { | ||
const data = [ | ||
{ | ||
value: 't1', | ||
children: [ | ||
{ | ||
value: 't1.1', | ||
}, | ||
{ | ||
value: 't1.2', | ||
}, | ||
], | ||
}, | ||
]; | ||
const wrapper = mount({ | ||
render() { | ||
return <Tree ref="tree" data={data} activable defaultActived={['t1.1']} expand-all transition={false}></Tree>; | ||
}, | ||
}); | ||
await delay(1); | ||
expect(wrapper.find('[data-value="t1"]').classes('t-is-active')).toBe(false); | ||
expect(wrapper.find('[data-value="t1.1"]').classes('t-is-active')).toBe(true); | ||
expect(wrapper.find('[data-value="t1.2"]').classes('t-is-active')).toBe(false); | ||
}); | ||
}); | ||
|
||
describe('props.actived', () => { | ||
it('设置 actived 可指定激活节点', async () => { | ||
const data = [ | ||
{ | ||
value: 't1', | ||
children: [ | ||
{ | ||
value: 't1.1', | ||
}, | ||
{ | ||
value: 't1.2', | ||
}, | ||
], | ||
}, | ||
]; | ||
const wrapper = mount({ | ||
render() { | ||
return <Tree ref="tree" data={data} activable actived={['t1.1']} expand-all transition={false}></Tree>; | ||
}, | ||
}); | ||
await delay(1); | ||
expect(wrapper.find('[data-value="t1"]').classes('t-is-active')).toBe(false); | ||
expect(wrapper.find('[data-value="t1.1"]').classes('t-is-active')).toBe(true); | ||
expect(wrapper.find('[data-value="t1.2"]').classes('t-is-active')).toBe(false); | ||
}); | ||
|
||
it('设置 actived 可变更激活节点', async () => { | ||
const data = [ | ||
{ | ||
value: 't1', | ||
children: [ | ||
{ | ||
value: 't1.1', | ||
}, | ||
{ | ||
value: 't1.2', | ||
}, | ||
], | ||
}, | ||
]; | ||
const wrapper = mount({ | ||
data() { | ||
return { | ||
actived: ['t1.1'], | ||
}; | ||
}, | ||
render() { | ||
return <Tree ref="tree" data={data} activable actived={this.actived} expand-all transition={false}></Tree>; | ||
}, | ||
}); | ||
await delay(1); | ||
expect(wrapper.find('[data-value="t1"]').classes('t-is-active')).toBe(false); | ||
expect(wrapper.find('[data-value="t1.1"]').classes('t-is-active')).toBe(true); | ||
expect(wrapper.find('[data-value="t1.2"]').classes('t-is-active')).toBe(false); | ||
wrapper.setData({ | ||
actived: ['t1.2'], | ||
}); | ||
await delay(1); | ||
expect(wrapper.find('[data-value="t1"]').classes('t-is-active')).toBe(false); | ||
expect(wrapper.find('[data-value="t1.1"]').classes('t-is-active')).toBe(false); | ||
expect(wrapper.find('[data-value="t1.2"]').classes('t-is-active')).toBe(true); | ||
wrapper.setData({ | ||
actived: ['t1'], | ||
}); | ||
await delay(1); | ||
expect(wrapper.find('[data-value="t1"]').classes('t-is-active')).toBe(true); | ||
expect(wrapper.find('[data-value="t1.1"]').classes('t-is-active')).toBe(false); | ||
expect(wrapper.find('[data-value="t1.2"]').classes('t-is-active')).toBe(false); | ||
}); | ||
|
||
it('actived 受控处理可赋值空值', async () => { | ||
const data = [ | ||
{ | ||
value: 't1', | ||
children: [ | ||
{ | ||
value: 't1.1', | ||
}, | ||
{ | ||
value: 't1.2', | ||
}, | ||
], | ||
}, | ||
]; | ||
const wrapper = mount({ | ||
data() { | ||
return { | ||
actived: ['t1'], | ||
}; | ||
}, | ||
methods: { | ||
onActive(vals) { | ||
const actived = vals.filter((val) => val !== 't1'); | ||
this.actived = actived; | ||
}, | ||
}, | ||
render() { | ||
return ( | ||
<Tree | ||
ref="tree" | ||
data={data} | ||
activable | ||
actived={this.actived} | ||
onActive={this.onActive} | ||
expand-all | ||
transition={false} | ||
></Tree> | ||
); | ||
}, | ||
}); | ||
await delay(1); | ||
expect(wrapper.find('[data-value="t1"]').classes('t-is-active')).toBe(true); | ||
await wrapper.find('[data-value="t1"] .t-tree__label').trigger('click'); | ||
expect(wrapper.find('[data-value="t1"]').classes('t-is-active')).toBe(false); | ||
await delay(1); | ||
await wrapper.find('[data-value="t1"] .t-tree__label').trigger('click'); | ||
expect(wrapper.find('[data-value="t1"]').classes('t-is-active')).toBe(false); | ||
}); | ||
}); | ||
|
||
describe('props.activeMultiple', () => { | ||
it('默认激活其他节点,原激活节点要取消激活状态', async () => { | ||
const data = [ | ||
{ | ||
value: 't1', | ||
actived: true, | ||
children: [ | ||
{ | ||
value: 't1.1', | ||
}, | ||
{ | ||
value: 't1.2', | ||
}, | ||
], | ||
}, | ||
]; | ||
const wrapper = mount({ | ||
render() { | ||
return <Tree ref="tree" data={data} activable expand-all transition={false}></Tree>; | ||
}, | ||
}); | ||
await delay(1); | ||
const { tree } = wrapper.vm.$refs; | ||
|
||
expect(wrapper.find('[data-value="t1"]').classes('t-is-active')).toBe(true); | ||
expect(wrapper.find('[data-value="t1.1"]').classes('t-is-active')).toBe(false); | ||
expect(wrapper.find('[data-value="t1.2"]').classes('t-is-active')).toBe(false); | ||
|
||
tree.setItem('t1.1', { | ||
actived: true, | ||
}); | ||
|
||
await delay(1); | ||
|
||
expect(wrapper.find('[data-value="t1"]').classes('t-is-active')).toBe(false); | ||
expect(wrapper.find('[data-value="t1.1"]').classes('t-is-active')).toBe(true); | ||
expect(wrapper.find('[data-value="t1.2"]').classes('t-is-active')).toBe(false); | ||
|
||
tree.setItem('t1.2', { | ||
actived: true, | ||
}); | ||
await delay(1); | ||
|
||
expect(wrapper.find('[data-value="t1"]').classes('t-is-active')).toBe(false); | ||
expect(wrapper.find('[data-value="t1.1"]').classes('t-is-active')).toBe(false); | ||
expect(wrapper.find('[data-value="t1.2"]').classes('t-is-active')).toBe(true); | ||
}); | ||
|
||
it('设置 activeMultiple 为 true,节点激活状态互不关联', async () => { | ||
const data = [ | ||
{ | ||
value: 't1', | ||
actived: true, | ||
children: [ | ||
{ | ||
value: 't1.1', | ||
}, | ||
{ | ||
value: 't1.2', | ||
}, | ||
], | ||
}, | ||
]; | ||
const wrapper = mount({ | ||
render() { | ||
return <Tree ref="tree" data={data} activable expand-all activeMultiple={true} transition={false}></Tree>; | ||
}, | ||
}); | ||
await delay(1); | ||
const { tree } = wrapper.vm.$refs; | ||
|
||
expect(wrapper.find('[data-value="t1"]').classes('t-is-active')).toBe(true); | ||
expect(wrapper.find('[data-value="t1.1"]').classes('t-is-active')).toBe(false); | ||
expect(wrapper.find('[data-value="t1.2"]').classes('t-is-active')).toBe(false); | ||
|
||
tree.setItem('t1.1', { | ||
actived: true, | ||
}); | ||
|
||
await delay(1); | ||
|
||
expect(wrapper.find('[data-value="t1"]').classes('t-is-active')).toBe(true); | ||
expect(wrapper.find('[data-value="t1.1"]').classes('t-is-active')).toBe(true); | ||
expect(wrapper.find('[data-value="t1.2"]').classes('t-is-active')).toBe(false); | ||
|
||
tree.setItem('t1.2', { | ||
actived: true, | ||
}); | ||
await delay(1); | ||
|
||
expect(wrapper.find('[data-value="t1"]').classes('t-is-active')).toBe(true); | ||
expect(wrapper.find('[data-value="t1.1"]').classes('t-is-active')).toBe(true); | ||
expect(wrapper.find('[data-value="t1.2"]').classes('t-is-active')).toBe(true); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { defineComponent } from 'vue'; |
Oops, something went wrong.