From 435d0d11aa7fe06362fdad507b07d8d35e941308 Mon Sep 17 00:00:00 2001 From: zhangbing4 Date: Tue, 30 Jul 2024 19:20:50 +0800 Subject: [PATCH 01/18] feat: add virtuallist component --- components/select/demos/virtual.md | 47 +++++++++++ components/virtuallist/demos/basic.md | 50 ++++++++++++ components/virtuallist/index.md | 21 +++++ components/virtuallist/index.spec.ts | 0 components/virtuallist/index.ts | 30 +++++++ components/virtuallist/index.vdt | 20 +++++ components/virtuallist/styles.ts | 10 +++ components/virtuallist/useVirtual.ts | 67 +++++++++++++++ components/virtuallist/useVirtual2.ts | 113 ++++++++++++++++++++++++++ 9 files changed, 358 insertions(+) create mode 100644 components/select/demos/virtual.md create mode 100644 components/virtuallist/demos/basic.md create mode 100644 components/virtuallist/index.md create mode 100644 components/virtuallist/index.spec.ts create mode 100644 components/virtuallist/index.ts create mode 100644 components/virtuallist/index.vdt create mode 100644 components/virtuallist/styles.ts create mode 100644 components/virtuallist/useVirtual.ts create mode 100644 components/virtuallist/useVirtual2.ts diff --git a/components/select/demos/virtual.md b/components/select/demos/virtual.md new file mode 100644 index 000000000..bbf66461d --- /dev/null +++ b/components/select/demos/virtual.md @@ -0,0 +1,47 @@ +--- +title: 虚拟列表 +order: 14 +--- + +`virtualMode`属性开启虚拟列表 + +```vdt +import {Select, Option} from 'kpc'; + +
+ +
+``` + +```ts +interface Props { + day?: string | null + data: any[] +} + +export default class extends Component { + static template = template; + + static defaults() { + return { + day: null, + data: [] + } as Props; + } + + init() { + const arr = []; + for (let index = 0; index < 10000; index++) { + arr.push({ + value: index, + label: `测试${index}` + }); + } + this.set({data: arr}); + } +} +``` diff --git a/components/virtuallist/demos/basic.md b/components/virtuallist/demos/basic.md new file mode 100644 index 000000000..85b80a9b9 --- /dev/null +++ b/components/virtuallist/demos/basic.md @@ -0,0 +1,50 @@ +--- +title: 基本用法 +order: 0 +--- + +组件接收任意合法的字符串当做`text`值 + +```vdt +import { VirtualList } from 'kpc'; + + +
+
{$value.label}
+
+
+``` + +```styl +.div-height + height 30px +``` + +```ts +interface Props { + day?: string | null + data: any[] +} + +export default class extends Component { + static template = template; + + static defaults() { + return { + day: null, + data: [] + } as Props; + } + + init() { + const arr = []; + for (let index = 0; index < 10000; index++) { + arr.push({ + value: index, + label: `测试${index}` + }); + } + this.set({data: arr}); + } +} +``` diff --git a/components/virtuallist/index.md b/components/virtuallist/index.md new file mode 100644 index 000000000..4b3850ce2 --- /dev/null +++ b/components/virtuallist/index.md @@ -0,0 +1,21 @@ +--- +title: 虚拟列表 +category: 组件 +order: 99 +sidebar: doc +--- + +# 属性 + +| 属性 | 说明 | 类型 | 默认值 | +| --- | --- | --- | --- | +| text | 复制文案值 | `string` | `undefined` | +| showMessage | 复制成功/失败时,是否展示Message | `boolean` | `true` | + +# 事件 + +| 事件名 | 说明 | 参数 | +| --- | --- | --- | +| success | 复制成功时触发 | `(value: string) => void` | +| error | 复制失败时触发 | `() => void` | + diff --git a/components/virtuallist/index.spec.ts b/components/virtuallist/index.spec.ts new file mode 100644 index 000000000..e69de29bb diff --git a/components/virtuallist/index.ts b/components/virtuallist/index.ts new file mode 100644 index 000000000..b450a4be3 --- /dev/null +++ b/components/virtuallist/index.ts @@ -0,0 +1,30 @@ +import { Component, TypeDefs } from 'intact'; +import template from './index.vdt'; +import { useVirtual } from './useVirtual2'; +import { Events } from '../types'; +import { useConfigContext } from '../config'; + +export interface VirtualListProps {} + +export interface VirtualListEvents {} + +const typeDefs: Required> = { + text: String, + showMessage: Boolean, +}; + +// const defaults = (): Partial => ({ +// showMessage: true, +// }); + +const events: Events = {}; + +export class VirtualList extends Component { + static template = template; + static typeDefs = typeDefs; + // static defaults = defaults; + // static events = events; + + public virtual = useVirtual(); + private config = useConfigContext(); +} diff --git a/components/virtuallist/index.vdt b/components/virtuallist/index.vdt new file mode 100644 index 000000000..6c685aa6e --- /dev/null +++ b/components/virtuallist/index.vdt @@ -0,0 +1,20 @@ +import { Icon } from '../icon'; +import { getRestProps } from '../utils'; +import { makeStyles } from './styles'; +import { _$ } from '../../i18n'; + +const { children, className } = this.get(); +const {containerRef, contentRef, virtualChildren, paddingTop, paddingBottom} = this.virtual; +const { k } = this.config; + +const classNameObj = { + [`${k}-virtual`]: true, + [makeStyles(k)]: true, + [className]: className, +} + +
+
+ {virtualChildren.value} +
+
diff --git a/components/virtuallist/styles.ts b/components/virtuallist/styles.ts new file mode 100644 index 000000000..fcd08c57b --- /dev/null +++ b/components/virtuallist/styles.ts @@ -0,0 +1,10 @@ +import {css} from '@emotion/css'; +import '../../styles/global'; +import { cache } from '../utils'; + +export const makeStyles = cache(function makeStyles(k: string) { + return css` + cursor: pointer; + vertical-align: middle; + ` +}); diff --git a/components/virtuallist/useVirtual.ts b/components/virtuallist/useVirtual.ts new file mode 100644 index 000000000..860aa7741 --- /dev/null +++ b/components/virtuallist/useVirtual.ts @@ -0,0 +1,67 @@ +import { useInstance, onMounted, onUnmounted, createRef, Children, VNode, directClone } from 'intact'; +import { useState } from '../../hooks/useState'; +import type { VirtualList } from './index'; +import { isComponentVNode } from '../utils'; +// import { Option } from './option'; + +export function useVirtual() { + const instance = useInstance() as VirtualList; + const children: Children = instance.get('children'); + if (!Array.isArray(children)) return {}; + const bufferSize = 6; + const containerRef = createRef(); + const startIndex = useState(0); + const endIndex = useState(0); + const itemHeightState = useState(32); + const containerHeightState = useState(200); + const virtualChildren = useState<(VNode | string | number)[]>([]); + let ticking = false; + + const handleScroll = () => { + if (!ticking && containerRef.value) { + ticking = true; + requestAnimationFrame(() => { + const scrollTop = containerRef.value!.scrollTop; + const itemHeight = itemHeightState.value; + const containerHeight = containerHeightState.value; + const start = Math.floor(scrollTop / itemHeight); + const end = Math.min(children.length - 1, Math.ceil((scrollTop + containerHeight) / itemHeight)); + console.log('handleScroll', scrollTop, itemHeight, start, end, children.length); + startIndex.set(start); + endIndex.set(end); + setVisibleItems(start, end); + ticking = false; + }); + } + }; + + const setVisibleItems = (start: number, end: number) => { + const visibleItems = []; + const startWithBuffer = Math.max(0, start - bufferSize); + const endWithBuffer = Math.min(children.length - 1, end + bufferSize); + for (let i = startWithBuffer; i <= endWithBuffer; i++) { + visibleItems.push(children[i] as VNode); + } + virtualChildren.set(visibleItems); + }; + + onMounted(() => { + if (containerRef.value) { + containerRef.value.addEventListener('scroll', handleScroll); + handleScroll(); + } + }); + + onUnmounted(() => { + if (containerRef.value) { + containerRef.value.removeEventListener('scroll', handleScroll); + } + }); + + return { + containerRef, + virtualChildren, + paddingTop: () => Math.max(0, startIndex.value - bufferSize) * itemHeightState.value, + paddingBottom: () => Math.max(0, (children.length - endIndex.value - 1 - bufferSize)) * itemHeightState.value, + }; +} diff --git a/components/virtuallist/useVirtual2.ts b/components/virtuallist/useVirtual2.ts new file mode 100644 index 000000000..ee1af77cd --- /dev/null +++ b/components/virtuallist/useVirtual2.ts @@ -0,0 +1,113 @@ +import { useInstance, onMounted, onUnmounted, createRef, Children, VNode, directClone } from 'intact'; +import { useState } from '../../hooks/useState'; +import type { VirtualList } from './index'; +import { isComponentVNode } from '../utils'; +// import { Option } from './option'; + +export function useVirtual() { + const instance = useInstance() as VirtualList; + const children: Children = instance.get('children'); + if (!Array.isArray(children)) return {}; + const bufferSize = 6; + const containerRef = createRef(); + const contentRef = createRef(); + const startIndex = useState(0); + const endIndex = useState(0); + const itemHeights = useState(new Map()); + const estimatedHeight = 50; + const containerHeightState = useState(200); + const virtualChildren = useState<(VNode | string | number)[]>([]); + let ticking = false; + + const calculateHeights = () => { + if (contentRef.value) { + const nodes = contentRef.value.children as any; + const heightsMap = new Map(); + debugger; + for (let i = 0; i < nodes.length; i++) { + heightsMap.set(i, nodes[i].offsetHeight); + } + itemHeights.set(heightsMap); + } + }; + + const handleScroll = () => { + if (!ticking && containerRef.value) { + ticking = true; + requestAnimationFrame(() => { + const scrollTop = containerRef.value!.scrollTop; + let accumulatedHeight = 0; + let start = 0; + for (let i = 0; i < children.length; i++) { + accumulatedHeight += itemHeights.value.get(i) || estimatedHeight; + if (accumulatedHeight >= scrollTop) { + start = i; + break; + } + } + let end = start; + accumulatedHeight = 0; + for (let i = start; i < children.length; i++) { + accumulatedHeight += itemHeights.value.get(i) || estimatedHeight; + if (accumulatedHeight >= containerHeightState.value) { + end = i; + break; + } + } + startIndex.set(start); + endIndex.set(end); + setVisibleItems(start, end); + ticking = false; + }); + } + }; + + const setVisibleItems = (start: number, end: number) => { + const visibleItems = []; + const startWithBuffer = Math.max(0, start - bufferSize); + const endWithBuffer = Math.min(children.length - 1, end + bufferSize); + for (let i = startWithBuffer; i <= endWithBuffer; i++) { + visibleItems.push(children[i] as VNode); + } + virtualChildren.set(visibleItems); + }; + + onMounted(() => { + if (containerRef.value) { + calculateHeights(); + containerRef.value.addEventListener('scroll', handleScroll); + handleScroll(); + } + }); + + onUnmounted(() => { + if (containerRef.value) { + containerRef.value.removeEventListener('scroll', handleScroll); + } + }); + + const paddingTop = (): number => { + let height = 0; + for (let i = 0; i < Math.max(0, startIndex.value - bufferSize); i++) { + height += itemHeights.value.get(i) || estimatedHeight; + } + return height; + }; + + const paddingBottom = (): number => { + let height = 0; + for (let i = Math.min(children.length, endIndex.value + bufferSize + 1); i < children.length; i++) { + height += itemHeights.value.get(i) || estimatedHeight; + } + return height; + }; + + + return { + containerRef, + contentRef, + virtualChildren, + paddingTop, + paddingBottom, + }; +} From c070e2615bee4244bdb651b188e73249010ee294 Mon Sep 17 00:00:00 2001 From: zhangbing4 Date: Tue, 30 Jul 2024 19:22:11 +0800 Subject: [PATCH 02/18] feat: add virtuallist component --- index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/index.ts b/index.ts index 0d3cba014..8880c7e14 100644 --- a/index.ts +++ b/index.ts @@ -67,6 +67,7 @@ export * from './components/tree'; export * from './components/treeSelect'; export * from './components/upload'; export * from './components/view'; +export * from './components/virtuallist'; export * from './components/wave'; export const version = '3.4.0-beta.2'; From a75878e169d7a2070536ac7fae5fc255a5280471 Mon Sep 17 00:00:00 2001 From: haoyu1 Date: Wed, 28 Aug 2024 19:34:41 +0800 Subject: [PATCH 03/18] feat: test --- components/virtuallist/useVirtual.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/components/virtuallist/useVirtual.ts b/components/virtuallist/useVirtual.ts index 860aa7741..12aebc5fc 100644 --- a/components/virtuallist/useVirtual.ts +++ b/components/virtuallist/useVirtual.ts @@ -6,6 +6,7 @@ import { isComponentVNode } from '../utils'; export function useVirtual() { const instance = useInstance() as VirtualList; + const children: Children = instance.get('children'); if (!Array.isArray(children)) return {}; const bufferSize = 6; From 6377653393a835acb3cb93e5f9139d7b269031fd Mon Sep 17 00:00:00 2001 From: zhangbing4 Date: Thu, 29 Aug 2024 20:19:46 +0800 Subject: [PATCH 04/18] fix: usevirtual --- components/virtuallist/demos/basic.md | 4 +- components/virtuallist/index.ts | 2 +- components/virtuallist/index.vdt | 8 +- components/virtuallist/useVirtual3.ts | 120 ++++++++++++++++++++++++++ 4 files changed, 128 insertions(+), 6 deletions(-) create mode 100644 components/virtuallist/useVirtual3.ts diff --git a/components/virtuallist/demos/basic.md b/components/virtuallist/demos/basic.md index 85b80a9b9..817d80c5d 100644 --- a/components/virtuallist/demos/basic.md +++ b/components/virtuallist/demos/basic.md @@ -9,8 +9,8 @@ order: 0 import { VirtualList } from 'kpc'; -
-
{$value.label}
+
+ {$value.label}
``` diff --git a/components/virtuallist/index.ts b/components/virtuallist/index.ts index b450a4be3..2e07778ad 100644 --- a/components/virtuallist/index.ts +++ b/components/virtuallist/index.ts @@ -1,6 +1,6 @@ import { Component, TypeDefs } from 'intact'; import template from './index.vdt'; -import { useVirtual } from './useVirtual2'; +import { useVirtual } from './useVirtual3'; import { Events } from '../types'; import { useConfigContext } from '../config'; diff --git a/components/virtuallist/index.vdt b/components/virtuallist/index.vdt index 6c685aa6e..9915334b0 100644 --- a/components/virtuallist/index.vdt +++ b/components/virtuallist/index.vdt @@ -4,7 +4,7 @@ import { makeStyles } from './styles'; import { _$ } from '../../i18n'; const { children, className } = this.get(); -const {containerRef, contentRef, virtualChildren, paddingTop, paddingBottom} = this.virtual; +const {containerRef, contentRef, virtualChildren, translateY, totalHeight} = this.virtual; const { k } = this.config; const classNameObj = { @@ -14,7 +14,9 @@ const classNameObj = { }
-
- {virtualChildren.value} +
+
+ {virtualChildren.value} +
diff --git a/components/virtuallist/useVirtual3.ts b/components/virtuallist/useVirtual3.ts new file mode 100644 index 000000000..de5b1c757 --- /dev/null +++ b/components/virtuallist/useVirtual3.ts @@ -0,0 +1,120 @@ +import { useInstance, onMounted, onUnmounted, createRef, Children, VNode, directClone } from 'intact'; +import { useState } from '../../hooks/useState'; +import type { VirtualList } from './index'; + +export function useVirtual(config?: { bufferSize?: number }) { + const instance = useInstance() as VirtualList; + const children: Children = instance.get('children'); + if (!Array.isArray(children)) return {}; + + const bufferSize = config?.bufferSize ?? 6; // 默认 bufferSize 为 6 + const containerRef = createRef(); + const contentRef = createRef(); + const startIndex = useState(0); + const endIndex = useState(0); + const itemHeights = useState(new Map()); + const estimatedHeight = 30; // 初始估计高度 + const containerHeightState = useState(200); + const virtualChildren = useState<(VNode | string | number)[]>([]); + const translateY = useState(0); // 记录偏移量 + const totalHeight = useState(1000); // 设置初始总高度为一个很大的值 + let ticking = false; + + const calculateHeights = () => { + if (contentRef.value) { + debugger; + const nodes = contentRef.value.children as HTMLCollection; + console.log('----',children,nodes); + const heightsMap = new Map(); + let totalHeightValue = 0; + for (let i = 0; i < nodes.length; i++) { + const height = (nodes[i] as HTMLElement).offsetHeight; // 自动计算高度 + heightsMap.set(i + startIndex.value, height); // 注意要加上 startIndex + totalHeightValue += height; + } + itemHeights.set(heightsMap); + + // 更新总高度 + let accumulatedTotalHeight = 0; + for (let i = 0; i < children.length; i++) { + accumulatedTotalHeight += itemHeights.value.get(i) || estimatedHeight; + } + totalHeight.set(accumulatedTotalHeight); + } + }; + + const handleScroll = () => { + if (!ticking && containerRef.value) { + ticking = true; + requestAnimationFrame(() => { + const scrollTop = containerRef.value!.scrollTop; + let accumulatedHeight = 0; + let start = 0; + for (let i = 0; i < children.length; i++) { + accumulatedHeight += itemHeights.value.get(i) || estimatedHeight; + if (accumulatedHeight >= scrollTop) { + start = i; + break; + } + } + let end = start; + accumulatedHeight = 0; + for (let i = start; i < children.length; i++) { + accumulatedHeight += itemHeights.value.get(i) || estimatedHeight; + if (accumulatedHeight >= containerHeightState.value) { + end = i; + break; + } + } + startIndex.set(start); + endIndex.set(end); + + // 计算 translateY 偏移量 + let translateYValue = 0; + for (let i = 0; i < start; i++) { + translateYValue += itemHeights.value.get(i) || estimatedHeight; + } + translateY.set(translateYValue); + + setVisibleItems(start, end); + ticking = false; + }); + } + }; + + const setVisibleItems = (start: number, end: number) => { + const visibleItems = []; + const startWithBuffer = Math.max(0, start - bufferSize); + const endWithBuffer = Math.min(children.length - 1, end + bufferSize); + for (let i = startWithBuffer; i <= endWithBuffer; i++) { + visibleItems.push(directClone(children[i] as VNode)); + } + virtualChildren.set(visibleItems); + + // 更新内容高度 + calculateHeights(); + }; + + onMounted(() => { + if (containerRef.value) { + calculateHeights(); // 初始计算高度 + + containerRef.value.addEventListener('scroll', handleScroll); + handleScroll(); // 初始化渲染可见项 + } + }); + + onUnmounted(() => { + if (containerRef.value) { + containerRef.value.removeEventListener('scroll', handleScroll); + } + }); + + return { + containerRef, + contentRef, + virtualChildren, + translateY, + totalHeight, // 新增总高度返回值 + }; +} From bd6798adad09711bb9af790c0f551b2ebeccaba3 Mon Sep 17 00:00:00 2001 From: zhangbing4 Date: Sat, 14 Sep 2024 15:07:37 +0800 Subject: [PATCH 05/18] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E8=99=9A?= =?UTF-8?q?=E6=8B=9F=E5=88=97=E8=A1=A8=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/select/base.ts | 2 + components/select/demos/virtual.md | 4 +- components/select/select.vdt | 11 +- components/virtuallist/demos/basic.md | 56 ++++++++-- components/virtuallist/index.ts | 27 +++-- components/virtuallist/index.vdt | 2 +- components/virtuallist/useVirtual.ts | 141 +++++++++++++++++++++----- components/virtuallist/useVirtual2.ts | 113 --------------------- components/virtuallist/useVirtual3.ts | 120 ---------------------- 9 files changed, 197 insertions(+), 279 deletions(-) delete mode 100644 components/virtuallist/useVirtual2.ts delete mode 100644 components/virtuallist/useVirtual3.ts diff --git a/components/select/base.ts b/components/select/base.ts index a607b9e7e..766a799d9 100644 --- a/components/select/base.ts +++ b/components/select/base.ts @@ -42,6 +42,7 @@ export interface BaseSelectProps>> = { flat: Boolean, nowrap: Boolean, draggable: Boolean, + virtual: Boolean, }; const defaults = (): Partial> => ({ diff --git a/components/select/demos/virtual.md b/components/select/demos/virtual.md index bbf66461d..eda227d27 100644 --- a/components/select/demos/virtual.md +++ b/components/select/demos/virtual.md @@ -9,7 +9,7 @@ order: 14 import {Select, Option} from 'kpc';
- @@ -35,7 +35,7 @@ export default class extends Component { init() { const arr = []; - for (let index = 0; index < 10000; index++) { + for (let index = 0; index < 50; index++) { arr.push({ value: index, label: `测试${index}` diff --git a/components/select/select.vdt b/components/select/select.vdt index 5f56642e0..ed789be49 100644 --- a/components/select/select.vdt +++ b/components/select/select.vdt @@ -1,7 +1,8 @@ import {SelectMenu} from './menu'; +import { VirtualList } from '../virtuallist'; import {isEmptyChildren} from '../utils'; -const {className, children, autoDisableArrow, disabled, multiple, value} = this.get(); +const {className, children, autoDisableArrow, disabled, multiple, value, virtual} = this.get(); const {getCreatedVNode, filter} = this.filterable; const options = filter(children); const allShowedValues = this.getAllShowedValues(options); @@ -19,7 +20,13 @@ const isDisableArrow = disabled || diff --git a/components/virtuallist/demos/basic.md b/components/virtuallist/demos/basic.md index 817d80c5d..448fe4a4d 100644 --- a/components/virtuallist/demos/basic.md +++ b/components/virtuallist/demos/basic.md @@ -8,22 +8,50 @@ order: 0 ```vdt import { VirtualList } from 'kpc'; - -
- {$value.label} -
-
+
+

1. 定高元素

+ +
+ {$value.label} +
+
+ +

2. 不定高元素

+ +
+ {$value.label} +
+
+ +

3. 异步加载

+ +
+
{$value.label}
+ + {this.get(`expanded.${$value.value}`) ?
aaaaaa
: null} +
+
+
``` ```styl -.div-height +.fixed-height-item height 30px + border-bottom 1px solid #eee + padding 5px +.variable-height-item + min-height 20px + border-bottom 1px solid #eee + padding 5px ``` ```ts +import {bind} from 'kpc/components/utils'; interface Props { day?: string | null data: any[] + variableHeightData: any[] + expanded: any } export default class extends Component { @@ -32,19 +60,31 @@ export default class extends Component { static defaults() { return { day: null, - data: [] + data: [], + variableHeightData: [], + expanded: {} } as Props; } init() { const arr = []; + const variableHeightData = []; for (let index = 0; index < 10000; index++) { arr.push({ value: index, label: `测试${index}` }); + const repeatPart = '行内容'.repeat(Math.floor(Math.random() * 5) + 1); + variableHeightData.push({ value: index, label: `不定高度项 ${index}\n${repeatPart}` }); } - this.set({data: arr}); + this.set({data: arr, variableHeightData}); + } + + toggleItem(index) { + const expanded = this.get('expanded') || {}; + expanded[index] = !expanded[index]; + this.set('expanded', expanded); + console.log(`expanded[${index}]: ${expanded[index]}`); } } ``` diff --git a/components/virtuallist/index.ts b/components/virtuallist/index.ts index 2e07778ad..f02b79b58 100644 --- a/components/virtuallist/index.ts +++ b/components/virtuallist/index.ts @@ -1,30 +1,37 @@ import { Component, TypeDefs } from 'intact'; import template from './index.vdt'; -import { useVirtual } from './useVirtual3'; +import { useVirtual } from './useVirtual'; import { Events } from '../types'; import { useConfigContext } from '../config'; -export interface VirtualListProps {} +export interface VirtualListProps { + estimatedItemHeight?: number; + bufferSize?: number; + asyncLoading?: boolean; +} export interface VirtualListEvents {} const typeDefs: Required> = { - text: String, - showMessage: Boolean, + estimatedItemHeight: Number, + bufferSize: Number, + asyncLoading: Boolean, }; -// const defaults = (): Partial => ({ -// showMessage: true, -// }); +const defaults = (): Partial => ({ + estimatedItemHeight: 30, + bufferSize: 6, + asyncLoading: false, +}); const events: Events = {}; export class VirtualList extends Component { static template = template; static typeDefs = typeDefs; - // static defaults = defaults; - // static events = events; + static defaults = defaults; + static events = events; public virtual = useVirtual(); private config = useConfigContext(); -} +} \ No newline at end of file diff --git a/components/virtuallist/index.vdt b/components/virtuallist/index.vdt index 9915334b0..4f2ea29b9 100644 --- a/components/virtuallist/index.vdt +++ b/components/virtuallist/index.vdt @@ -13,7 +13,7 @@ const classNameObj = { [className]: className, } -
+
{virtualChildren.value} diff --git a/components/virtuallist/useVirtual.ts b/components/virtuallist/useVirtual.ts index 12aebc5fc..60d38aef6 100644 --- a/components/virtuallist/useVirtual.ts +++ b/components/virtuallist/useVirtual.ts @@ -1,35 +1,100 @@ -import { useInstance, onMounted, onUnmounted, createRef, Children, VNode, directClone } from 'intact'; +// 支持定高、不定高、动态元素 +import { useInstance, onMounted, onUnmounted, createRef, Children, VNode, directClone, watch, nextTick } from 'intact'; import { useState } from '../../hooks/useState'; import type { VirtualList } from './index'; -import { isComponentVNode } from '../utils'; -// import { Option } from './option'; export function useVirtual() { const instance = useInstance() as VirtualList; - const children: Children = instance.get('children'); if (!Array.isArray(children)) return {}; - const bufferSize = 6; - const containerRef = createRef(); + + const bufferSize = instance.get('bufferSize'); + // 预估元素高度 + const estimatedItemHeight = instance.get('estimatedItemHeight'); + // const asyncLoading = instance.get('asyncLoading'); + + const containerRef = createRef(); + const contentRef = createRef(); const startIndex = useState(0); const endIndex = useState(0); - const itemHeightState = useState(32); - const containerHeightState = useState(200); + const itemHeights = useState(new Map()); const virtualChildren = useState<(VNode | string | number)[]>([]); + const translateY = useState(0); + const totalHeight = useState(0); let ticking = false; + // const mutationObserver = new MutationObserver(() => { + // calculateHeights(); + // handleScroll(); + // }); + + // const resizeObserver = new ResizeObserver(() => { + // calculateHeights(); + // handleScroll(); + // }); + + const calculateHeights = () => { + if (contentRef.value) { + const nodes = contentRef.value.children as HTMLCollection; + for (let i = 0; i < nodes.length; i++) { + const index = startIndex.value + i; + const height = (nodes[i] as HTMLElement).offsetHeight; + itemHeights.value.set(index, height); + } + updateTotalHeight(); + } + }; + + const updateTotalHeight = () => { + let height = 0; + for (let i = 0; i < children.length; i++) { + height += itemHeights.value.get(i) || (estimatedItemHeight as number); + } + totalHeight.set(height); + }; + + const getItemTop = (index: number) => { + let top = 0; + for (let i = 0; i < index; i++) { + top += itemHeights.value.get(i) || (estimatedItemHeight as number); + } + return top; + }; + const handleScroll = () => { if (!ticking && containerRef.value) { ticking = true; requestAnimationFrame(() => { - const scrollTop = containerRef.value!.scrollTop; - const itemHeight = itemHeightState.value; - const containerHeight = containerHeightState.value; - const start = Math.floor(scrollTop / itemHeight); - const end = Math.min(children.length - 1, Math.ceil((scrollTop + containerHeight) / itemHeight)); - console.log('handleScroll', scrollTop, itemHeight, start, end, children.length); + const { scrollTop, clientHeight } = containerRef.value as HTMLElement; + let start = 0; + let end = 0; + let accumulatedHeight = 0; + + // Find start index + while (start < children.length) { + const height = itemHeights.value.get(start) || (estimatedItemHeight as number); + if (accumulatedHeight + height > scrollTop - (bufferSize as number) * (estimatedItemHeight as number)) { + break; + } + accumulatedHeight += height; + start++; + } + + start = Math.max(0, start); + + // Find end index + end = start; + while (end < children.length && accumulatedHeight < scrollTop + clientHeight + (bufferSize as number) * (estimatedItemHeight as number)) { + accumulatedHeight += itemHeights.value.get(end) || (estimatedItemHeight as number); + end++; + } + + end = Math.min(children.length, end); + startIndex.set(start); endIndex.set(end); + translateY.set(getItemTop(start)); + setVisibleItems(start, end); ticking = false; }); @@ -37,19 +102,33 @@ export function useVirtual() { }; const setVisibleItems = (start: number, end: number) => { - const visibleItems = []; - const startWithBuffer = Math.max(0, start - bufferSize); - const endWithBuffer = Math.min(children.length - 1, end + bufferSize); - for (let i = startWithBuffer; i <= endWithBuffer; i++) { - visibleItems.push(children[i] as VNode); - } + const visibleItems = children.slice(start, end).map(directClone); virtualChildren.set(visibleItems); + nextTick(() => { + calculateHeights(); + }); }; + // const observeItems = () => { + // if (contentRef.value) { + // mutationObserver.observe(contentRef.value, { childList: true, subtree: true }); + // resizeObserver.observe(contentRef.value); + // } + // }; + + // const unobserveItems = () => { + // mutationObserver.disconnect(); + // resizeObserver.disconnect(); + // }; + onMounted(() => { if (containerRef.value) { containerRef.value.addEventListener('scroll', handleScroll); + updateTotalHeight(); handleScroll(); + // if (asyncLoading) { + // observeItems(); + // } } }); @@ -57,12 +136,28 @@ export function useVirtual() { if (containerRef.value) { containerRef.value.removeEventListener('scroll', handleScroll); } + // unobserveItems(); }); + // watch(() => instance.get('children'), () => { + // itemHeights.set(new Map()); + // updateTotalHeight(); + // handleScroll(); + // }); + + // watch(() => instance.get('asyncLoading'), (newValue) => { + // if (newValue) { + // observeItems(); + // } else { + // unobserveItems(); + // } + // }); + return { containerRef, + contentRef, virtualChildren, - paddingTop: () => Math.max(0, startIndex.value - bufferSize) * itemHeightState.value, - paddingBottom: () => Math.max(0, (children.length - endIndex.value - 1 - bufferSize)) * itemHeightState.value, + translateY, + totalHeight, }; -} +} \ No newline at end of file diff --git a/components/virtuallist/useVirtual2.ts b/components/virtuallist/useVirtual2.ts deleted file mode 100644 index ee1af77cd..000000000 --- a/components/virtuallist/useVirtual2.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { useInstance, onMounted, onUnmounted, createRef, Children, VNode, directClone } from 'intact'; -import { useState } from '../../hooks/useState'; -import type { VirtualList } from './index'; -import { isComponentVNode } from '../utils'; -// import { Option } from './option'; - -export function useVirtual() { - const instance = useInstance() as VirtualList; - const children: Children = instance.get('children'); - if (!Array.isArray(children)) return {}; - const bufferSize = 6; - const containerRef = createRef(); - const contentRef = createRef(); - const startIndex = useState(0); - const endIndex = useState(0); - const itemHeights = useState(new Map()); - const estimatedHeight = 50; - const containerHeightState = useState(200); - const virtualChildren = useState<(VNode | string | number)[]>([]); - let ticking = false; - - const calculateHeights = () => { - if (contentRef.value) { - const nodes = contentRef.value.children as any; - const heightsMap = new Map(); - debugger; - for (let i = 0; i < nodes.length; i++) { - heightsMap.set(i, nodes[i].offsetHeight); - } - itemHeights.set(heightsMap); - } - }; - - const handleScroll = () => { - if (!ticking && containerRef.value) { - ticking = true; - requestAnimationFrame(() => { - const scrollTop = containerRef.value!.scrollTop; - let accumulatedHeight = 0; - let start = 0; - for (let i = 0; i < children.length; i++) { - accumulatedHeight += itemHeights.value.get(i) || estimatedHeight; - if (accumulatedHeight >= scrollTop) { - start = i; - break; - } - } - let end = start; - accumulatedHeight = 0; - for (let i = start; i < children.length; i++) { - accumulatedHeight += itemHeights.value.get(i) || estimatedHeight; - if (accumulatedHeight >= containerHeightState.value) { - end = i; - break; - } - } - startIndex.set(start); - endIndex.set(end); - setVisibleItems(start, end); - ticking = false; - }); - } - }; - - const setVisibleItems = (start: number, end: number) => { - const visibleItems = []; - const startWithBuffer = Math.max(0, start - bufferSize); - const endWithBuffer = Math.min(children.length - 1, end + bufferSize); - for (let i = startWithBuffer; i <= endWithBuffer; i++) { - visibleItems.push(children[i] as VNode); - } - virtualChildren.set(visibleItems); - }; - - onMounted(() => { - if (containerRef.value) { - calculateHeights(); - containerRef.value.addEventListener('scroll', handleScroll); - handleScroll(); - } - }); - - onUnmounted(() => { - if (containerRef.value) { - containerRef.value.removeEventListener('scroll', handleScroll); - } - }); - - const paddingTop = (): number => { - let height = 0; - for (let i = 0; i < Math.max(0, startIndex.value - bufferSize); i++) { - height += itemHeights.value.get(i) || estimatedHeight; - } - return height; - }; - - const paddingBottom = (): number => { - let height = 0; - for (let i = Math.min(children.length, endIndex.value + bufferSize + 1); i < children.length; i++) { - height += itemHeights.value.get(i) || estimatedHeight; - } - return height; - }; - - - return { - containerRef, - contentRef, - virtualChildren, - paddingTop, - paddingBottom, - }; -} diff --git a/components/virtuallist/useVirtual3.ts b/components/virtuallist/useVirtual3.ts deleted file mode 100644 index de5b1c757..000000000 --- a/components/virtuallist/useVirtual3.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { useInstance, onMounted, onUnmounted, createRef, Children, VNode, directClone } from 'intact'; -import { useState } from '../../hooks/useState'; -import type { VirtualList } from './index'; - -export function useVirtual(config?: { bufferSize?: number }) { - const instance = useInstance() as VirtualList; - const children: Children = instance.get('children'); - if (!Array.isArray(children)) return {}; - - const bufferSize = config?.bufferSize ?? 6; // 默认 bufferSize 为 6 - const containerRef = createRef(); - const contentRef = createRef(); - const startIndex = useState(0); - const endIndex = useState(0); - const itemHeights = useState(new Map()); - const estimatedHeight = 30; // 初始估计高度 - const containerHeightState = useState(200); - const virtualChildren = useState<(VNode | string | number)[]>([]); - const translateY = useState(0); // 记录偏移量 - const totalHeight = useState(1000); // 设置初始总高度为一个很大的值 - let ticking = false; - - const calculateHeights = () => { - if (contentRef.value) { - debugger; - const nodes = contentRef.value.children as HTMLCollection; - console.log('----',children,nodes); - const heightsMap = new Map(); - let totalHeightValue = 0; - for (let i = 0; i < nodes.length; i++) { - const height = (nodes[i] as HTMLElement).offsetHeight; // 自动计算高度 - heightsMap.set(i + startIndex.value, height); // 注意要加上 startIndex - totalHeightValue += height; - } - itemHeights.set(heightsMap); - - // 更新总高度 - let accumulatedTotalHeight = 0; - for (let i = 0; i < children.length; i++) { - accumulatedTotalHeight += itemHeights.value.get(i) || estimatedHeight; - } - totalHeight.set(accumulatedTotalHeight); - } - }; - - const handleScroll = () => { - if (!ticking && containerRef.value) { - ticking = true; - requestAnimationFrame(() => { - const scrollTop = containerRef.value!.scrollTop; - let accumulatedHeight = 0; - let start = 0; - for (let i = 0; i < children.length; i++) { - accumulatedHeight += itemHeights.value.get(i) || estimatedHeight; - if (accumulatedHeight >= scrollTop) { - start = i; - break; - } - } - let end = start; - accumulatedHeight = 0; - for (let i = start; i < children.length; i++) { - accumulatedHeight += itemHeights.value.get(i) || estimatedHeight; - if (accumulatedHeight >= containerHeightState.value) { - end = i; - break; - } - } - startIndex.set(start); - endIndex.set(end); - - // 计算 translateY 偏移量 - let translateYValue = 0; - for (let i = 0; i < start; i++) { - translateYValue += itemHeights.value.get(i) || estimatedHeight; - } - translateY.set(translateYValue); - - setVisibleItems(start, end); - ticking = false; - }); - } - }; - - const setVisibleItems = (start: number, end: number) => { - const visibleItems = []; - const startWithBuffer = Math.max(0, start - bufferSize); - const endWithBuffer = Math.min(children.length - 1, end + bufferSize); - for (let i = startWithBuffer; i <= endWithBuffer; i++) { - visibleItems.push(directClone(children[i] as VNode)); - } - virtualChildren.set(visibleItems); - - // 更新内容高度 - calculateHeights(); - }; - - onMounted(() => { - if (containerRef.value) { - calculateHeights(); // 初始计算高度 - - containerRef.value.addEventListener('scroll', handleScroll); - handleScroll(); // 初始化渲染可见项 - } - }); - - onUnmounted(() => { - if (containerRef.value) { - containerRef.value.removeEventListener('scroll', handleScroll); - } - }); - - return { - containerRef, - contentRef, - virtualChildren, - translateY, - totalHeight, // 新增总高度返回值 - }; -} From 6f1cd4b411cd0309635a96f3b76568a2b95cbef2 Mon Sep 17 00:00:00 2001 From: zhangbing4 Date: Sat, 7 Dec 2024 19:54:15 +0800 Subject: [PATCH 06/18] =?UTF-8?q?fix:=20=E8=99=9A=E6=8B=9F=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/dropdown/useKeyboard.ts | 3 + components/select/demos/virtual.md | 2 +- components/table/demos/virtual.md | 103 +++++++++++++++ components/table/row.ts | 4 +- components/table/row.vdt | 3 +- components/table/styles.ts | 21 ++++ components/table/table.ts | 14 +++ components/table/table.vdt | 36 +++++- components/table/useScroll.ts | 13 +- components/table/useTableVirtual.ts | 132 +++++++++++++++++++ components/virtuallist/demos/basic.md | 22 +--- components/virtuallist/demos/scrollTo.md | 82 ++++++++++++ components/virtuallist/helpers.ts | 119 ++++++++++++++++++ components/virtuallist/index.md | 12 +- components/virtuallist/index.ts | 16 ++- components/virtuallist/index.vdt | 10 +- components/virtuallist/styles.ts | 21 +++- components/virtuallist/useVirtual.ts | 154 +++++++++-------------- 18 files changed, 626 insertions(+), 141 deletions(-) create mode 100644 components/table/demos/virtual.md create mode 100644 components/table/useTableVirtual.ts create mode 100644 components/virtuallist/demos/scrollTo.md create mode 100644 components/virtuallist/helpers.ts diff --git a/components/dropdown/useKeyboard.ts b/components/dropdown/useKeyboard.ts index e8f6c0348..b91efdc5f 100644 --- a/components/dropdown/useKeyboard.ts +++ b/components/dropdown/useKeyboard.ts @@ -140,6 +140,7 @@ export function useMenuKeyboard() { const item = items[focusIndex]; if (focusIndex > -1 && item) { + // TODO(find bug) itemEvents.get(item)!.onFocusout(); focusIndex = -1; } @@ -203,6 +204,8 @@ export function useItemKeyboard(itemEvents: Omit - diff --git a/components/table/demos/virtual.md b/components/table/demos/virtual.md new file mode 100644 index 000000000..4741c055d --- /dev/null +++ b/components/table/demos/virtual.md @@ -0,0 +1,103 @@ +--- +title: 虚拟表格 +order: 36 +--- + +添加`virtual`属性,即可开启虚拟滚动模式,树形表格开启虚拟滚动需指定`rowKey` + +```vdt +import {Table, TableColumn} from 'kpc'; + +
+

表格

+ + + +
+

树形表格

+ data.name}> + + +
+
+``` + +```styl +.no-data-template + display: flex + gap: 10px +.icon + vertical-align middle + margin-right 10px + cursor pointer +.name + vertical-align middle +.expand + padding 16px +``` +```ts +interface Props { + data: any[] + variableHeightData: any[] +} +import {range, bind} from 'kpc/components/utils'; +export default class extends Component { + static template = template; + static defaults() { + return { + data: [], + variableHeightData: [] + } as Props; + } + init() { + const arr = []; + for (let index = 0; index < 10000; index++) { + arr.push({ + a: `Cell ${index}-1`, + b: `Cell ${index}-2` + }); + } + this.set({data: arr}); + + const generateTreeData = (count: number) => { + const result = []; + + for (let i = 0; i < count; i++) { + const hasChildren = Math.random() > 0.5; + const node: any = { + name: `Node-${i}`, + key: `node-${i}`, + size: Math.floor(Math.random() * 100), + }; + + if (hasChildren) { + node.children = Array.from({ length: 2 }, (_, j) => ({ + name: `Node-${i}-${j}`, + key: `node-${i}-${j}`, + size: Math.floor(Math.random() * 100), + children: Math.random() > 0.5 ? [ + { + name: `Node-${i}-${j}-0`, + key: `node-${i}-${j}-0`, + size: Math.floor(Math.random() * 100), + } + ] : undefined + })); + } + + result.push(node); + } + + return result; + }; + + const data = generateTreeData(10000); + this.set({ variableHeightData: data }); + } + + @bind + scrollToRowByKey() { + this.refs.table.scrollToRowByKey('Node-400'); + } +} +``` diff --git a/components/table/row.ts b/components/table/row.ts index 1e5f627ce..c7b0d7220 100644 --- a/components/table/row.ts +++ b/components/table/row.ts @@ -5,7 +5,8 @@ import { VNodeComponentClass, Props, Key, - IntactDom + IntactDom, + RefObject } from 'intact'; import template from './row.vdt'; import type {TableColumnProps} from './column'; @@ -40,6 +41,7 @@ export interface TableRowProps { offsetMap: Record animation: boolean loaded: boolean + rowRef: RefObject draggable: boolean draggingKey: TableRowKey | null diff --git a/components/table/row.vdt b/components/table/row.vdt index d7ff9b2a0..06ecc7dd1 100644 --- a/components/table/row.vdt +++ b/components/table/row.vdt @@ -12,6 +12,7 @@ const { allDisabled, selected, /* hidden, */spreaded, hasChildren, indent, key, offsetMap, draggable, draggingKey, animation, loaded, + rowRef } = this.get(); const { k } = this.config; @@ -107,5 +108,5 @@ const rows = animation ? 'ev-dragover': draggable ? this.onRowDragOver : null, 'ev-dragend': draggable ? this.onRowDragEnd : null, 'draggable': draggable ? true : null, - })} + }, undefined, rowRef)} diff --git a/components/table/styles.ts b/components/table/styles.ts index 9c42fb4ce..4317d6581 100644 --- a/components/table/styles.ts +++ b/components/table/styles.ts @@ -398,6 +398,27 @@ export const makeStyles = cache(function makeStyles(k: string) { bottom: 0; } } + &.${k}-virtual { + .${k}-table-wrapper { + position: relative; + height: 400px; + } + + tbody { + width: 100%; + will-change: transform; + } + + .${k}-table-phantom { + position: absolute; + left: 0; + top: 0; + right: 0; + z-index: -1; + pointer-events: none; + } + } + }; `; }); diff --git a/components/table/table.ts b/components/table/table.ts index 11ca6ee6c..0c9690b8e 100644 --- a/components/table/table.ts +++ b/components/table/table.ts @@ -23,6 +23,7 @@ import type {Events} from '../types'; import type {PaginationProps, PaginationChangeData} from '../pagination'; import { usePagination } from './usePagination'; import { useConfigContext } from '../config'; +import { useTableVirtual } from './useTableVirtual'; export interface TableProps< T = any, @@ -66,6 +67,9 @@ export interface TableProps< hideHeader?: boolean pagination?: boolean | PaginationProps fixFooter?: boolean + virtual?: boolean + estimatedRowHeight?: number; + bufferSize?: number; load?: (value: T) => Promise | void } @@ -134,6 +138,9 @@ const typeDefs: Required>> = { hideHeader: Boolean, pagination: [Boolean, Object], fixFooter: Boolean, + virtual: Boolean, + estimatedRowHeight: Number, + bufferSize: Number, load: Function, }; @@ -147,6 +154,8 @@ const defaults = (): Partial => ({ minColWidth: 40, animation: true, showIndeterminate: true, + estimatedRowHeight: 40, + bufferSize: 6, }); const events: Events = { @@ -182,6 +191,11 @@ export class Table< this.scroll.scrollRef, this.columns.getColumns, ); + private virtual = useTableVirtual( + this.pagination.data, + this.scroll.scrollRef, + this.scroll.callbacks, + ); private resizable = useResizable( this.scroll.scrollRef, this.width.tableRef, diff --git a/components/table/table.vdt b/components/table/table.vdt index da02ed4cc..585d1c13b 100644 --- a/components/table/table.vdt +++ b/components/table/table.vdt @@ -25,7 +25,7 @@ const { merge, childrenKey, indent, tooltipPosition, tooltipContainer, showIndeterminate, resizable, draggable, animation: _animation, hideHeader, - pagination, fixFooter, + pagination, fixFooter, virtual, spreadKeys } = this.get(); const animation = !Array.isArray(_animation) ? [_animation, _animation] : _animation; const {columns, cols, maxRows, maxCols} = this.columns.getData(); @@ -42,6 +42,7 @@ const classNameObj = { [`${k}-${type}`]: type && type !== 'default', [`${k}-stripe`]: stripe, [`${k}-with-expand`]: $blocks.expand, + [`${k}-virtual`]: virtual, [className]: className, [makeStyles(k)]: true, }; @@ -51,6 +52,7 @@ const style = isStringOrNumber(fixHeader) ? const {getWidth, tableRef, tableWidth} = this.width; const {start: onStart} = this.resizable; +const {totalHeight, translateY, startIndex, endIndex, getRowRef, addActualDataItem} = this.virtual; const colgroup = ( @@ -120,7 +122,7 @@ const {isSelected} = this.selected; const {loopData, isSpreaded, toggleSpreadRow} = this.tree; const {onRowDragStart, onRowDragOver, onRowDragEnd, draggingKey} = this.draggable; const tbody = ( - + {!hasData ? @@ -129,6 +131,15 @@ const tbody = ( : (() => { const rows = []; + let visibleIndex = -1; + // console.log('Virtual scroll params:', { + // startIndex: this.virtual?.startIndex.value, + // endIndex: this.virtual?.endIndex.value, + // translateY: this.virtual?.translateY.value + // }); + if (virtual) { + this.virtual.resetActualData(); + } loopData((value, index, level, hidden) => { // don't render if row is hidden if (hidden) return hidden; @@ -137,6 +148,21 @@ const tbody = ( const key = allKeys[index]; const spreaded = isSpreaded(key); const hasChildren = !!childrenKey && Array.isArray(value[childrenKey]); + const rowRef = virtual ? getRowRef(key) : null; + if (virtual) { + addActualDataItem(value); + visibleIndex++; + const isInView = + visibleIndex >= startIndex.value && + visibleIndex < endIndex.value; + + // Whether the element is in the visible area + if (!isInView) { + // The parent element is collapsed and should prevent traversal of child elements + return !spreaded; + } + } + const indentSize = indent ? indent * level : 0; let row =
void; +export type ScrollCallback = (scrollLeft: number, scrollTop?: number) => void; export function useScroll() { const callbacks: ScrollCallback[] = []; const scrollRef = createRef(); let scrollLeft = 0; + let scrollTop = 0; function onScroll() { - const newScrollLeft = scrollRef.value!.scrollLeft; - if (scrollLeft !== newScrollLeft) { + const element = scrollRef.value!; + const newScrollLeft = element.scrollLeft; + const newScrollTop = element.scrollTop; + + if (scrollLeft !== newScrollLeft || scrollTop !== newScrollTop) { scrollLeft = newScrollLeft; - callbacks.forEach(fn => fn(scrollLeft)); + scrollTop = newScrollTop; + callbacks.forEach(fn => fn(scrollLeft, scrollTop)); } } diff --git a/components/table/useTableVirtual.ts b/components/table/useTableVirtual.ts new file mode 100644 index 000000000..d0e7fb32d --- /dev/null +++ b/components/table/useTableVirtual.ts @@ -0,0 +1,132 @@ +import { useInstance, onMounted, onBeforeUnmount, RefObject, createRef } from 'intact'; +import { useState } from '../../hooks/useState'; +import type { Table } from './table'; +import type { ScrollCallback } from './useScroll'; +import { State } from '../../hooks/useState'; +import { createHeightManager } from '../virtuallist/helpers'; + +interface RowRefCache { + [key: string]: RefObject; +} + +export function useTableVirtual( + data: State, + scrollRef: RefObject, + callbacks: ScrollCallback[], +) { + const instance = useInstance() as Table; + const { virtual, rowKey, estimatedRowHeight, bufferSize } = instance.get(); + + if (!virtual) return null; + + const startIndex = useState(0); + const endIndex = useState(0); + const translateY = useState(0); + const totalHeight = useState(0); + + // row refs + const rowRefs: RowRefCache = {}; + // actual data because data don't loop children + let actualData: any[] = []; + // height manager for calculate total height and update item heights + const { + batchUpdate, + calculateTotalHeight, + calculateVisibleRange, + } = createHeightManager({ + defaultHeight: estimatedRowHeight as number, + onTotalHeightChange: (height) => totalHeight.set(height), + getItems: () => actualData, + getKey: (item, index) => getRowKey(item, index) + }); + + const calculateHeights = () => { + const visibleElements = []; + for (let i = startIndex.value; i < endIndex.value; i++) { + if (actualData[i]) { + const key = getRowKey(actualData[i], i); + const element = rowRefs[key]?.value; + if (element) { + visibleElements.push({ key, element }); + } + } + } + batchUpdate(visibleElements); + }; + + // reset and start collecting actual rendered data + const resetActualData = () => { + actualData = []; + }; + + // add actual rendered data item + const addActualDataItem = (item: any) => { + actualData.push(item); + }; + // get or create row ref + const getRowRef = (key: string) => { + if (!rowRefs[key]) { + rowRefs[key] = createRef(); + } + return rowRefs[key]; + }; + + const getRowKey = (item: any, index: number) => { + return typeof rowKey === 'function' ? + rowKey(item, index) : + item['key']; + }; + + const handleScroll = (_scrollLeft?: number, scrollTop: number = 0) => { + if (!scrollRef.value || !actualData) return; + + const { start, end, translateY: offset } = calculateVisibleRange( + scrollTop, + scrollRef.value.clientHeight, + bufferSize as number + ); + + startIndex.set(start); + endIndex.set(end); + translateY.set(offset); + }; + + callbacks.push(handleScroll); + + onMounted(() => { + if (actualData) { + calculateTotalHeight(); + handleScroll(0, 0); + } + let updateQueued = false; + const observer = new MutationObserver(() => { + if (!updateQueued) { + updateQueued = true; + requestAnimationFrame(() => { + calculateHeights(); + updateQueued = false; + }); + } + }); + + if (scrollRef.value) { + observer.observe(scrollRef.value, { + childList: true, + subtree: true, + attributes: true + }); + } + + return () => observer.disconnect(); + }); + + return { + startIndex, + endIndex, + translateY, + totalHeight, + getRowRef, + resetActualData, + addActualDataItem, + }; +} \ No newline at end of file diff --git a/components/virtuallist/demos/basic.md b/components/virtuallist/demos/basic.md index 448fe4a4d..95e6f4b08 100644 --- a/components/virtuallist/demos/basic.md +++ b/components/virtuallist/demos/basic.md @@ -3,34 +3,23 @@ title: 基本用法 order: 0 --- -组件接收任意合法的字符串当做`text`值 - ```vdt import { VirtualList } from 'kpc';

1. 定高元素

- +
{$value.label}

2. 不定高元素

- +
{$value.label}
- -

3. 异步加载

- -
-
{$value.label}
- - {this.get(`expanded.${$value.value}`) ?
aaaaaa
: null} -
-
``` @@ -79,12 +68,5 @@ export default class extends Component { } this.set({data: arr, variableHeightData}); } - - toggleItem(index) { - const expanded = this.get('expanded') || {}; - expanded[index] = !expanded[index]; - this.set('expanded', expanded); - console.log(`expanded[${index}]: ${expanded[index]}`); - } } ``` diff --git a/components/virtuallist/demos/scrollTo.md b/components/virtuallist/demos/scrollTo.md new file mode 100644 index 000000000..411aced13 --- /dev/null +++ b/components/virtuallist/demos/scrollTo.md @@ -0,0 +1,82 @@ +--- +title: 滑动到固定位置 +order: 1 +--- + +可以通过实例方法`scrollToIndex`滚动到具体位置 + +```vdt +import { VirtualList, Button } from 'kpc'; + +
+
+ + +
+ +
+ {$value.label} +
+
+
+``` + +```styl +.fixed-height-item + height 30px + border-bottom 1px solid #eee + padding 5px +.k-btn + margin-right 8px +``` + +```ts +import {bind} from 'kpc/components/utils'; +interface Props { + day?: string | null + virtualListData: any[] + variableHeightData: any[] + expanded: any +} + +export default class extends Component { + static template = template; + + static defaults() { + return { + day: null, + virtualListData: [], + variableHeightData: [], + expanded: {} + } as Props; + } + + init() { + const arr = []; + const variableHeightData = []; + for (let index = 0; index < 10000; index++) { + arr.push({ + value: index, + label: `测试${index}` + }); + const repeatPart = '行内容'.repeat(Math.floor(Math.random() * 5) + 1); + variableHeightData.push({ value: index, label: `不定高度项 ${index}\n${repeatPart}` }); + } + this.set({virtualListData: arr, variableHeightData}); + } + + scrollToMiddle() { + // 滚动到中间位置 + const list = this.refs.fixedList as any; + const middleIndex = Math.floor((this.get('virtualListData') as any[]).length / 2); + list.scrollToIndex(middleIndex); + } + + scrollToEnd() { + // 滚动到底部 + const list = this.refs.fixedList as any; + const lastIndex = (this.get('virtualListData') as any[]).length - 1; + list.scrollToIndex(lastIndex, 'smooth'); + } +} +``` diff --git a/components/virtuallist/helpers.ts b/components/virtuallist/helpers.ts new file mode 100644 index 000000000..8c2b243ab --- /dev/null +++ b/components/virtuallist/helpers.ts @@ -0,0 +1,119 @@ +import { useState } from '../../hooks/useState'; +import { Children } from 'intact'; + +export interface HeightManagerOptions { + defaultHeight: number; + onTotalHeightChange: (height: number) => void; + getItems: () => Children[] | any[]; + getKey?: (item: any, index: number) => string | number; +} + +export interface HeightManager { + batchUpdate: (elements: { key: string | number; element: HTMLElement }[]) => void; + getItemHeight: (key: string | number) => number; + clear: () => void; + getAllHeights: () => number[]; + calculateTotalHeight: () => number; + calculateVisibleRange: (scrollTop: number, clientHeight: number, bufferSize: number) => VisibleRange; +} + +export interface VisibleRange { + start: number; + end: number; + translateY: number; +} + +// 创建高度管理器 +export const createHeightManager = (options: HeightManagerOptions): HeightManager => { + const { defaultHeight, onTotalHeightChange, getItems, getKey } = options; + const cache = new Map(); + + const calculateTotalHeight = () => { + const items = getItems(); + const itemCount = Array.isArray(items) ? items.length : items; + let height = 0; + + for (let i = 0; i < itemCount; i++) { + height += cache.get(getKey ? getKey(items[i], i) : i) || defaultHeight; + } + onTotalHeightChange(height); + return height; + }; + + const batchUpdate = ( + elements: { key: string | number; element: HTMLElement }[] + ) => { + let hasUpdates = false; + + elements.forEach(({ key, element }) => { + const height = element.offsetHeight; + if (height > 0 && cache.get(key) !== height) { + cache.set(key, height); + hasUpdates = true; + } + }); + + if (hasUpdates) { + calculateTotalHeight(); + } + }; + + const getItemHeight = (key: string | number) => cache.get(key) || defaultHeight; + + const clear = () => { + cache.clear(); + calculateTotalHeight(); + }; + + const getAllHeights = () => Array.from(cache.values()); + + const calculateVisibleRange = ( + scrollTop: number, + clientHeight: number, + bufferSize: number + ): VisibleRange => { + const items = getItems(); + const itemCount = Array.isArray(items) ? items.length : items; + let accumulatedHeight = 0; + let start = 0; + + // 查找起始索引 + while (start < itemCount) { + const key = getKey ? getKey(items[start], start) : start; + const height = cache.get(key) || defaultHeight; + if (accumulatedHeight + height > scrollTop - (bufferSize * defaultHeight)) { + break; + } + accumulatedHeight += height; + start++; + } + + // 查找结束索引 + let end = start; + let visibleHeight = 0; + while ( + end < itemCount && + visibleHeight < clientHeight + (2 * bufferSize * defaultHeight) + ) { + const key = getKey ? getKey(items[end], end) : end; + visibleHeight += cache.get(key) || defaultHeight; + end++; + } + + return { + start: Math.max(0, start), + end: Math.min(itemCount, end), + translateY: accumulatedHeight + }; + }; + + return { + batchUpdate, + getItemHeight, + clear, + getAllHeights, + calculateTotalHeight, + calculateVisibleRange, + }; +}; + diff --git a/components/virtuallist/index.md b/components/virtuallist/index.md index 4b3850ce2..94819ccfc 100644 --- a/components/virtuallist/index.md +++ b/components/virtuallist/index.md @@ -9,13 +9,13 @@ sidebar: doc | 属性 | 说明 | 类型 | 默认值 | | --- | --- | --- | --- | -| text | 复制文案值 | `string` | `undefined` | -| showMessage | 复制成功/失败时,是否展示Message | `boolean` | `true` | +| estimatedItemHeight | 列表每行预估高度 | `number` | `40` | +| bufferSize | 虚拟列表缓冲区数量 | `number` | `6` | +| height | 虚拟滚动区域的高度,不设置则继承父级容器高度 | `number` | `string` | `undefined` | -# 事件 +# 方法 -| 事件名 | 说明 | 参数 | +| 方法名 | 说明 | 参数 | | --- | --- | --- | -| success | 复制成功时触发 | `(value: string) => void` | -| error | 复制失败时触发 | `() => void` | +| scrollToIndex | 滚动到指定索引位置 | `(index: number, behavior?: 'auto' \| 'smooth') => void` | diff --git a/components/virtuallist/index.ts b/components/virtuallist/index.ts index f02b79b58..5925ee585 100644 --- a/components/virtuallist/index.ts +++ b/components/virtuallist/index.ts @@ -7,7 +7,7 @@ import { useConfigContext } from '../config'; export interface VirtualListProps { estimatedItemHeight?: number; bufferSize?: number; - asyncLoading?: boolean; + height?: number | string; } export interface VirtualListEvents {} @@ -15,13 +15,12 @@ export interface VirtualListEvents {} const typeDefs: Required> = { estimatedItemHeight: Number, bufferSize: Number, - asyncLoading: Boolean, + height: [Number, String], }; const defaults = (): Partial => ({ estimatedItemHeight: 30, bufferSize: 6, - asyncLoading: false, }); const events: Events = {}; @@ -34,4 +33,15 @@ export class VirtualList extends Component public virtual = useVirtual(); private config = useConfigContext(); + + scrollToIndex(index: number, behavior: ScrollBehavior = 'auto') { + const { containerRef, getItemTop } = this.virtual; + if (containerRef?.value) { + const top = getItemTop(index); + containerRef.value.scrollTo({ + top, + behavior, + }); + } + } } \ No newline at end of file diff --git a/components/virtuallist/index.vdt b/components/virtuallist/index.vdt index 4f2ea29b9..ef67f7b53 100644 --- a/components/virtuallist/index.vdt +++ b/components/virtuallist/index.vdt @@ -1,9 +1,9 @@ import { Icon } from '../icon'; -import { getRestProps } from '../utils'; +import { addStyle, getRestProps } from '../utils'; import { makeStyles } from './styles'; import { _$ } from '../../i18n'; -const { children, className } = this.get(); +const { children, className, style, type, height } = this.get(); const {containerRef, contentRef, virtualChildren, translateY, totalHeight} = this.virtual; const { k } = this.config; @@ -12,8 +12,12 @@ const classNameObj = { [makeStyles(k)]: true, [className]: className, } +const _style = { + overflow: 'auto', + ...(height && { maxHeight: `${height}px` }), +} -
+
{virtualChildren.value} diff --git a/components/virtuallist/styles.ts b/components/virtuallist/styles.ts index fcd08c57b..e8cf04867 100644 --- a/components/virtuallist/styles.ts +++ b/components/virtuallist/styles.ts @@ -4,7 +4,24 @@ import { cache } from '../utils'; export const makeStyles = cache(function makeStyles(k: string) { return css` - cursor: pointer; - vertical-align: middle; + &.${k}-virtual { + position: relative; + overflow: auto; + height: inherit; + min-height: inherit; + max-height: inherit; + + // .${k}-virtual-content { + // position: relative; + // width: 100%; + // } + + // .${k}-virtual-list { + // position: absolute; + // width: 100%; + // left: 0; + // top: 0; + // } + } ` }); diff --git a/components/virtuallist/useVirtual.ts b/components/virtuallist/useVirtual.ts index 60d38aef6..5c92842d4 100644 --- a/components/virtuallist/useVirtual.ts +++ b/components/virtuallist/useVirtual.ts @@ -1,62 +1,64 @@ -// 支持定高、不定高、动态元素 -import { useInstance, onMounted, onUnmounted, createRef, Children, VNode, directClone, watch, nextTick } from 'intact'; +import { useInstance, onMounted, onUnmounted, createRef, Children, VNode, directClone, watch, nextTick, RefObject } from 'intact'; import { useState } from '../../hooks/useState'; import type { VirtualList } from './index'; - -export function useVirtual() { +import { createHeightManager } from './helpers'; +export interface VirtualProps { + containerRef: RefObject; + contentRef: RefObject; + children: Children; + estimatedItemHeight: number | undefined; + bufferSize: number | undefined; +} +export function useVirtual(props?: VirtualProps) { const instance = useInstance() as VirtualList; - const children: Children = instance.get('children'); - if (!Array.isArray(children)) return {}; - - const bufferSize = instance.get('bufferSize'); - // 预估元素高度 - const estimatedItemHeight = instance.get('estimatedItemHeight'); - // const asyncLoading = instance.get('asyncLoading'); + const {estimatedItemHeight, bufferSize} = instance.get(); + + // 如果没有传入 props,则使用组件自身的配置 + const getChildren = () => { + const children = instance.get('children'); + return Array.isArray(children) ? children : []; + }; + if (!Array.isArray(getChildren())) return {}; const containerRef = createRef(); const contentRef = createRef(); + const startIndex = useState(0); const endIndex = useState(0); - const itemHeights = useState(new Map()); - const virtualChildren = useState<(VNode | string | number)[]>([]); const translateY = useState(0); const totalHeight = useState(0); - let ticking = false; - - // const mutationObserver = new MutationObserver(() => { - // calculateHeights(); - // handleScroll(); - // }); - // const resizeObserver = new ResizeObserver(() => { - // calculateHeights(); - // handleScroll(); - // }); + const virtualChildren = useState<(VNode | string | number)[]>([]); + + let ticking = false; + // height manager for calculate total height and update item heights + const { + batchUpdate, + getItemHeight, + clear, + calculateTotalHeight, + calculateVisibleRange, + } = createHeightManager({ + defaultHeight: estimatedItemHeight as number, + onTotalHeightChange: (height) => totalHeight.set(height), + getItems: () => getChildren() + }); const calculateHeights = () => { if (contentRef.value) { - const nodes = contentRef.value.children as HTMLCollection; - for (let i = 0; i < nodes.length; i++) { - const index = startIndex.value + i; - const height = (nodes[i] as HTMLElement).offsetHeight; - itemHeights.value.set(index, height); - } - updateTotalHeight(); - } - }; - - const updateTotalHeight = () => { - let height = 0; - for (let i = 0; i < children.length; i++) { - height += itemHeights.value.get(i) || (estimatedItemHeight as number); + const visibleElements = Array.from(contentRef.value.children).map((node, i) => ({ + key: startIndex.value + i, + element: node as HTMLElement, + })); + + batchUpdate(visibleElements); } - totalHeight.set(height); }; const getItemTop = (index: number) => { let top = 0; for (let i = 0; i < index; i++) { - top += itemHeights.value.get(i) || (estimatedItemHeight as number); + top += getItemHeight(i); } return top; }; @@ -66,35 +68,15 @@ export function useVirtual() { ticking = true; requestAnimationFrame(() => { const { scrollTop, clientHeight } = containerRef.value as HTMLElement; - let start = 0; - let end = 0; - let accumulatedHeight = 0; - - // Find start index - while (start < children.length) { - const height = itemHeights.value.get(start) || (estimatedItemHeight as number); - if (accumulatedHeight + height > scrollTop - (bufferSize as number) * (estimatedItemHeight as number)) { - break; - } - accumulatedHeight += height; - start++; - } - - start = Math.max(0, start); - - // Find end index - end = start; - while (end < children.length && accumulatedHeight < scrollTop + clientHeight + (bufferSize as number) * (estimatedItemHeight as number)) { - accumulatedHeight += itemHeights.value.get(end) || (estimatedItemHeight as number); - end++; - } - - end = Math.min(children.length, end); + const { start, end, translateY: offset } = calculateVisibleRange( + scrollTop, + clientHeight, + bufferSize as number + ); startIndex.set(start); endIndex.set(end); - translateY.set(getItemTop(start)); - + translateY.set(offset); setVisibleItems(start, end); ticking = false; }); @@ -102,33 +84,19 @@ export function useVirtual() { }; const setVisibleItems = (start: number, end: number) => { - const visibleItems = children.slice(start, end).map(directClone); + const children = getChildren(); + const visibleItems = children.slice(start, end).map(vNode => directClone(vNode as VNode)); virtualChildren.set(visibleItems); nextTick(() => { calculateHeights(); }); }; - // const observeItems = () => { - // if (contentRef.value) { - // mutationObserver.observe(contentRef.value, { childList: true, subtree: true }); - // resizeObserver.observe(contentRef.value); - // } - // }; - - // const unobserveItems = () => { - // mutationObserver.disconnect(); - // resizeObserver.disconnect(); - // }; - onMounted(() => { if (containerRef.value) { containerRef.value.addEventListener('scroll', handleScroll); - updateTotalHeight(); + calculateTotalHeight(); handleScroll(); - // if (asyncLoading) { - // observeItems(); - // } } }); @@ -136,22 +104,15 @@ export function useVirtual() { if (containerRef.value) { containerRef.value.removeEventListener('scroll', handleScroll); } - // unobserveItems(); }); - // watch(() => instance.get('children'), () => { - // itemHeights.set(new Map()); - // updateTotalHeight(); - // handleScroll(); - // }); - - // watch(() => instance.get('asyncLoading'), (newValue) => { - // if (newValue) { - // observeItems(); - // } else { - // unobserveItems(); - // } - // }); + instance.on('$receive:children', () => { + clear(); + calculateTotalHeight(); + nextTick(() => { + handleScroll(); + }); + }); return { containerRef, @@ -159,5 +120,6 @@ export function useVirtual() { virtualChildren, translateY, totalHeight, + getItemTop, }; } \ No newline at end of file From 94ead53311dc626ca0abcafdd1a8a90be63dcfa0 Mon Sep 17 00:00:00 2001 From: zhangbing4 Date: Mon, 9 Dec 2024 14:46:08 +0800 Subject: [PATCH 07/18] fix: Modify comments --- components/virtuallist/helpers.ts | 6 +++--- components/virtuallist/useVirtual.ts | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/components/virtuallist/helpers.ts b/components/virtuallist/helpers.ts index 8c2b243ab..65531286d 100644 --- a/components/virtuallist/helpers.ts +++ b/components/virtuallist/helpers.ts @@ -23,7 +23,7 @@ export interface VisibleRange { translateY: number; } -// 创建高度管理器 +// create height manager export const createHeightManager = (options: HeightManagerOptions): HeightManager => { const { defaultHeight, onTotalHeightChange, getItems, getKey } = options; const cache = new Map(); @@ -77,7 +77,7 @@ export const createHeightManager = (options: HeightManagerOptions): HeightManage let accumulatedHeight = 0; let start = 0; - // 查找起始索引 + // find start index while (start < itemCount) { const key = getKey ? getKey(items[start], start) : start; const height = cache.get(key) || defaultHeight; @@ -88,7 +88,7 @@ export const createHeightManager = (options: HeightManagerOptions): HeightManage start++; } - // 查找结束索引 + // find end index let end = start; let visibleHeight = 0; while ( diff --git a/components/virtuallist/useVirtual.ts b/components/virtuallist/useVirtual.ts index 5c92842d4..0cbd57705 100644 --- a/components/virtuallist/useVirtual.ts +++ b/components/virtuallist/useVirtual.ts @@ -13,7 +13,6 @@ export function useVirtual(props?: VirtualProps) { const instance = useInstance() as VirtualList; const {estimatedItemHeight, bufferSize} = instance.get(); - // 如果没有传入 props,则使用组件自身的配置 const getChildren = () => { const children = instance.get('children'); return Array.isArray(children) ? children : []; From 251766e6e847a6c558d7be8c8b63a4030c18acb8 Mon Sep 17 00:00:00 2001 From: zhangbing4 Date: Tue, 24 Dec 2024 18:16:45 +0800 Subject: [PATCH 08/18] fix: optimize virtual list --- components/table/demos/virtual.md | 6 +- components/table/table.ts | 10 +- components/table/table.vdt | 29 +---- components/table/useTableVirtual.ts | 2 +- components/virtuallist/container.ts | 48 ++++++++ components/virtuallist/container.vdt | 31 ++++++ components/virtuallist/demos/scrollTo.md | 23 ++-- components/virtuallist/dynamicVirtual.ts | 13 +++ components/virtuallist/helpers.ts | 25 +++-- components/virtuallist/index.ts | 51 +-------- components/virtuallist/rows.ts | 85 ++++++++++++++ components/virtuallist/styles.ts | 31 +++--- components/virtuallist/useVirtual.ts | 116 +++++++++++++++----- components/virtuallist/useVirtualContext.ts | 28 +++++ components/virtuallist/virtual.ts | 34 ++++++ components/virtuallist/virtual.vdt | 15 +++ components/virtuallist/wrapper.ts | 20 ++++ components/virtuallist/wrapper.vdt | 22 ++++ index.ts | 2 +- 19 files changed, 436 insertions(+), 155 deletions(-) create mode 100644 components/virtuallist/container.ts create mode 100644 components/virtuallist/container.vdt create mode 100644 components/virtuallist/dynamicVirtual.ts create mode 100644 components/virtuallist/rows.ts create mode 100644 components/virtuallist/useVirtualContext.ts create mode 100644 components/virtuallist/virtual.ts create mode 100644 components/virtuallist/virtual.vdt create mode 100644 components/virtuallist/wrapper.ts create mode 100644 components/virtuallist/wrapper.vdt diff --git a/components/table/demos/virtual.md b/components/table/demos/virtual.md index 4741c055d..1509d5527 100644 --- a/components/table/demos/virtual.md +++ b/components/table/demos/virtual.md @@ -10,7 +10,7 @@ import {Table, TableColumn} from 'kpc';

表格

- +
@@ -51,7 +51,7 @@ export default class extends Component { } init() { const arr = []; - for (let index = 0; index < 10000; index++) { + for (let index = 0; index < 100; index++) { arr.push({ a: `Cell ${index}-1`, b: `Cell ${index}-2` @@ -91,7 +91,7 @@ export default class extends Component { return result; }; - const data = generateTreeData(10000); + const data = generateTreeData(100); this.set({ variableHeightData: data }); } diff --git a/components/table/table.ts b/components/table/table.ts index 55c6ecaf7..f68147faf 100644 --- a/components/table/table.ts +++ b/components/table/table.ts @@ -192,11 +192,11 @@ export class Table< this.scroll.scrollRef, this.columns.getColumns, ); - private virtual = useTableVirtual( - this.pagination.data, - this.scroll.scrollRef, - this.scroll.callbacks, - ); + // private virtual = useTableVirtual( + // this.pagination.data, + // this.scroll.scrollRef, + // this.scroll.callbacks, + // ); private resizable = useResizable( this.scroll.scrollRef, this.width.tableRef, diff --git a/components/table/table.vdt b/components/table/table.vdt index 13210aaa9..034f42e6f 100644 --- a/components/table/table.vdt +++ b/components/table/table.vdt @@ -17,6 +17,7 @@ import {AllCheckedStatus} from './useChecked'; import {context as ResizableContext} from './useResizable'; import {context as FixedColumnsContext} from './useFixedColumns'; import {Pagination} from '../pagination'; +import {VirtualListContainer,VirtualListWrapper,VirtualListRows} from '../virtualList'; const { data, children, className, fixHeader, @@ -53,7 +54,6 @@ const style = isStringOrNumber(fixHeader) ? const {getWidth, tableRef, tableWidth} = this.width; const {start: onStart} = this.resizable; -const {totalHeight, translateY, startIndex, endIndex, getRowRef, addActualDataItem} = this.virtual; const colgroup = ( @@ -123,7 +123,7 @@ const {isSelected} = this.selected; const {loopData, isSpreaded, toggleSpreadRow} = this.tree; const {onRowDragStart, onRowDragOver, onRowDragEnd, draggingKey} = this.draggable; const tbody = ( - + {!hasData ? @@ -132,15 +132,6 @@ const tbody = ( : (() => { const rows = []; - let visibleIndex = -1; - // console.log('Virtual scroll params:', { - // startIndex: this.virtual?.startIndex.value, - // endIndex: this.virtual?.endIndex.value, - // translateY: this.virtual?.translateY.value - // }); - if (virtual) { - this.virtual.resetActualData(); - } loopData((value, index, level, hidden) => { // don't render if row is hidden if (hidden) return hidden; @@ -149,20 +140,6 @@ const tbody = ( const key = allKeys[index]; const spreaded = isSpreaded(key); const hasChildren = !!childrenKey && Array.isArray(value[childrenKey]); - const rowRef = virtual ? getRowRef(key) : null; - if (virtual) { - addActualDataItem(value); - visibleIndex++; - const isInView = - visibleIndex >= startIndex.value && - visibleIndex < endIndex.value; - - // Whether the element is in the visible area - if (!isInView) { - // The parent element is collapsed and should prevent traversal of child elements - return !spreaded; - } - } const indentSize = indent ? indent * level : 0; let row = {tbody} -
; diff --git a/components/virtuallist/container.ts b/components/virtuallist/container.ts new file mode 100644 index 000000000..2cec8a7a6 --- /dev/null +++ b/components/virtuallist/container.ts @@ -0,0 +1,48 @@ +import { Component, TypeDefs } from 'intact'; +import template from './container.vdt'; +import { Events } from '../types'; +import { useConfigContext } from '../config'; +import { useVirtual } from './useVirtual'; + +export interface VirtualListContainerProps { + estimatedItemHeight?: number; + bufferSize?: number; + height?: number | string; +} + +export interface VirtualListContainerEvents {} + +const typeDefs: Required> = { + height: [Number, String], + estimatedItemHeight: Number, + bufferSize: Number, +}; + +const defaults = (): Partial => ({ + estimatedItemHeight: 30, + bufferSize: 6, +}); + +const events: Events = {}; + +export class VirtualListContainer extends Component { + static template = template; + static typeDefs = typeDefs; + static defaults = defaults; + static events = events; + + private config = useConfigContext(); + private virtualState = useVirtual(); + + + public scrollToIndex(index: number, behavior: ScrollBehavior = 'auto') { + const { containerRef, getItemTop } = this.virtualState; + if (containerRef?.value) { + const top = getItemTop(index); + containerRef.value.scrollTo({ + top, + behavior, + }); + } + } +} \ No newline at end of file diff --git a/components/virtuallist/container.vdt b/components/virtuallist/container.vdt new file mode 100644 index 000000000..93bf8bd1a --- /dev/null +++ b/components/virtuallist/container.vdt @@ -0,0 +1,31 @@ +import { addStyle, getRestProps } from '../utils'; +import { makeStyles } from './styles'; +import { VirtualContext } from './useVirtualContext'; + +const { children, className, style, height } = this.get(); +const virtualState = this.virtualState; +const { k } = this.config; + +const classNameObj = { + [`${k}-virtual`]: true, + [`${k}-virtual-container`]: true, + [makeStyles(k)]: true, + [className]: className, +} + +const _style = { + overflow: 'auto', + ...(height && { height: `${height}px` }), +} + + +
+ {children} +
+
+
diff --git a/components/virtuallist/demos/scrollTo.md b/components/virtuallist/demos/scrollTo.md index 411aced13..ed7cce860 100644 --- a/components/virtuallist/demos/scrollTo.md +++ b/components/virtuallist/demos/scrollTo.md @@ -13,11 +13,13 @@ import { VirtualList, Button } from 'kpc';
- -
- {$value.label} -
-
+
+ +
+ {$value.label} +
+
+
``` @@ -33,10 +35,7 @@ import { VirtualList, Button } from 'kpc'; ```ts import {bind} from 'kpc/components/utils'; interface Props { - day?: string | null virtualListData: any[] - variableHeightData: any[] - expanded: any } export default class extends Component { @@ -44,25 +43,19 @@ export default class extends Component { static defaults() { return { - day: null, virtualListData: [], - variableHeightData: [], - expanded: {} } as Props; } init() { const arr = []; - const variableHeightData = []; for (let index = 0; index < 10000; index++) { arr.push({ value: index, label: `测试${index}` }); - const repeatPart = '行内容'.repeat(Math.floor(Math.random() * 5) + 1); - variableHeightData.push({ value: index, label: `不定高度项 ${index}\n${repeatPart}` }); } - this.set({virtualListData: arr, variableHeightData}); + this.set({virtualListData: arr}); } scrollToMiddle() { diff --git a/components/virtuallist/dynamicVirtual.ts b/components/virtuallist/dynamicVirtual.ts new file mode 100644 index 000000000..a18b0943c --- /dev/null +++ b/components/virtuallist/dynamicVirtual.ts @@ -0,0 +1,13 @@ +import {createVNode as h, ComponentFunction, ComponentConstructor} from 'intact'; +import {isUndefined} from 'intact-shared'; + +export type DynamicVirtualProps = { + tagName: string | ComponentConstructor, +} + +export const DynamicVirtual: ComponentFunction = ({tagName, ...props}) => { + if (isUndefined(tagName)) { + tagName = 'div'; + } + return h(tagName, props); +} diff --git a/components/virtuallist/helpers.ts b/components/virtuallist/helpers.ts index 65531286d..a068a0419 100644 --- a/components/virtuallist/helpers.ts +++ b/components/virtuallist/helpers.ts @@ -1,4 +1,3 @@ -import { useState } from '../../hooks/useState'; import { Children } from 'intact'; export interface HeightManagerOptions { @@ -76,11 +75,11 @@ export const createHeightManager = (options: HeightManagerOptions): HeightManage const itemCount = Array.isArray(items) ? items.length : items; let accumulatedHeight = 0; let start = 0; + let end = 0; // find start index while (start < itemCount) { - const key = getKey ? getKey(items[start], start) : start; - const height = cache.get(key) || defaultHeight; + const height = cache.get(start) || defaultHeight; if (accumulatedHeight + height > scrollTop - (bufferSize * defaultHeight)) { break; } @@ -89,21 +88,25 @@ export const createHeightManager = (options: HeightManagerOptions): HeightManage } // find end index - let end = start; - let visibleHeight = 0; + start = Math.max(0, start); + const startOffset = accumulatedHeight; + + // 查找结束索引 + end = start; while ( end < itemCount && - visibleHeight < clientHeight + (2 * bufferSize * defaultHeight) + accumulatedHeight < scrollTop + clientHeight + (bufferSize * defaultHeight) ) { - const key = getKey ? getKey(items[end], end) : end; - visibleHeight += cache.get(key) || defaultHeight; + accumulatedHeight += cache.get(end) || defaultHeight; end++; } + end = Math.min(itemCount, end); + return { - start: Math.max(0, start), - end: Math.min(itemCount, end), - translateY: accumulatedHeight + start, + end, + translateY: startOffset }; }; diff --git a/components/virtuallist/index.ts b/components/virtuallist/index.ts index 5925ee585..619bcdd30 100644 --- a/components/virtuallist/index.ts +++ b/components/virtuallist/index.ts @@ -1,47 +1,4 @@ -import { Component, TypeDefs } from 'intact'; -import template from './index.vdt'; -import { useVirtual } from './useVirtual'; -import { Events } from '../types'; -import { useConfigContext } from '../config'; - -export interface VirtualListProps { - estimatedItemHeight?: number; - bufferSize?: number; - height?: number | string; -} - -export interface VirtualListEvents {} - -const typeDefs: Required> = { - estimatedItemHeight: Number, - bufferSize: Number, - height: [Number, String], -}; - -const defaults = (): Partial => ({ - estimatedItemHeight: 30, - bufferSize: 6, -}); - -const events: Events = {}; - -export class VirtualList extends Component { - static template = template; - static typeDefs = typeDefs; - static defaults = defaults; - static events = events; - - public virtual = useVirtual(); - private config = useConfigContext(); - - scrollToIndex(index: number, behavior: ScrollBehavior = 'auto') { - const { containerRef, getItemTop } = this.virtual; - if (containerRef?.value) { - const top = getItemTop(index); - containerRef.value.scrollTo({ - top, - behavior, - }); - } - } -} \ No newline at end of file +export * from './container'; +export * from './wrapper'; +export * from './rows'; +export * from './virtual'; diff --git a/components/virtuallist/rows.ts b/components/virtuallist/rows.ts new file mode 100644 index 000000000..63397b86a --- /dev/null +++ b/components/virtuallist/rows.ts @@ -0,0 +1,85 @@ +import { + Component, + createFragment, + createElementVNode, + createComponentVNode, + VNode, + Types, + normalizeChildren, + createVoidVNode, + ChildrenTypes, + createVNode as h, +} from 'intact'; +import {isUndefined} from 'intact-shared'; +import { useVirtualContext } from './useVirtualContext'; + +export interface VirtualListRowsProps { + tagName?: string +} + +export class VirtualListRows extends Component { + private virtualContext = useVirtualContext(); + + static template(this: VirtualListRows) { + const { tagName } = this.get(); + const { virtualChildren } = this.virtualContext.value!; + let vNode: VNode; + + // 创建基础 vNode + if (isUndefined(tagName)) { + vNode = createFragment(virtualChildren.value, 8); + } else { // TODO createVNode + vNode = createElementVNode( + 2, + tagName, + virtualChildren.value, + 8 + ); + } + + // 规范化子节点 + normalizeChildren(vNode, virtualChildren.value); + + let nextChildren = vNode.children as VNode[]; + + // 处理不同的子节点类型 + const childrenType = vNode.childrenType; + // TODO + if (childrenType & 2) { + // 单个子节点转换为数组 + vNode.childrenType = 8; + nextChildren = vNode.children = [nextChildren as unknown as VNode]; + } else if (childrenType & 1) { + // 处理无效的子节点 + vNode.childrenType = 2; + vNode.children = createVoidVNode(); + nextChildren = []; + } + + // 开发环境下的检查 + // if (process.env.NODE_ENV !== 'production') { + // nextChildren.forEach(child => { + // if (child.type & 32768) { + // console.error('Virtual list items must have key'); + // } + // }); + // } + + return vNode; + }; + + init() { + // 设置获取子节点的方法 + this.virtualContext.value!.setGetChildrenFn(() => { + const children = this.get('children'); + return Array.isArray(children) ? children : []; + }); + + // 监听自身的 children 变化 + this.on('$receive:children', () => { + console.log('rows children changed'); + this.virtualContext.value!.onChildrenChange(); + }); + } + +} \ No newline at end of file diff --git a/components/virtuallist/styles.ts b/components/virtuallist/styles.ts index e8cf04867..4a2d52ced 100644 --- a/components/virtuallist/styles.ts +++ b/components/virtuallist/styles.ts @@ -5,23 +5,24 @@ import { cache } from '../utils'; export const makeStyles = cache(function makeStyles(k: string) { return css` &.${k}-virtual { - position: relative; - overflow: auto; - height: inherit; - min-height: inherit; - max-height: inherit; + &.${k}-virtual-container { + position: relative; + height: 100%; + } - // .${k}-virtual-content { - // position: relative; - // width: 100%; - // } + .${k}-virtual-wrapper { + width: 100%; + will-change: transform; + } - // .${k}-virtual-list { - // position: absolute; - // width: 100%; - // left: 0; - // top: 0; - // } + .${k}-virtual-phantom { + position: absolute; + left: 0; + top: 0; + right: 0; + z-index: -1; + pointer-events: none; + } } ` }); diff --git a/components/virtuallist/useVirtual.ts b/components/virtuallist/useVirtual.ts index 0cbd57705..306d6f315 100644 --- a/components/virtuallist/useVirtual.ts +++ b/components/virtuallist/useVirtual.ts @@ -1,24 +1,39 @@ -import { useInstance, onMounted, onUnmounted, createRef, Children, VNode, directClone, watch, nextTick, RefObject } from 'intact'; +import { + useInstance, + onMounted, + onUnmounted, + createRef, + VNode, + directClone, + nextTick, + RefObject, + Children +} from 'intact'; import { useState } from '../../hooks/useState'; -import type { VirtualList } from './index'; import { createHeightManager } from './helpers'; -export interface VirtualProps { +import type { VirtualListContainer } from './container'; +import { State } from '../../hooks/useState'; + +export interface VirtualState { containerRef: RefObject; contentRef: RefObject; - children: Children; - estimatedItemHeight: number | undefined; - bufferSize: number | undefined; + virtualChildren: State; + translateY: State; + totalHeight: State; + startIndex: State; + endIndex: State; + getItemTop: (index: number) => number; + scrollToIndex: (index: number, behavior?: ScrollBehavior) => void; + onChildrenChange: () => void; + setGetChildrenFn: (fn: () => VNode[]) => void; } -export function useVirtual(props?: VirtualProps) { - const instance = useInstance() as VirtualList; - const {estimatedItemHeight, bufferSize} = instance.get(); - - const getChildren = () => { - const children = instance.get('children'); - return Array.isArray(children) ? children : []; - }; - if (!Array.isArray(getChildren())) return {}; +export function useVirtual(): VirtualState { + const instance = useInstance() as VirtualListContainer; + const aaa = instance.get(); + console.log('aaaa', aaa); + const { estimatedItemHeight = 30, bufferSize = 6 } = instance.get(); + // TODO containerRef 暂不支持响应式 const containerRef = createRef(); const contentRef = createRef(); @@ -26,11 +41,37 @@ export function useVirtual(props?: VirtualProps) { const endIndex = useState(0); const translateY = useState(0); const totalHeight = useState(0); - - const virtualChildren = useState<(VNode | string | number)[]>([]); - + const virtualChildren = useState([]); + let getChildrenFn: (() => (VNode | Children)[]) | null = null; let ticking = false; - // height manager for calculate total height and update item heights + let lastScrollTop = 0; + let isUpdating = false; + + const setGetChildrenFn = (fn: () => (VNode | Children)[]) => { + getChildrenFn = fn; + // 初始化计算 + calculateTotalHeight(); + handleScroll(); + }; + + const getChildren = () => { + const children = getChildrenFn ? getChildrenFn() : []; + return children;// 添加日志 + + }; + + // 处理 children 变化的方法 + const onChildrenChange = () => { + if (!isUpdating) { + clear(); + calculateTotalHeight(); + nextTick(handleScroll); + } + }; + // const getChildren = () => { + // const children = instance.get('children'); + // return Array.isArray(children) ? children : []; + // }; const { batchUpdate, getItemHeight, @@ -62,15 +103,32 @@ export function useVirtual(props?: VirtualProps) { return top; }; + const scrollToIndex = (index: number, behavior: ScrollBehavior = 'auto') => { + if (containerRef.value) { + const top = getItemTop(index); + containerRef.value.scrollTo({ + top, + behavior, + }); + } + }; + const handleScroll = () => { - if (!ticking && containerRef.value) { + if (!ticking && containerRef.value && !isUpdating) { ticking = true; requestAnimationFrame(() => { - const { scrollTop, clientHeight } = containerRef.value as HTMLElement; + const { scrollTop, clientHeight } = containerRef.value!; + + // // 避免小幅度滚动触发更新 + // if (Math.abs(lastScrollTop - scrollTop) < 1) { + // return; + // } + // lastScrollTop = scrollTop; + const { start, end, translateY: offset } = calculateVisibleRange( scrollTop, clientHeight, - bufferSize as number + bufferSize ); startIndex.set(start); @@ -92,6 +150,7 @@ export function useVirtual(props?: VirtualProps) { }; onMounted(() => { + console.log('Component mounted'); // 添加日志 if (containerRef.value) { containerRef.value.addEventListener('scroll', handleScroll); calculateTotalHeight(); @@ -105,20 +164,17 @@ export function useVirtual(props?: VirtualProps) { } }); - instance.on('$receive:children', () => { - clear(); - calculateTotalHeight(); - nextTick(() => { - handleScroll(); - }); - }); - return { containerRef, contentRef, virtualChildren, translateY, totalHeight, + startIndex, + endIndex, getItemTop, + scrollToIndex, + setGetChildrenFn, + onChildrenChange, }; } \ No newline at end of file diff --git a/components/virtuallist/useVirtualContext.ts b/components/virtuallist/useVirtualContext.ts new file mode 100644 index 000000000..345851f47 --- /dev/null +++ b/components/virtuallist/useVirtualContext.ts @@ -0,0 +1,28 @@ +import { RefObject, VNode, Children } from 'intact'; +import { createContext } from '../context'; +import { State } from '../../hooks/useState'; + + +export interface VirtualContextValue { + containerRef: RefObject; + contentRef: RefObject; + virtualChildren: State; + translateY: State; + totalHeight: State; + startIndex: State; + endIndex: State; + updateHeights: () => void; + scrollToIndex: (index: number, options?: ScrollOptions) => void; + setGetChildrenFn: (fn: () => (VNode | Children)[]) => void; + onChildrenChange: () => void; +} + +export const VirtualContext = createContext(); + +export const useVirtualContext = () => { + const context = VirtualContext.useContext(); + if (!context) { + throw new Error('VirtualContext must be used within a VirtualProvider'); + } + return context; +}; \ No newline at end of file diff --git a/components/virtuallist/virtual.ts b/components/virtuallist/virtual.ts new file mode 100644 index 000000000..8c84f3b4d --- /dev/null +++ b/components/virtuallist/virtual.ts @@ -0,0 +1,34 @@ +import { Component, TypeDefs, createRef } from 'intact'; +import template from './virtual.vdt'; +import { useConfigContext } from '../config'; +import type { VirtualListContainer } from './container'; + +export interface VirtualListProps { + estimatedItemHeight?: number; + bufferSize?: number; + height?: number | string; +} + +const typeDefs: Required> = { + estimatedItemHeight: Number, + bufferSize: Number, + height: [Number, String], +}; + +const defaults = (): Partial => ({ + estimatedItemHeight: 30, + bufferSize: 6, +}); + +export class VirtualList extends Component { + static template = template; + static typeDefs = typeDefs; + static defaults = defaults; + + private config = useConfigContext(); + private containerRef = createRef(); + + public scrollToIndex(index: number, behavior: ScrollBehavior = 'auto') { + this.containerRef.value?.scrollToIndex(index, behavior); + } +} diff --git a/components/virtuallist/virtual.vdt b/components/virtuallist/virtual.vdt new file mode 100644 index 000000000..4dd1ee7bc --- /dev/null +++ b/components/virtuallist/virtual.vdt @@ -0,0 +1,15 @@ +import { Icon } from '../icon'; +import { addStyle, getRestProps } from '../utils'; +import { makeStyles } from './styles'; +import { VirtualListContainer } from './container'; +import { VirtualListWrapper } from './wrapper'; +import { VirtualListRows } from './rows'; + +const { children, className, style, type, height } = this.get(); +const { k } = this.config; + + + + {children} + + diff --git a/components/virtuallist/wrapper.ts b/components/virtuallist/wrapper.ts new file mode 100644 index 000000000..df45ea8d9 --- /dev/null +++ b/components/virtuallist/wrapper.ts @@ -0,0 +1,20 @@ +import { Component, TypeDefs, ComponentConstructor } from 'intact'; +import template from './wrapper.vdt'; +import { useConfigContext } from '../config'; +import { useVirtualContext } from './useVirtualContext'; + +export interface VirtualListWrapperProps { + tagName: string | ComponentConstructor, +} + +const typeDefs: Required> = { + tagName: String, +}; + +export class VirtualListWrapper extends Component { + static template = template; + static typeDefs = typeDefs; + + private config = useConfigContext(); + private virtualContext = useVirtualContext(); +} \ No newline at end of file diff --git a/components/virtuallist/wrapper.vdt b/components/virtuallist/wrapper.vdt new file mode 100644 index 000000000..71170349e --- /dev/null +++ b/components/virtuallist/wrapper.vdt @@ -0,0 +1,22 @@ +import { addStyle, getRestProps } from '../utils'; +import { makeStyles } from './styles'; +import { DynamicVirtual } from './dynamicVirtual'; +const { children, className, tagName, style } = this.get(); +const {contentRef, translateY} = this.virtualContext.value; +const { k } = this.config; + +const classNameObj = { + [`${k}-virtual-wrapper`]: true, + [makeStyles(k)]: true, + [className]: className, +} + + + {children} + diff --git a/index.ts b/index.ts index 46d9b304e..44edbfa46 100644 --- a/index.ts +++ b/index.ts @@ -67,7 +67,7 @@ export * from './components/tree'; export * from './components/treeSelect'; export * from './components/upload'; export * from './components/view'; -export * from './components/virtuallist'; +export * from './components/virtualList'; export * from './components/wave'; export const version = '3.4.5'; From 925067ad12efe7e974b943e42af81437604169ef Mon Sep 17 00:00:00 2001 From: Javey Date: Wed, 25 Dec 2024 19:22:33 +0800 Subject: [PATCH 09/18] refactor(VirtualList) --- components/select/menu.vdt | 3 + components/select/select.vdt | 9 +- .../{virtuallist => virtualList}/container.ts | 28 ++--- components/virtualList/container.vdt | 26 +++++ .../demos/basic.md | 0 .../demos/scrollTo.md | 0 .../dynamicVirtual.ts | 4 +- .../{virtuallist => virtualList}/helpers.ts | 0 .../{virtuallist => virtualList}/index.md | 0 .../index.spec.ts | 0 .../{virtuallist => virtualList}/index.ts | 0 .../{virtuallist => virtualList}/index.vdt | 0 components/virtualList/rows.ts | 13 +++ components/virtualList/rows.vdt | 19 ++++ components/virtualList/styles.ts | 27 +++++ components/virtualList/useRows.ts | 27 +++++ components/virtualList/useScroll.ts | 33 ++++++ .../useVirtual.ts | 42 +++---- .../useVirtualContext.ts | 0 components/virtualList/useVirtualRows.ts | 105 ++++++++++++++++++ components/virtualList/virtual.ts | 10 ++ components/virtualList/virtual.vdt | 12 ++ .../{virtuallist => virtualList}/wrapper.ts | 0 .../{virtuallist => virtualList}/wrapper.vdt | 8 +- components/virtuallist/container.vdt | 31 ------ components/virtuallist/rows.ts | 85 -------------- components/virtuallist/styles.ts | 28 ----- components/virtuallist/virtual.ts | 34 ------ components/virtuallist/virtual.vdt | 15 --- 29 files changed, 317 insertions(+), 242 deletions(-) rename components/{virtuallist => virtualList}/container.ts (69%) create mode 100644 components/virtualList/container.vdt rename components/{virtuallist => virtualList}/demos/basic.md (100%) rename components/{virtuallist => virtualList}/demos/scrollTo.md (100%) rename components/{virtuallist => virtualList}/dynamicVirtual.ts (78%) rename components/{virtuallist => virtualList}/helpers.ts (100%) rename components/{virtuallist => virtualList}/index.md (100%) rename components/{virtuallist => virtualList}/index.spec.ts (100%) rename components/{virtuallist => virtualList}/index.ts (100%) rename components/{virtuallist => virtualList}/index.vdt (100%) create mode 100644 components/virtualList/rows.ts create mode 100644 components/virtualList/rows.vdt create mode 100644 components/virtualList/styles.ts create mode 100644 components/virtualList/useRows.ts create mode 100644 components/virtualList/useScroll.ts rename components/{virtuallist => virtualList}/useVirtual.ts (86%) rename components/{virtuallist => virtualList}/useVirtualContext.ts (100%) create mode 100644 components/virtualList/useVirtualRows.ts create mode 100644 components/virtualList/virtual.ts create mode 100644 components/virtualList/virtual.vdt rename components/{virtuallist => virtualList}/wrapper.ts (100%) rename components/{virtuallist => virtualList}/wrapper.vdt (64%) delete mode 100644 components/virtuallist/container.vdt delete mode 100644 components/virtuallist/rows.ts delete mode 100644 components/virtuallist/styles.ts delete mode 100644 components/virtuallist/virtual.ts delete mode 100644 components/virtuallist/virtual.vdt diff --git a/components/select/menu.vdt b/components/select/menu.vdt index 57b9d13f7..b2933cd96 100644 --- a/components/select/menu.vdt +++ b/components/select/menu.vdt @@ -9,6 +9,7 @@ import {Button} from '../button'; import {Icon} from '../icon'; import {context} from './useSearchable'; import {Tabs, Tab} from '../tabs'; +import { VirtualList } from '../virtualList'; let {children, className} = this.get(); const {card, searchable, multiple} = this.select.get(); @@ -22,6 +23,8 @@ const classNameObj = { [makeMenuStyles(k)]: true, } +children = {children} + if (card) { const {process, activeIndex} = this.card; const {labels, group} = process(children); diff --git a/components/select/select.vdt b/components/select/select.vdt index ed789be49..3f15e6c08 100644 --- a/components/select/select.vdt +++ b/components/select/select.vdt @@ -1,5 +1,4 @@ import {SelectMenu} from './menu'; -import { VirtualList } from '../virtuallist'; import {isEmptyChildren} from '../utils'; const {className, children, autoDisableArrow, disabled, multiple, value, virtual} = this.get(); @@ -20,13 +19,7 @@ const isDisableArrow = disabled || diff --git a/components/virtuallist/container.ts b/components/virtualList/container.ts similarity index 69% rename from components/virtuallist/container.ts rename to components/virtualList/container.ts index 2cec8a7a6..eced7fcff 100644 --- a/components/virtuallist/container.ts +++ b/components/virtualList/container.ts @@ -3,6 +3,7 @@ import template from './container.vdt'; import { Events } from '../types'; import { useConfigContext } from '../config'; import { useVirtual } from './useVirtual'; +import { useVirtualRows } from './useVirtualRows'; export interface VirtualListContainerProps { estimatedItemHeight?: number; @@ -33,16 +34,17 @@ export class VirtualListContainer extends Component +
+ {children} +
+
+ diff --git a/components/virtuallist/demos/basic.md b/components/virtualList/demos/basic.md similarity index 100% rename from components/virtuallist/demos/basic.md rename to components/virtualList/demos/basic.md diff --git a/components/virtuallist/demos/scrollTo.md b/components/virtualList/demos/scrollTo.md similarity index 100% rename from components/virtuallist/demos/scrollTo.md rename to components/virtualList/demos/scrollTo.md diff --git a/components/virtuallist/dynamicVirtual.ts b/components/virtualList/dynamicVirtual.ts similarity index 78% rename from components/virtuallist/dynamicVirtual.ts rename to components/virtualList/dynamicVirtual.ts index a18b0943c..473bf5f3b 100644 --- a/components/virtuallist/dynamicVirtual.ts +++ b/components/virtualList/dynamicVirtual.ts @@ -1,12 +1,12 @@ import {createVNode as h, ComponentFunction, ComponentConstructor} from 'intact'; -import {isUndefined} from 'intact-shared'; +import {isNullOrUndefined} from 'intact-shared'; export type DynamicVirtualProps = { tagName: string | ComponentConstructor, } export const DynamicVirtual: ComponentFunction = ({tagName, ...props}) => { - if (isUndefined(tagName)) { + if (isNullOrUndefined(tagName)) { tagName = 'div'; } return h(tagName, props); diff --git a/components/virtuallist/helpers.ts b/components/virtualList/helpers.ts similarity index 100% rename from components/virtuallist/helpers.ts rename to components/virtualList/helpers.ts diff --git a/components/virtuallist/index.md b/components/virtualList/index.md similarity index 100% rename from components/virtuallist/index.md rename to components/virtualList/index.md diff --git a/components/virtuallist/index.spec.ts b/components/virtualList/index.spec.ts similarity index 100% rename from components/virtuallist/index.spec.ts rename to components/virtualList/index.spec.ts diff --git a/components/virtuallist/index.ts b/components/virtualList/index.ts similarity index 100% rename from components/virtuallist/index.ts rename to components/virtualList/index.ts diff --git a/components/virtuallist/index.vdt b/components/virtualList/index.vdt similarity index 100% rename from components/virtuallist/index.vdt rename to components/virtualList/index.vdt diff --git a/components/virtualList/rows.ts b/components/virtualList/rows.ts new file mode 100644 index 000000000..5933fb639 --- /dev/null +++ b/components/virtualList/rows.ts @@ -0,0 +1,13 @@ +import { Component } from 'intact'; +import { useRows } from './useRows'; +import template from './rows.vdt'; + +export interface VirtualListRowsProps { + tagName?: string +} + +export class VirtualListRows extends Component { + static template = template; + + private rows = useRows(); +} diff --git a/components/virtualList/rows.vdt b/components/virtualList/rows.vdt new file mode 100644 index 000000000..f637d24a0 --- /dev/null +++ b/components/virtualList/rows.vdt @@ -0,0 +1,19 @@ +import { createFragment, createVNode } from 'intact'; +import { isNullOrUndefined } from 'intact-shared'; +import { context as VirtualRowsContext } from './useVirtualRows'; + +const { tagName } = this.get(); +const rows = this.rows; + + + {({ notifyRows, startIndex, length }) => { + const children = rows.value.slice(startIndex, startIndex + length); + + notifyRows(rows); + + if (isNullOrUndefined(tagName)) { + return createFragment(children, 8 /* ChildrenTypes.HasKeyedChildren */); + } + return createVNode(tagName, null, children); + }} + diff --git a/components/virtualList/styles.ts b/components/virtualList/styles.ts new file mode 100644 index 000000000..e594f6f21 --- /dev/null +++ b/components/virtualList/styles.ts @@ -0,0 +1,27 @@ +import {css} from '@emotion/css'; +import '../../styles/global'; +import { cache } from '../utils'; + +export const makeContainerStyles = cache((k: string) => { + return css` + overflow: auto; + position: relative; + height: 100%; + .${k}-virtual-phantom { + position: absolute; + left: 0; + top: 0; + right: 0; + z-index: -1; + pointer-events: none; + } + + `; +}); + +export const makeWrapperStyles = cache(() => { + return css` + width: 100%; + will-change: transform; + `; +}); diff --git a/components/virtualList/useRows.ts b/components/virtualList/useRows.ts new file mode 100644 index 000000000..f4f2d7570 --- /dev/null +++ b/components/virtualList/useRows.ts @@ -0,0 +1,27 @@ +import { useInstance, VNode, createRef, normalizeChildren, createFragment, createVNode, ChildrenTypes, createVoidVNode } from 'intact'; +import { VirtualListRows } from './rows'; +import { useState, State } from '../../hooks/useState'; +import { useReceive } from '../../hooks/useReceive'; +import { isNullOrUndefined } from 'intact-shared'; + +export function useRows() { + const instance = useInstance() as VirtualListRows; + const rows = createRef([]); + + instance.on('$receive:children', (children) => { + // use fragment vnode to normalize children + const vNode = createFragment(children, 0 /* ChildrenTypes.UnknownChildren */); + + // convert to array if it has only one child + const childrenType = vNode.childrenType; + if (childrenType & 2 /* ChildrenTypes.HasVNodeChildren */) { + rows.value = [vNode.children as unknown as VNode]; + } else if (childrenType & 1 /* ChildrenTypes.HasInvalidChildren */) { + rows.value = []; + } else { + rows.value = vNode.children as VNode[]; + } + }); + + return rows; +} diff --git a/components/virtualList/useScroll.ts b/components/virtualList/useScroll.ts new file mode 100644 index 000000000..0e71ea155 --- /dev/null +++ b/components/virtualList/useScroll.ts @@ -0,0 +1,33 @@ +import { createContext } from '../context'; +import { useState, State } from '../../hooks/useState'; +import { useInstance, VNode, Key, onMounted, onUpdated, findDomFromVNode, createRef, normalizeChildren, createFragment, createVNode, ChildrenTypes, createVoidVNode } from 'intact'; +import { VirtualListContainer } from './container'; + +export function useScroll(rows: State, startIndex: State, length: State) { + const instance = useInstance() as VirtualListContainer; + const rowsHeightMap = new Map(); + + let calculatedHeight = 0; + function calculateRowsHeight() { + for (let i = startIndex.value; i < startIndex.value + length.value; i++) { + const row = rows.value[i]; + const key = row.key!; + if (!rowsHeightMap.has(key)) { + const rowDom = findDomFromVNode(row, true) as HTMLElement; + const height = rowDom.offsetHeight; + rowsHeightMap.set(key, height); + calculatedHeight += height; + } + } + } + + function getTotalHeight() { + const calculatedSize = rowsHeightMap.size; + return calculatedHeight + calculatedHeight / calculatedSize * (rows.value.length - calculatedSize); + } + + let containerDom: HTMLElement; + onMounted(() => { + containerDom = findDomFromVNode(instance.$lastInput!, true) as HTMLElement; + }); +} diff --git a/components/virtuallist/useVirtual.ts b/components/virtualList/useVirtual.ts similarity index 86% rename from components/virtuallist/useVirtual.ts rename to components/virtualList/useVirtual.ts index 306d6f315..47216fdd3 100644 --- a/components/virtuallist/useVirtual.ts +++ b/components/virtualList/useVirtual.ts @@ -141,28 +141,28 @@ export function useVirtual(): VirtualState { }; const setVisibleItems = (start: number, end: number) => { - const children = getChildren(); - const visibleItems = children.slice(start, end).map(vNode => directClone(vNode as VNode)); - virtualChildren.set(visibleItems); - nextTick(() => { - calculateHeights(); - }); + // const children = getChildren(); + // const visibleItems = children.slice(start, end).map(vNode => directClone(vNode as VNode)); + // virtualChildren.set(visibleItems); + // nextTick(() => { + // calculateHeights(); + // }); }; - onMounted(() => { - console.log('Component mounted'); // 添加日志 - if (containerRef.value) { - containerRef.value.addEventListener('scroll', handleScroll); - calculateTotalHeight(); - handleScroll(); - } - }); - - onUnmounted(() => { - if (containerRef.value) { - containerRef.value.removeEventListener('scroll', handleScroll); - } - }); + // onMounted(() => { + // console.log('Component mounted'); // 添加日志 + // if (containerRef.value) { + // containerRef.value.addEventListener('scroll', handleScroll); + // calculateTotalHeight(); + // handleScroll(); + // } + // }); + + // onUnmounted(() => { + // if (containerRef.value) { + // containerRef.value.removeEventListener('scroll', handleScroll); + // } + // }); return { containerRef, @@ -177,4 +177,4 @@ export function useVirtual(): VirtualState { setGetChildrenFn, onChildrenChange, }; -} \ No newline at end of file +} diff --git a/components/virtuallist/useVirtualContext.ts b/components/virtualList/useVirtualContext.ts similarity index 100% rename from components/virtuallist/useVirtualContext.ts rename to components/virtualList/useVirtualContext.ts diff --git a/components/virtualList/useVirtualRows.ts b/components/virtualList/useVirtualRows.ts new file mode 100644 index 000000000..245f31254 --- /dev/null +++ b/components/virtualList/useVirtualRows.ts @@ -0,0 +1,105 @@ +import { createContext } from '../context'; +import { useState, watchState, State } from '../../hooks/useState'; +import { useInstance, nextTick, VNode, onMounted, onUnmounted, Key, onUpdated, findDomFromVNode, createRef, normalizeChildren, createFragment, createVNode, ChildrenTypes, createVoidVNode } from 'intact'; +import { VirtualListContainer } from './container'; + +type ContextValue = { + notifyRows: (rows: State) => void; + startIndex: number; + endIndex: number; +} + +export const context = createContext(); + +const MIN_LENGTH = 10; +const BUFFER_SIZE = 6; + +export function useVirtualRows() { + const instance = useInstance() as VirtualListContainer; + const rowsHeightMap = new Map(); + const startIndex = useState(0); + const length = useState(MIN_LENGTH); + + let rows = useState([]); + function notifyRows(_rows: State) { + rows = _rows; + } + + let calculatedHeight = 0; + let rowAvgHeight = 0; + function calculateRowsHeight() { + for ( + let i = startIndex.value; + i < startIndex.value + length.value && i < rows.value.length; + i++ + ) { + const row = rows.value[i]; + const key = row.key!; + if (!rowsHeightMap.has(key)) { + const rowDom = findDomFromVNode(row, true) as HTMLElement; + const height = rowDom.offsetHeight; + rowsHeightMap.set(key, height); + calculatedHeight += height; + } + } + + // use the average height to estimate the row height + rowAvgHeight = calculatedHeight / rowsHeightMap.size; + } + + watchState(startIndex, () => { + nextTick(calculateRowsHeight); + }); + + let containerDom: HTMLElement; + onMounted(() => { + // get contains height + containerDom = findDomFromVNode(instance.$lastInput!, true) as HTMLElement; + const containerHeight = containerDom.offsetHeight; + + calculateRowsHeight(); + + // calculate the length of rows we should render + length.set(Math.max(Math.ceil(containerHeight / rowAvgHeight) + BUFFER_SIZE, MIN_LENGTH)); + + containerDom.addEventListener('scroll', handleScroll); + + }); + + onUnmounted(() => { + containerDom.removeEventListener('scroll', handleScroll); + }); + + function getTotalHeight() { + return calculatedHeight + rowAvgHeight * (rows.value.length - rowsHeightMap.size); + } + + let lastScrollTop = 0; + function handleScroll() { + requestAnimationFrame(() => { + const scrollTop = containerDom.scrollTop; + + let accumulatedHeight = 0; + let start = 0; + + while (start < rows.value.length) { + const key = rows.value[start].key!; + const rowHeight = rowsHeightMap.get(key) || rowAvgHeight; + + accumulatedHeight += rowHeight; + if (accumulatedHeight > scrollTop) { + break; + } + + start++; + } + + console.log('newValue', Math.max(start - BUFFER_SIZE / 2, 0), 'oldValue', startIndex.value, 'newScrollTop', scrollTop, 'lastScrollTop', lastScrollTop); + // if (lastScrollTop > scrollTop) debugger; + lastScrollTop = scrollTop; + startIndex.set(Math.max(start - BUFFER_SIZE / 2, 0)); + }); + } + + return { notifyRows, startIndex, length, getTotalHeight }; +} diff --git a/components/virtualList/virtual.ts b/components/virtualList/virtual.ts new file mode 100644 index 000000000..2913deef3 --- /dev/null +++ b/components/virtualList/virtual.ts @@ -0,0 +1,10 @@ +import { Component, TypeDefs, createRef } from 'intact'; +import template from './virtual.vdt'; + +export class VirtualList extends Component { + static template = template; + + // public scrollToIndex(index: number, behavior: ScrollBehavior = 'auto') { + // this.containerRef.value?.scrollToIndex(index, behavior); + // } +} diff --git a/components/virtualList/virtual.vdt b/components/virtualList/virtual.vdt new file mode 100644 index 000000000..30a113eeb --- /dev/null +++ b/components/virtualList/virtual.vdt @@ -0,0 +1,12 @@ +import { getRestProps } from '../utils'; +import { VirtualListContainer } from './container'; +import { VirtualListWrapper } from './wrapper'; +import { VirtualListRows } from './rows'; + +const { children } = this.get(); + + + + {children} + + diff --git a/components/virtuallist/wrapper.ts b/components/virtualList/wrapper.ts similarity index 100% rename from components/virtuallist/wrapper.ts rename to components/virtualList/wrapper.ts diff --git a/components/virtuallist/wrapper.vdt b/components/virtualList/wrapper.vdt similarity index 64% rename from components/virtuallist/wrapper.vdt rename to components/virtualList/wrapper.vdt index 71170349e..5bf5f195a 100644 --- a/components/virtuallist/wrapper.vdt +++ b/components/virtualList/wrapper.vdt @@ -1,13 +1,13 @@ import { addStyle, getRestProps } from '../utils'; -import { makeStyles } from './styles'; +import { makeWrapperStyles } from './styles'; import { DynamicVirtual } from './dynamicVirtual'; const { children, className, tagName, style } = this.get(); -const {contentRef, translateY} = this.virtualContext.value; +/* const {contentRef, translateY} = this.virtualContext.value; */ const { k } = this.config; const classNameObj = { [`${k}-virtual-wrapper`]: true, - [makeStyles(k)]: true, + [makeWrapperStyles(k)]: true, [className]: className, } @@ -15,8 +15,6 @@ const classNameObj = { {...getRestProps(this)} class={classNameObj} tagName={tagName} - style={ addStyle(style, {transform: `translateY(${translateY.value}px)`}) } - ref={contentRef} > {children} diff --git a/components/virtuallist/container.vdt b/components/virtuallist/container.vdt deleted file mode 100644 index 93bf8bd1a..000000000 --- a/components/virtuallist/container.vdt +++ /dev/null @@ -1,31 +0,0 @@ -import { addStyle, getRestProps } from '../utils'; -import { makeStyles } from './styles'; -import { VirtualContext } from './useVirtualContext'; - -const { children, className, style, height } = this.get(); -const virtualState = this.virtualState; -const { k } = this.config; - -const classNameObj = { - [`${k}-virtual`]: true, - [`${k}-virtual-container`]: true, - [makeStyles(k)]: true, - [className]: className, -} - -const _style = { - overflow: 'auto', - ...(height && { height: `${height}px` }), -} - - -
- {children} -
-
-
diff --git a/components/virtuallist/rows.ts b/components/virtuallist/rows.ts deleted file mode 100644 index 63397b86a..000000000 --- a/components/virtuallist/rows.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { - Component, - createFragment, - createElementVNode, - createComponentVNode, - VNode, - Types, - normalizeChildren, - createVoidVNode, - ChildrenTypes, - createVNode as h, -} from 'intact'; -import {isUndefined} from 'intact-shared'; -import { useVirtualContext } from './useVirtualContext'; - -export interface VirtualListRowsProps { - tagName?: string -} - -export class VirtualListRows extends Component { - private virtualContext = useVirtualContext(); - - static template(this: VirtualListRows) { - const { tagName } = this.get(); - const { virtualChildren } = this.virtualContext.value!; - let vNode: VNode; - - // 创建基础 vNode - if (isUndefined(tagName)) { - vNode = createFragment(virtualChildren.value, 8); - } else { // TODO createVNode - vNode = createElementVNode( - 2, - tagName, - virtualChildren.value, - 8 - ); - } - - // 规范化子节点 - normalizeChildren(vNode, virtualChildren.value); - - let nextChildren = vNode.children as VNode[]; - - // 处理不同的子节点类型 - const childrenType = vNode.childrenType; - // TODO - if (childrenType & 2) { - // 单个子节点转换为数组 - vNode.childrenType = 8; - nextChildren = vNode.children = [nextChildren as unknown as VNode]; - } else if (childrenType & 1) { - // 处理无效的子节点 - vNode.childrenType = 2; - vNode.children = createVoidVNode(); - nextChildren = []; - } - - // 开发环境下的检查 - // if (process.env.NODE_ENV !== 'production') { - // nextChildren.forEach(child => { - // if (child.type & 32768) { - // console.error('Virtual list items must have key'); - // } - // }); - // } - - return vNode; - }; - - init() { - // 设置获取子节点的方法 - this.virtualContext.value!.setGetChildrenFn(() => { - const children = this.get('children'); - return Array.isArray(children) ? children : []; - }); - - // 监听自身的 children 变化 - this.on('$receive:children', () => { - console.log('rows children changed'); - this.virtualContext.value!.onChildrenChange(); - }); - } - -} \ No newline at end of file diff --git a/components/virtuallist/styles.ts b/components/virtuallist/styles.ts deleted file mode 100644 index 4a2d52ced..000000000 --- a/components/virtuallist/styles.ts +++ /dev/null @@ -1,28 +0,0 @@ -import {css} from '@emotion/css'; -import '../../styles/global'; -import { cache } from '../utils'; - -export const makeStyles = cache(function makeStyles(k: string) { - return css` - &.${k}-virtual { - &.${k}-virtual-container { - position: relative; - height: 100%; - } - - .${k}-virtual-wrapper { - width: 100%; - will-change: transform; - } - - .${k}-virtual-phantom { - position: absolute; - left: 0; - top: 0; - right: 0; - z-index: -1; - pointer-events: none; - } - } - ` -}); diff --git a/components/virtuallist/virtual.ts b/components/virtuallist/virtual.ts deleted file mode 100644 index 8c84f3b4d..000000000 --- a/components/virtuallist/virtual.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Component, TypeDefs, createRef } from 'intact'; -import template from './virtual.vdt'; -import { useConfigContext } from '../config'; -import type { VirtualListContainer } from './container'; - -export interface VirtualListProps { - estimatedItemHeight?: number; - bufferSize?: number; - height?: number | string; -} - -const typeDefs: Required> = { - estimatedItemHeight: Number, - bufferSize: Number, - height: [Number, String], -}; - -const defaults = (): Partial => ({ - estimatedItemHeight: 30, - bufferSize: 6, -}); - -export class VirtualList extends Component { - static template = template; - static typeDefs = typeDefs; - static defaults = defaults; - - private config = useConfigContext(); - private containerRef = createRef(); - - public scrollToIndex(index: number, behavior: ScrollBehavior = 'auto') { - this.containerRef.value?.scrollToIndex(index, behavior); - } -} diff --git a/components/virtuallist/virtual.vdt b/components/virtuallist/virtual.vdt deleted file mode 100644 index 4dd1ee7bc..000000000 --- a/components/virtuallist/virtual.vdt +++ /dev/null @@ -1,15 +0,0 @@ -import { Icon } from '../icon'; -import { addStyle, getRestProps } from '../utils'; -import { makeStyles } from './styles'; -import { VirtualListContainer } from './container'; -import { VirtualListWrapper } from './wrapper'; -import { VirtualListRows } from './rows'; - -const { children, className, style, type, height } = this.get(); -const { k } = this.config; - - - - {children} - - From 1d13cccd2bec2ea487ad31fdda5f603e1e6d3719 Mon Sep 17 00:00:00 2001 From: Javey Date: Wed, 25 Dec 2024 21:16:41 +0800 Subject: [PATCH 10/18] refactor: virtual list --- components/select/demos/group.md | 2 +- components/select/group.vdt | 3 +- components/select/menu.vdt | 8 +- components/virtualList/container.ts | 32 +--- components/virtualList/container.vdt | 11 +- components/virtualList/dynamicVirtual.ts | 13 -- components/virtualList/helpers.ts | 122 ------------- components/virtualList/index.vdt | 26 --- components/virtualList/useScroll.ts | 33 ---- components/virtualList/useVirtual.ts | 180 -------------------- components/virtualList/useVirtualContext.ts | 28 --- components/virtualList/useVirtualRows.ts | 50 +++--- components/virtualList/wrapper.ts | 3 - components/virtualList/wrapper.vdt | 24 +-- 14 files changed, 56 insertions(+), 479 deletions(-) delete mode 100644 components/virtualList/dynamicVirtual.ts delete mode 100644 components/virtualList/helpers.ts delete mode 100644 components/virtualList/index.vdt delete mode 100644 components/virtualList/useScroll.ts delete mode 100644 components/virtualList/useVirtual.ts delete mode 100644 components/virtualList/useVirtualContext.ts diff --git a/components/select/demos/group.md b/components/select/demos/group.md index 282b91225..6bef953c5 100644 --- a/components/select/demos/group.md +++ b/components/select/demos/group.md @@ -29,7 +29,7 @@ import {Select, Option, OptionGroup} from 'kpc'; - + 休息日 diff --git a/components/select/group.vdt b/components/select/group.vdt index 7637bf425..91485aeba 100644 --- a/components/select/group.vdt +++ b/components/select/group.vdt @@ -1,5 +1,6 @@ import {makeGroupStyles} from './styles'; import {getRestProps} from '../utils'; +import { VirtualList } from '../virtualList'; const {children, label, className} = this.get(); const {card} = this.select.get(); @@ -15,5 +16,5 @@ const classNameObj = {
{label}
- {children} + {children}
diff --git a/components/select/menu.vdt b/components/select/menu.vdt index b2933cd96..b68064d1c 100644 --- a/components/select/menu.vdt +++ b/components/select/menu.vdt @@ -23,8 +23,6 @@ const classNameObj = { [makeMenuStyles(k)]: true, } -children = {children} - if (card) { const {process, activeIndex} = this.card; const {labels, group} = process(children); @@ -41,14 +39,14 @@ if (card) { {group} ); -} - -if (isEmptyChildren(children)) { +} else if (isEmptyChildren(children)) { children = (
{_$('无数据')}
); +} else { + children = {children} } if (searchable) { diff --git a/components/virtualList/container.ts b/components/virtualList/container.ts index eced7fcff..8a3b4aa2f 100644 --- a/components/virtualList/container.ts +++ b/components/virtualList/container.ts @@ -1,42 +1,14 @@ -import { Component, TypeDefs } from 'intact'; +import { Component } from 'intact'; import template from './container.vdt'; -import { Events } from '../types'; import { useConfigContext } from '../config'; -import { useVirtual } from './useVirtual'; import { useVirtualRows } from './useVirtualRows'; -export interface VirtualListContainerProps { - estimatedItemHeight?: number; - bufferSize?: number; - height?: number | string; -} - -export interface VirtualListContainerEvents {} - -const typeDefs: Required> = { - height: [Number, String], - estimatedItemHeight: Number, - bufferSize: Number, -}; - -const defaults = (): Partial => ({ - estimatedItemHeight: 30, - bufferSize: 6, -}); - -const events: Events = {}; - -export class VirtualListContainer extends Component { +export class VirtualListContainer extends Component { static template = template; - static typeDefs = typeDefs; - static defaults = defaults; - static events = events; private config = useConfigContext(); - private virtualState = useVirtual(); private virtualRows = useVirtualRows(); - // public scrollToIndex(index: number, behavior: ScrollBehavior = 'auto') { // const { containerRef, getItemTop } = this.virtualState; // if (containerRef?.value) { diff --git a/components/virtualList/container.vdt b/components/virtualList/container.vdt index d4c16c519..32819d574 100644 --- a/components/virtualList/container.vdt +++ b/components/virtualList/container.vdt @@ -7,7 +7,7 @@ const { children, className, style, height } = this.get(); const virtualState = this.virtualState; const { k } = this.config; -const { notifyRows, startIndex, length, getTotalHeight } = this.virtualRows; +const { notifyRows, startIndex, length, getTotalHeight, translateY } = this.virtualRows; const classNameObj = { [`${k}-virtual`]: true, @@ -17,10 +17,15 @@ const classNameObj = { }
- {children}
+ {children}
diff --git a/components/virtualList/dynamicVirtual.ts b/components/virtualList/dynamicVirtual.ts deleted file mode 100644 index 473bf5f3b..000000000 --- a/components/virtualList/dynamicVirtual.ts +++ /dev/null @@ -1,13 +0,0 @@ -import {createVNode as h, ComponentFunction, ComponentConstructor} from 'intact'; -import {isNullOrUndefined} from 'intact-shared'; - -export type DynamicVirtualProps = { - tagName: string | ComponentConstructor, -} - -export const DynamicVirtual: ComponentFunction = ({tagName, ...props}) => { - if (isNullOrUndefined(tagName)) { - tagName = 'div'; - } - return h(tagName, props); -} diff --git a/components/virtualList/helpers.ts b/components/virtualList/helpers.ts deleted file mode 100644 index a068a0419..000000000 --- a/components/virtualList/helpers.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { Children } from 'intact'; - -export interface HeightManagerOptions { - defaultHeight: number; - onTotalHeightChange: (height: number) => void; - getItems: () => Children[] | any[]; - getKey?: (item: any, index: number) => string | number; -} - -export interface HeightManager { - batchUpdate: (elements: { key: string | number; element: HTMLElement }[]) => void; - getItemHeight: (key: string | number) => number; - clear: () => void; - getAllHeights: () => number[]; - calculateTotalHeight: () => number; - calculateVisibleRange: (scrollTop: number, clientHeight: number, bufferSize: number) => VisibleRange; -} - -export interface VisibleRange { - start: number; - end: number; - translateY: number; -} - -// create height manager -export const createHeightManager = (options: HeightManagerOptions): HeightManager => { - const { defaultHeight, onTotalHeightChange, getItems, getKey } = options; - const cache = new Map(); - - const calculateTotalHeight = () => { - const items = getItems(); - const itemCount = Array.isArray(items) ? items.length : items; - let height = 0; - - for (let i = 0; i < itemCount; i++) { - height += cache.get(getKey ? getKey(items[i], i) : i) || defaultHeight; - } - onTotalHeightChange(height); - return height; - }; - - const batchUpdate = ( - elements: { key: string | number; element: HTMLElement }[] - ) => { - let hasUpdates = false; - - elements.forEach(({ key, element }) => { - const height = element.offsetHeight; - if (height > 0 && cache.get(key) !== height) { - cache.set(key, height); - hasUpdates = true; - } - }); - - if (hasUpdates) { - calculateTotalHeight(); - } - }; - - const getItemHeight = (key: string | number) => cache.get(key) || defaultHeight; - - const clear = () => { - cache.clear(); - calculateTotalHeight(); - }; - - const getAllHeights = () => Array.from(cache.values()); - - const calculateVisibleRange = ( - scrollTop: number, - clientHeight: number, - bufferSize: number - ): VisibleRange => { - const items = getItems(); - const itemCount = Array.isArray(items) ? items.length : items; - let accumulatedHeight = 0; - let start = 0; - let end = 0; - - // find start index - while (start < itemCount) { - const height = cache.get(start) || defaultHeight; - if (accumulatedHeight + height > scrollTop - (bufferSize * defaultHeight)) { - break; - } - accumulatedHeight += height; - start++; - } - - // find end index - start = Math.max(0, start); - const startOffset = accumulatedHeight; - - // 查找结束索引 - end = start; - while ( - end < itemCount && - accumulatedHeight < scrollTop + clientHeight + (bufferSize * defaultHeight) - ) { - accumulatedHeight += cache.get(end) || defaultHeight; - end++; - } - - end = Math.min(itemCount, end); - - return { - start, - end, - translateY: startOffset - }; - }; - - return { - batchUpdate, - getItemHeight, - clear, - getAllHeights, - calculateTotalHeight, - calculateVisibleRange, - }; -}; - diff --git a/components/virtualList/index.vdt b/components/virtualList/index.vdt deleted file mode 100644 index ef67f7b53..000000000 --- a/components/virtualList/index.vdt +++ /dev/null @@ -1,26 +0,0 @@ -import { Icon } from '../icon'; -import { addStyle, getRestProps } from '../utils'; -import { makeStyles } from './styles'; -import { _$ } from '../../i18n'; - -const { children, className, style, type, height } = this.get(); -const {containerRef, contentRef, virtualChildren, translateY, totalHeight} = this.virtual; -const { k } = this.config; - -const classNameObj = { - [`${k}-virtual`]: true, - [makeStyles(k)]: true, - [className]: className, -} -const _style = { - overflow: 'auto', - ...(height && { maxHeight: `${height}px` }), -} - -
-
-
- {virtualChildren.value} -
-
-
diff --git a/components/virtualList/useScroll.ts b/components/virtualList/useScroll.ts deleted file mode 100644 index 0e71ea155..000000000 --- a/components/virtualList/useScroll.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { createContext } from '../context'; -import { useState, State } from '../../hooks/useState'; -import { useInstance, VNode, Key, onMounted, onUpdated, findDomFromVNode, createRef, normalizeChildren, createFragment, createVNode, ChildrenTypes, createVoidVNode } from 'intact'; -import { VirtualListContainer } from './container'; - -export function useScroll(rows: State, startIndex: State, length: State) { - const instance = useInstance() as VirtualListContainer; - const rowsHeightMap = new Map(); - - let calculatedHeight = 0; - function calculateRowsHeight() { - for (let i = startIndex.value; i < startIndex.value + length.value; i++) { - const row = rows.value[i]; - const key = row.key!; - if (!rowsHeightMap.has(key)) { - const rowDom = findDomFromVNode(row, true) as HTMLElement; - const height = rowDom.offsetHeight; - rowsHeightMap.set(key, height); - calculatedHeight += height; - } - } - } - - function getTotalHeight() { - const calculatedSize = rowsHeightMap.size; - return calculatedHeight + calculatedHeight / calculatedSize * (rows.value.length - calculatedSize); - } - - let containerDom: HTMLElement; - onMounted(() => { - containerDom = findDomFromVNode(instance.$lastInput!, true) as HTMLElement; - }); -} diff --git a/components/virtualList/useVirtual.ts b/components/virtualList/useVirtual.ts deleted file mode 100644 index 47216fdd3..000000000 --- a/components/virtualList/useVirtual.ts +++ /dev/null @@ -1,180 +0,0 @@ -import { - useInstance, - onMounted, - onUnmounted, - createRef, - VNode, - directClone, - nextTick, - RefObject, - Children -} from 'intact'; -import { useState } from '../../hooks/useState'; -import { createHeightManager } from './helpers'; -import type { VirtualListContainer } from './container'; -import { State } from '../../hooks/useState'; - -export interface VirtualState { - containerRef: RefObject; - contentRef: RefObject; - virtualChildren: State; - translateY: State; - totalHeight: State; - startIndex: State; - endIndex: State; - getItemTop: (index: number) => number; - scrollToIndex: (index: number, behavior?: ScrollBehavior) => void; - onChildrenChange: () => void; - setGetChildrenFn: (fn: () => VNode[]) => void; -} - -export function useVirtual(): VirtualState { - const instance = useInstance() as VirtualListContainer; - const aaa = instance.get(); - console.log('aaaa', aaa); - const { estimatedItemHeight = 30, bufferSize = 6 } = instance.get(); - // TODO containerRef 暂不支持响应式 - const containerRef = createRef(); - const contentRef = createRef(); - - const startIndex = useState(0); - const endIndex = useState(0); - const translateY = useState(0); - const totalHeight = useState(0); - const virtualChildren = useState([]); - let getChildrenFn: (() => (VNode | Children)[]) | null = null; - let ticking = false; - let lastScrollTop = 0; - let isUpdating = false; - - const setGetChildrenFn = (fn: () => (VNode | Children)[]) => { - getChildrenFn = fn; - // 初始化计算 - calculateTotalHeight(); - handleScroll(); - }; - - const getChildren = () => { - const children = getChildrenFn ? getChildrenFn() : []; - return children;// 添加日志 - - }; - - // 处理 children 变化的方法 - const onChildrenChange = () => { - if (!isUpdating) { - clear(); - calculateTotalHeight(); - nextTick(handleScroll); - } - }; - // const getChildren = () => { - // const children = instance.get('children'); - // return Array.isArray(children) ? children : []; - // }; - const { - batchUpdate, - getItemHeight, - clear, - calculateTotalHeight, - calculateVisibleRange, - } = createHeightManager({ - defaultHeight: estimatedItemHeight as number, - onTotalHeightChange: (height) => totalHeight.set(height), - getItems: () => getChildren() - }); - - const calculateHeights = () => { - if (contentRef.value) { - const visibleElements = Array.from(contentRef.value.children).map((node, i) => ({ - key: startIndex.value + i, - element: node as HTMLElement, - })); - - batchUpdate(visibleElements); - } - }; - - const getItemTop = (index: number) => { - let top = 0; - for (let i = 0; i < index; i++) { - top += getItemHeight(i); - } - return top; - }; - - const scrollToIndex = (index: number, behavior: ScrollBehavior = 'auto') => { - if (containerRef.value) { - const top = getItemTop(index); - containerRef.value.scrollTo({ - top, - behavior, - }); - } - }; - - const handleScroll = () => { - if (!ticking && containerRef.value && !isUpdating) { - ticking = true; - requestAnimationFrame(() => { - const { scrollTop, clientHeight } = containerRef.value!; - - // // 避免小幅度滚动触发更新 - // if (Math.abs(lastScrollTop - scrollTop) < 1) { - // return; - // } - // lastScrollTop = scrollTop; - - const { start, end, translateY: offset } = calculateVisibleRange( - scrollTop, - clientHeight, - bufferSize - ); - - startIndex.set(start); - endIndex.set(end); - translateY.set(offset); - setVisibleItems(start, end); - ticking = false; - }); - } - }; - - const setVisibleItems = (start: number, end: number) => { - // const children = getChildren(); - // const visibleItems = children.slice(start, end).map(vNode => directClone(vNode as VNode)); - // virtualChildren.set(visibleItems); - // nextTick(() => { - // calculateHeights(); - // }); - }; - - // onMounted(() => { - // console.log('Component mounted'); // 添加日志 - // if (containerRef.value) { - // containerRef.value.addEventListener('scroll', handleScroll); - // calculateTotalHeight(); - // handleScroll(); - // } - // }); - - // onUnmounted(() => { - // if (containerRef.value) { - // containerRef.value.removeEventListener('scroll', handleScroll); - // } - // }); - - return { - containerRef, - contentRef, - virtualChildren, - translateY, - totalHeight, - startIndex, - endIndex, - getItemTop, - scrollToIndex, - setGetChildrenFn, - onChildrenChange, - }; -} diff --git a/components/virtualList/useVirtualContext.ts b/components/virtualList/useVirtualContext.ts deleted file mode 100644 index 345851f47..000000000 --- a/components/virtualList/useVirtualContext.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { RefObject, VNode, Children } from 'intact'; -import { createContext } from '../context'; -import { State } from '../../hooks/useState'; - - -export interface VirtualContextValue { - containerRef: RefObject; - contentRef: RefObject; - virtualChildren: State; - translateY: State; - totalHeight: State; - startIndex: State; - endIndex: State; - updateHeights: () => void; - scrollToIndex: (index: number, options?: ScrollOptions) => void; - setGetChildrenFn: (fn: () => (VNode | Children)[]) => void; - onChildrenChange: () => void; -} - -export const VirtualContext = createContext(); - -export const useVirtualContext = () => { - const context = VirtualContext.useContext(); - if (!context) { - throw new Error('VirtualContext must be used within a VirtualProvider'); - } - return context; -}; \ No newline at end of file diff --git a/components/virtualList/useVirtualRows.ts b/components/virtualList/useVirtualRows.ts index 245f31254..b3e13b284 100644 --- a/components/virtualList/useVirtualRows.ts +++ b/components/virtualList/useVirtualRows.ts @@ -1,6 +1,6 @@ import { createContext } from '../context'; import { useState, watchState, State } from '../../hooks/useState'; -import { useInstance, nextTick, VNode, onMounted, onUnmounted, Key, onUpdated, findDomFromVNode, createRef, normalizeChildren, createFragment, createVNode, ChildrenTypes, createVoidVNode } from 'intact'; +import { useInstance, nextTick, VNode, onMounted, onUnmounted, Key, findDomFromVNode } from 'intact'; import { VirtualListContainer } from './container'; type ContextValue = { @@ -12,7 +12,7 @@ type ContextValue = { export const context = createContext(); const MIN_LENGTH = 10; -const BUFFER_SIZE = 6; +const BUFFER_SIZE = 3; export function useVirtualRows() { const instance = useInstance() as VirtualListContainer; @@ -60,7 +60,7 @@ export function useVirtualRows() { calculateRowsHeight(); // calculate the length of rows we should render - length.set(Math.max(Math.ceil(containerHeight / rowAvgHeight) + BUFFER_SIZE, MIN_LENGTH)); + length.set(Math.max(Math.ceil(containerHeight / rowAvgHeight) + BUFFER_SIZE * 2, MIN_LENGTH)); containerDom.addEventListener('scroll', handleScroll); @@ -74,32 +74,36 @@ export function useVirtualRows() { return calculatedHeight + rowAvgHeight * (rows.value.length - rowsHeightMap.size); } - let lastScrollTop = 0; + const translateY = useState(0); function handleScroll() { - requestAnimationFrame(() => { - const scrollTop = containerDom.scrollTop; + const scrollTop = containerDom.scrollTop; - let accumulatedHeight = 0; - let start = 0; + let accumulatedHeight = 0; + let start = 0; - while (start < rows.value.length) { - const key = rows.value[start].key!; - const rowHeight = rowsHeightMap.get(key) || rowAvgHeight; + while (start < rows.value.length) { + accumulatedHeight += getRowHeightByIndex(start); + if (accumulatedHeight > scrollTop) { + break; + } + + start++; + } - accumulatedHeight += rowHeight; - if (accumulatedHeight > scrollTop) { - break; - } + startIndex.set(Math.max(start - BUFFER_SIZE, 0)); - start++; - } + // translateY should substract the buffer size rows height + for (let i = start; i >= startIndex.value; i--) { + accumulatedHeight -= getRowHeightByIndex(i); + } + + translateY.set(accumulatedHeight); + } - console.log('newValue', Math.max(start - BUFFER_SIZE / 2, 0), 'oldValue', startIndex.value, 'newScrollTop', scrollTop, 'lastScrollTop', lastScrollTop); - // if (lastScrollTop > scrollTop) debugger; - lastScrollTop = scrollTop; - startIndex.set(Math.max(start - BUFFER_SIZE / 2, 0)); - }); + function getRowHeightByIndex(index: number) { + const key = rows.value[index].key!; + return rowsHeightMap.get(key) || rowAvgHeight; } - return { notifyRows, startIndex, length, getTotalHeight }; + return { notifyRows, startIndex, length, getTotalHeight, translateY }; } diff --git a/components/virtualList/wrapper.ts b/components/virtualList/wrapper.ts index df45ea8d9..534d8ed37 100644 --- a/components/virtualList/wrapper.ts +++ b/components/virtualList/wrapper.ts @@ -1,8 +1,6 @@ import { Component, TypeDefs, ComponentConstructor } from 'intact'; import template from './wrapper.vdt'; import { useConfigContext } from '../config'; -import { useVirtualContext } from './useVirtualContext'; - export interface VirtualListWrapperProps { tagName: string | ComponentConstructor, } @@ -16,5 +14,4 @@ export class VirtualListWrapper extends Component { static typeDefs = typeDefs; private config = useConfigContext(); - private virtualContext = useVirtualContext(); } \ No newline at end of file diff --git a/components/virtualList/wrapper.vdt b/components/virtualList/wrapper.vdt index 5bf5f195a..6ee350722 100644 --- a/components/virtualList/wrapper.vdt +++ b/components/virtualList/wrapper.vdt @@ -1,20 +1,22 @@ import { addStyle, getRestProps } from '../utils'; -import { makeWrapperStyles } from './styles'; -import { DynamicVirtual } from './dynamicVirtual'; +import { context as VirtualRowsContext } from './useVirtualRows'; +import { createVNode } from 'intact'; +import { cx } from '@emotion/css'; + const { children, className, tagName, style } = this.get(); -/* const {contentRef, translateY} = this.virtualContext.value; */ const { k } = this.config; const classNameObj = { [`${k}-virtual-wrapper`]: true, - [makeWrapperStyles(k)]: true, [className]: className, } - - {children} - + + {({ translateY }) => { + return createVNode(tagName || 'div', { + ...getRestProps(this), + className: cx(classNameObj), + style: addStyle(style, {transform: `translateY(${translateY}px)`}) + }, children); + }} + From 63be96ed32ea41f654a3b6933caefd53cf400572 Mon Sep 17 00:00:00 2001 From: Javey Date: Thu, 26 Dec 2024 16:59:01 +0800 Subject: [PATCH 11/18] refactor: extract phantom for table; --- components/select/menu.vdt | 2 +- components/table/demos/fixHeader.md | 30 ++++++++++++++++++++---- components/table/styles.ts | 21 ++--------------- components/table/table.vdt | 18 +++++++------- components/virtualList/container.ts | 13 ++++++++-- components/virtualList/container.vdt | 8 +++---- components/virtualList/index.ts | 1 + components/virtualList/phantom.ts | 18 ++++++++++++++ components/virtualList/phantom.vdt | 26 ++++++++++++++++++++ components/virtualList/styles.ts | 20 +++++++++------- components/virtualList/useVirtualRows.ts | 4 +++- components/virtualList/virtual.ts | 11 ++++++++- components/virtualList/virtual.vdt | 7 +++++- 13 files changed, 126 insertions(+), 53 deletions(-) create mode 100644 components/virtualList/phantom.ts create mode 100644 components/virtualList/phantom.vdt diff --git a/components/select/menu.vdt b/components/select/menu.vdt index b68064d1c..5d03ff60d 100644 --- a/components/select/menu.vdt +++ b/components/select/menu.vdt @@ -46,7 +46,7 @@ if (card) {
); } else { - children = {children} + children = {children} } if (searchable) { diff --git a/components/table/demos/fixHeader.md b/components/table/demos/fixHeader.md index 1136efce9..5f09aa9af 100644 --- a/components/table/demos/fixHeader.md +++ b/components/table/demos/fixHeader.md @@ -18,11 +18,9 @@ import {Table, TableColumn} from 'kpc'; > - - +
+ +
``` @@ -30,7 +28,29 @@ import {Table, TableColumn} from 'kpc'; ```styl .wrapper display flex + align-items flex-start .k-table margin-left: 20px flex: 1 ``` + +```ts +import {range, bind} from 'kpc/components/utils'; + +const data = range(1, 100).map(item => { + return { + name: 'name ' + item, + ip: '127.0.0.' + item + }; +}); + +export default class extends Component { + static template = template; + + static defaults() { + return { + data: data + } + } +} +``` diff --git a/components/table/styles.ts b/components/table/styles.ts index 1711c7a02..8a441b5ab 100644 --- a/components/table/styles.ts +++ b/components/table/styles.ts @@ -401,27 +401,10 @@ export const makeStyles = cache(function makeStyles(k: string) { bottom: 0; } } - &.${k}-virtual { - .${k}-table-wrapper { - position: relative; - height: 400px; - } - - tbody { - width: 100%; - will-change: transform; - } - .${k}-table-phantom { - position: absolute; - left: 0; - top: 0; - right: 0; - z-index: -1; - pointer-events: none; - } + .${k}-table-phantom { + position: static; } - }; `; }); diff --git a/components/table/table.vdt b/components/table/table.vdt index 034f42e6f..50316f5dc 100644 --- a/components/table/table.vdt +++ b/components/table/table.vdt @@ -17,7 +17,7 @@ import {AllCheckedStatus} from './useChecked'; import {context as ResizableContext} from './useResizable'; import {context as FixedColumnsContext} from './useFixedColumns'; import {Pagination} from '../pagination'; -import {VirtualListContainer,VirtualListWrapper,VirtualListRows} from '../virtualList'; +import {VirtualListContainer, VirtualListWrapper, VirtualListRows, VirtualListPhantom} from '../virtualList'; const { data, children, className, fixHeader, @@ -44,7 +44,6 @@ const classNameObj = { [`${k}-${type}`]: type && type !== 'default', [`${k}-stripe`]: stripe, [`${k}-with-expand`]: $blocks.expand, - [`${k}-virtual`]: virtual, [className]: className, [makeStyles(k)]: true, }; @@ -123,7 +122,7 @@ const {isSelected} = this.selected; const {loopData, isSpreaded, toggleSpreadRow} = this.tree; const {onRowDragStart, onRowDragOver, onRowDragEnd, draggingKey} = this.draggable; const tbody = ( - + {!hasData ? @@ -206,12 +205,12 @@ const tbody = ( return hidden || !spreaded; }); - return animation[0] && !virtual ? - {rows} : - rows; + /* return animation[0] ? */ + /* {rows} : */ + return {rows}; })() } - + ); let tfooter = null; @@ -242,7 +241,7 @@ const { } = this.pagination;
-
+ {thead} + -
+ > = { + disabled: Boolean, +}; + +export class VirtualListContainer extends Component { static template = template; + static typeDefs = typeDefs; private config = useConfigContext(); private virtualRows = useVirtualRows(); diff --git a/components/virtualList/container.vdt b/components/virtualList/container.vdt index 32819d574..4c76770e8 100644 --- a/components/virtualList/container.vdt +++ b/components/virtualList/container.vdt @@ -1,10 +1,8 @@ import { addStyle, getRestProps } from '../utils'; import { makeContainerStyles } from './styles'; -import { VirtualContext } from './useVirtualContext'; import { context as VirtualRowsContext } from './useVirtualRows'; -const { children, className, style, height } = this.get(); -const virtualState = this.virtualState; +const { children, className, disabled, ref } = this.get(); const { k } = this.config; const { notifyRows, startIndex, length, getTotalHeight, translateY } = this.virtualRows; @@ -19,13 +17,13 @@ const classNameObj = { -
-
+
{children}
diff --git a/components/virtualList/index.ts b/components/virtualList/index.ts index 619bcdd30..f211a0c4c 100644 --- a/components/virtualList/index.ts +++ b/components/virtualList/index.ts @@ -2,3 +2,4 @@ export * from './container'; export * from './wrapper'; export * from './rows'; export * from './virtual'; +export * from './phantom'; diff --git a/components/virtualList/phantom.ts b/components/virtualList/phantom.ts new file mode 100644 index 000000000..8ff285c12 --- /dev/null +++ b/components/virtualList/phantom.ts @@ -0,0 +1,18 @@ +import { Component, TypeDefs } from 'intact'; +import template from './phantom.vdt'; +import { useConfigContext } from '../config'; + +export interface VirtualListPhantomProps { + tagName?: string; +} + +const typeDefs: Required> = { + tagName: String, +}; + +export class VirtualListPhantom extends Component { + static template = template; + static typeDefs = typeDefs; + + private config = useConfigContext(); +} diff --git a/components/virtualList/phantom.vdt b/components/virtualList/phantom.vdt new file mode 100644 index 000000000..c2b407afe --- /dev/null +++ b/components/virtualList/phantom.vdt @@ -0,0 +1,26 @@ +import { getRestProps } from '../utils'; +import { context as VirtualRowsContext } from './useVirtualRows'; +import { cx } from '@emotion/css'; +import { createVNode } from 'intact'; +import { makePhantomStyles } from './styles'; + +const { className, tagName } = this.get(); +const { k } = this.config; + +const classNameObj = { + [`${k}-virtual-phantom`]: true, + [makePhantomStyles()]: true, + [className]: className, +} + + + {({ getTotalHeight }) => { + return createVNode(tagName || 'div', { + ...getRestProps(this), + className: cx(classNameObj), + style: { + height: getTotalHeight() + 'px', + }, + }); + }} + diff --git a/components/virtualList/styles.ts b/components/virtualList/styles.ts index e594f6f21..2d120e258 100644 --- a/components/virtualList/styles.ts +++ b/components/virtualList/styles.ts @@ -2,20 +2,22 @@ import {css} from '@emotion/css'; import '../../styles/global'; import { cache } from '../utils'; -export const makeContainerStyles = cache((k: string) => { +export const makeContainerStyles = cache(() => { return css` overflow: auto; position: relative; height: 100%; - .${k}-virtual-phantom { - position: absolute; - left: 0; - top: 0; - right: 0; - z-index: -1; - pointer-events: none; - } + `; +}); +export const makePhantomStyles = cache(() => { + return css` + position: absolute; + left: 0; + top: 0; + right: 0; + z-index: -1; + pointer-events: none; `; }); diff --git a/components/virtualList/useVirtualRows.ts b/components/virtualList/useVirtualRows.ts index b3e13b284..4d9ec25bf 100644 --- a/components/virtualList/useVirtualRows.ts +++ b/components/virtualList/useVirtualRows.ts @@ -63,7 +63,6 @@ export function useVirtualRows() { length.set(Math.max(Math.ceil(containerHeight / rowAvgHeight) + BUFFER_SIZE * 2, MIN_LENGTH)); containerDom.addEventListener('scroll', handleScroll); - }); onUnmounted(() => { @@ -76,6 +75,9 @@ export function useVirtualRows() { const translateY = useState(0); function handleScroll() { + const { disabled } = instance.get(); + if (disabled) return; + const scrollTop = containerDom.scrollTop; let accumulatedHeight = 0; diff --git a/components/virtualList/virtual.ts b/components/virtualList/virtual.ts index 2913deef3..f578e38cc 100644 --- a/components/virtualList/virtual.ts +++ b/components/virtualList/virtual.ts @@ -1,8 +1,17 @@ import { Component, TypeDefs, createRef } from 'intact'; import template from './virtual.vdt'; -export class VirtualList extends Component { +export interface VirtualListProps { + disabled?: boolean +} + +const typeDefs: Required> = { + disabled: Boolean, +}; + +export class VirtualList extends Component { static template = template; + static typeDefs = typeDefs; // public scrollToIndex(index: number, behavior: ScrollBehavior = 'auto') { // this.containerRef.value?.scrollToIndex(index, behavior); diff --git a/components/virtualList/virtual.vdt b/components/virtualList/virtual.vdt index 30a113eeb..855f242dd 100644 --- a/components/virtualList/virtual.vdt +++ b/components/virtualList/virtual.vdt @@ -2,10 +2,15 @@ import { getRestProps } from '../utils'; import { VirtualListContainer } from './container'; import { VirtualListWrapper } from './wrapper'; import { VirtualListRows } from './rows'; +import { VirtualListPhantom } from './phantom'; -const { children } = this.get(); +const { children, disabled } = this.get(); +if (disabled) { + return children; +} + {children} From 65d837f117cfcb21561ea726a188355fe022bdbc Mon Sep 17 00:00:00 2001 From: zhangbing4 Date: Sat, 28 Dec 2024 18:24:41 +0800 Subject: [PATCH 12/18] fix: add test --- components/table/table.ts | 1 - components/table/useTableVirtual.ts | 132 ------------------- components/virtualList/demos/basic.md | 9 +- components/virtualList/demos/combined.md | 57 ++++++++ components/virtualList/demos/scrollTo.md | 2 +- components/virtualList/index.spec.ts | 159 +++++++++++++++++++++++ components/virtualList/wrapper.ts | 2 +- 7 files changed, 220 insertions(+), 142 deletions(-) delete mode 100644 components/table/useTableVirtual.ts create mode 100644 components/virtualList/demos/combined.md diff --git a/components/table/table.ts b/components/table/table.ts index f68147faf..f52b69a6e 100644 --- a/components/table/table.ts +++ b/components/table/table.ts @@ -23,7 +23,6 @@ import type {Events} from '../types'; import type {PaginationProps, PaginationChangeData} from '../pagination'; import { usePagination } from './usePagination'; import { useConfigContext } from '../config'; -import { useTableVirtual } from './useTableVirtual'; export interface TableProps< T = any, diff --git a/components/table/useTableVirtual.ts b/components/table/useTableVirtual.ts deleted file mode 100644 index c22b524b0..000000000 --- a/components/table/useTableVirtual.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { useInstance, onMounted, onBeforeUnmount, RefObject, createRef } from 'intact'; -import { useState } from '../../hooks/useState'; -import type { Table } from './table'; -import type { ScrollCallback } from './useScroll'; -import { State } from '../../hooks/useState'; -import { createHeightManager } from '../virtualList/helpers'; - -interface RowRefCache { - [key: string]: RefObject; -} - -export function useTableVirtual( - data: State, - scrollRef: RefObject, - callbacks: ScrollCallback[], -) { - const instance = useInstance() as Table; - const { virtual, rowKey, estimatedRowHeight, bufferSize } = instance.get(); - - if (!virtual) return null; - - const startIndex = useState(0); - const endIndex = useState(0); - const translateY = useState(0); - const totalHeight = useState(0); - - // row refs - const rowRefs: RowRefCache = {}; - // actual data because data don't loop children - let actualData: any[] = []; - // height manager for calculate total height and update item heights - const { - batchUpdate, - calculateTotalHeight, - calculateVisibleRange, - } = createHeightManager({ - defaultHeight: estimatedRowHeight as number, - onTotalHeightChange: (height) => totalHeight.set(height), - getItems: () => actualData, - getKey: (item, index) => getRowKey(item, index) - }); - - const calculateHeights = () => { - const visibleElements = []; - for (let i = startIndex.value; i < endIndex.value; i++) { - if (actualData[i]) { - const key = getRowKey(actualData[i], i); - const element = rowRefs[key]?.value; - if (element) { - visibleElements.push({ key, element }); - } - } - } - batchUpdate(visibleElements); - }; - - // reset and start collecting actual rendered data - const resetActualData = () => { - actualData = []; - }; - - // add actual rendered data item - const addActualDataItem = (item: any) => { - actualData.push(item); - }; - // get or create row ref - const getRowRef = (key: string) => { - if (!rowRefs[key]) { - rowRefs[key] = createRef(); - } - return rowRefs[key]; - }; - - const getRowKey = (item: any, index: number) => { - return typeof rowKey === 'function' ? - rowKey(item, index) : - item['key']; - }; - - const handleScroll = (_scrollLeft?: number, scrollTop: number = 0) => { - if (!scrollRef.value || !actualData) return; - - const { start, end, translateY: offset } = calculateVisibleRange( - scrollTop, - scrollRef.value.clientHeight, - bufferSize as number - ); - - startIndex.set(start); - endIndex.set(end); - translateY.set(offset); - }; - - callbacks.push(handleScroll); - - onMounted(() => { - if (actualData) { - calculateTotalHeight(); - handleScroll(0, 0); - } - let updateQueued = false; - const observer = new MutationObserver(() => { - if (!updateQueued) { - updateQueued = true; - requestAnimationFrame(() => { - calculateHeights(); - updateQueued = false; - }); - } - }); - - if (scrollRef.value) { - observer.observe(scrollRef.value, { - childList: true, - subtree: true, - attributes: true - }); - } - - return () => observer.disconnect(); - }); - - return { - startIndex, - endIndex, - translateY, - totalHeight, - getRowRef, - resetActualData, - addActualDataItem, - }; -} \ No newline at end of file diff --git a/components/virtualList/demos/basic.md b/components/virtualList/demos/basic.md index 95e6f4b08..ceb5ae180 100644 --- a/components/virtualList/demos/basic.md +++ b/components/virtualList/demos/basic.md @@ -8,14 +8,14 @@ import { VirtualList } from 'kpc';

1. 定高元素

- +
{$value.label}

2. 不定高元素

- +
{$value.label}
@@ -35,12 +35,9 @@ import { VirtualList } from 'kpc'; ``` ```ts -import {bind} from 'kpc/components/utils'; interface Props { - day?: string | null data: any[] variableHeightData: any[] - expanded: any } export default class extends Component { @@ -48,10 +45,8 @@ export default class extends Component { static defaults() { return { - day: null, data: [], variableHeightData: [], - expanded: {} } as Props; } diff --git a/components/virtualList/demos/combined.md b/components/virtualList/demos/combined.md new file mode 100644 index 000000000..03652ef94 --- /dev/null +++ b/components/virtualList/demos/combined.md @@ -0,0 +1,57 @@ +--- +title: 组合使用 +order: 1 +--- + +在一些复杂场景中,可以通过`VirtualListContainer`、`VirtualListWrapper`等子组件组合使用, 也可通过`tagName`属性指定自定义标签 + +```vdt +import { VirtualListContainer, VirtualListWrapper, VirtualListPhantom, VirtualListRows } from 'kpc'; + +
+ + + + +
  • + {$value.label} +
  • +
    +
    +
    +
    +``` + +```styl +.fixed-height-item + height 30px + border-bottom 1px solid #eee + padding 5px +``` + +```ts +interface Props { + data: any[] +} + +export default class extends Component { + static template = template; + + static defaults() { + return { + data: [], + } as Props; + } + + init() { + const arr = []; + for (let index = 0; index < 10000; index++) { + arr.push({ + value: index, + label: `测试${index}` + }); + } + this.set({data: arr}); + } +} +``` diff --git a/components/virtualList/demos/scrollTo.md b/components/virtualList/demos/scrollTo.md index ed7cce860..6b8529da3 100644 --- a/components/virtualList/demos/scrollTo.md +++ b/components/virtualList/demos/scrollTo.md @@ -1,6 +1,6 @@ --- title: 滑动到固定位置 -order: 1 +order: 2 --- 可以通过实例方法`scrollToIndex`滚动到具体位置 diff --git a/components/virtualList/index.spec.ts b/components/virtualList/index.spec.ts index e69de29bb..f2dbc8823 100644 --- a/components/virtualList/index.spec.ts +++ b/components/virtualList/index.spec.ts @@ -0,0 +1,159 @@ +import {mount, unmount, dispatchEvent, getElement, wait} from '../../test/utils'; +import {VirtualList, VirtualListContainer, VirtualListWrapper, VirtualListPhantom} from './'; +import {Component} from 'intact'; +import BasicDemo from '~/components/virtualList/demos/basic'; +import CombinedDemo from '~/components/virtualList/demos/combined'; + + +describe('VirtualList', () => { + // afterEach(() => unmount()); + + it('should render virtual list correctly', async () => { + const [instance, element] = mount(BasicDemo); + + // check basic structure + const container = element.querySelector('.k-virtual-container')!; + expect(container.outerHTML).to.matchSnapshot(); + + const wrapper = element.querySelector('.k-virtual-wrapper')!; + expect(wrapper).to.exist; + + // check render items is less than total + const items = wrapper.children; + expect(items.length).to.be.lessThan(100); + }); + + it('should handle scroll correctly', async () => { + const [instance, element] = mount(BasicDemo); + + const container = element.querySelector('.k-virtual-container')!; + const wrapper = element.querySelector('.k-virtual-wrapper')!; + await wait(50); + container.scrollTop = 400; + await wait(50); + + // check content is updated + const currentFirstItem = wrapper.firstElementChild; + expect(currentFirstItem!.textContent).to.not.equal('测试0'); + }); + + + it('should update total height when data changes', async () => { + const [instance, element] = mount(BasicDemo); + const [, container2] = element.querySelectorAll('.k-virtual-container'); + + + const [, phantom2] = element.querySelectorAll('.k-virtual-phantom')!; + const initialHeight = phantom2.style.height; + await wait(50); + container2.scrollTop = 800; + await wait(50); + + // check phantom height is updated + expect(phantom2.style.height).to.not.equal(initialHeight); + }); + + it('should work with custom container and wrapper', async () => { + const [instance, element] = mount(CombinedDemo); + await wait(); + + const container = element.querySelector('.k-virtual-container')!; + const wrapper = element.querySelector('.k-virtual-wrapper')!; + + expect(container.outerHTML).to.matchSnapshot(); + // check wrapper tag name + expect(wrapper.tagName.toLowerCase()).to.equal('ul'); + }); + + it('should handle dynamic data changes correctly', async () => { + class Demo extends Component<{list: number[]}> { + static template = ` + const VirtualList = this.VirtualList; + +
    Item {$value}
    +
    + `; + + static defaults() { + return { + list: Array.from({length: 100}, (_, i) => i) + } + } + private VirtualList = VirtualList; + } + + const [instance] = mount(Demo); + await wait(); + + const container = getElement('.k-virtual-container')!; + const wrapper = getElement('.k-virtual-wrapper')!; + + instance.set('list', instance.get('list')!.filter(i => i % 2 === 0)); + await wait(); + + // check deleted render + expect(wrapper.children.length).to.be.lessThan(100); + expect(wrapper.firstElementChild!.textContent).to.equal('Item 0'); + + container.scrollTop = 300; + await wait(50); + + // check scroll render + const middleContent = wrapper.firstElementChild!.textContent; + expect(middleContent).to.not.equal('Item 0'); + + const newList = [...instance.get('list'), ...Array.from({length: 20}, (_, i) => i + 200)]; + instance.set('list', newList); + await wait(50); + + // check add render position not change + expect(wrapper.firstElementChild!.textContent).to.equal(middleContent); + + const prevScrollTop = container.scrollTop; + container.scrollTop = prevScrollTop + 200; + await wait(50); + + // check scroll to new content + expect(wrapper.firstElementChild!.textContent).to.not.equal(middleContent); + }); + + it('should handle visible area data changes', async () => { + class Demo extends Component<{list: number[]}> { + static template = ` + const VirtualList = this.VirtualList; + +
    Item {$value}
    +
    + `; + static defaults() { + return { + list: Array.from({length: 100}, (_, i) => i) + } + } + private VirtualList = VirtualList; + } + + const [instance, element] = mount(Demo); + await wait(); + + const container = getElement('.k-virtual-container')!; + const wrapper = getElement('.k-virtual-wrapper')!; + + container.scrollTop = 300; + await wait(50); + + const visibleFirstItem = wrapper.firstElementChild!.textContent; + const currentList = instance.get('list'); + + // delete visible items + const visibleIndex = parseInt(visibleFirstItem!.replace('Item ', '')); + const newList = currentList.filter(i => i !== visibleIndex && i !== visibleIndex + 1); + instance.set('list', newList); + await wait(); + + // check deleted render and position + expect(wrapper.firstElementChild!.textContent).to.not.equal(visibleFirstItem); + expect(wrapper.children.length).to.be.greaterThan(0); + }); +}); + diff --git a/components/virtualList/wrapper.ts b/components/virtualList/wrapper.ts index 534d8ed37..88580d589 100644 --- a/components/virtualList/wrapper.ts +++ b/components/virtualList/wrapper.ts @@ -2,7 +2,7 @@ import { Component, TypeDefs, ComponentConstructor } from 'intact'; import template from './wrapper.vdt'; import { useConfigContext } from '../config'; export interface VirtualListWrapperProps { - tagName: string | ComponentConstructor, + tagName?: string | ComponentConstructor, } const typeDefs: Required> = { From ff5ea26611314e53294cb0be64c696a29e5e9a51 Mon Sep 17 00:00:00 2001 From: Javey Date: Tue, 31 Dec 2024 15:04:58 +0800 Subject: [PATCH 13/18] feat: table virtual list --- components/affix/index.md | 1 + components/affix/index.ts | 2 + components/affix/useStyle.ts | 73 ++++++++++++++++--------------- components/table/demos/virtual.md | 8 ++-- components/table/table.vdt | 7 +-- 5 files changed, 50 insertions(+), 41 deletions(-) diff --git a/components/affix/index.md b/components/affix/index.md index 84cde6ae2..53e6009bb 100644 --- a/components/affix/index.md +++ b/components/affix/index.md @@ -13,6 +13,7 @@ sidebar: doc | bottom | 指定元素固定距离底部的位置 | `number` | `undefined` | | shouldFix | 自定义元素固定规则 | `Function` | `undefined` | | exclude | 排除某些固定的情况 | `Function` | `undefined` | +| disabled | 是否禁用 | `Boolean` | `false` | # 事件 diff --git a/components/affix/index.ts b/components/affix/index.ts index ebef3b04f..5b940dd1d 100644 --- a/components/affix/index.ts +++ b/components/affix/index.ts @@ -8,6 +8,7 @@ export interface AffixProps { bottom?: number exclude?: (data: ExcludeParam) => boolean shouldFix?: (data: ShouldFixParam) => boolean + disabled?: boolean } export interface AffixEvents { @@ -34,6 +35,7 @@ const typeDefs: Required> = { bottom: Number, exclude: Function, shouldFix: Function, + disabled: Boolean, }; export class Affix extends Component { diff --git a/components/affix/useStyle.ts b/components/affix/useStyle.ts index 173aeb9ce..4c376bb1b 100644 --- a/components/affix/useStyle.ts +++ b/components/affix/useStyle.ts @@ -12,44 +12,47 @@ export function useStyle(elementRef: RefObject) { let ro: ResizeObserver | null = null; function genStyle() { - let {top: offsetTop, bottom: offsetBottom, exclude, shouldFix} = instance.get(); - const {top, bottom, width, height} = elementRef.value!.getBoundingClientRect(); - - const setStyle = (styles: Record) => { - if (!exclude || exclude && !exclude({ - offsetTop, offsetBottom, top, bottom, width, height - })) { - style.set({ - position: 'fixed', - width: `${width}px`, - ...styles, - }); - containerStyle.set({ - height: `${height}px`, - }); - } else { - resetStyle(); - } - }; + let {top: offsetTop, bottom: offsetBottom, exclude, shouldFix, disabled} = instance.get(); - if (isNullOrUndefined(offsetTop) && isNullOrUndefined(offsetBottom)) { - offsetTop = 0; - } + if (!disabled) { + const {top, bottom, width, height} = elementRef.value!.getBoundingClientRect(); + + const setStyle = (styles: Record) => { + if (!exclude || exclude && !exclude({ + offsetTop, offsetBottom, top, bottom, width, height + })) { + style.set({ + position: 'fixed', + width: `${width}px`, + ...styles, + }); + containerStyle.set({ + height: `${height}px`, + }); + } else { + resetStyle(); + } + }; - if (!isNullOrUndefined(offsetTop)) { - if ( - shouldFix && shouldFix({offsetTop, offsetBottom}) || - !shouldFix && top < offsetTop - ) { - return setStyle({top: `${offsetTop}px`}); + if (isNullOrUndefined(offsetTop) && isNullOrUndefined(offsetBottom)) { + offsetTop = 0; } - } else { - const viewportHeight = document.documentElement.clientHeight; - if ( - shouldFix && shouldFix({offsetTop, offsetBottom, viewportHeight}) || - !shouldFix && !isNullOrUndefined(offsetBottom) && viewportHeight - bottom <= offsetBottom - ) { - return setStyle({bottom: `${offsetBottom}px`}); + + if (!isNullOrUndefined(offsetTop)) { + if ( + shouldFix && shouldFix({offsetTop, offsetBottom}) || + !shouldFix && top < offsetTop + ) { + return setStyle({top: `${offsetTop}px`}); + } + } else { + const viewportHeight = document.documentElement.clientHeight; + if ( + shouldFix && shouldFix({offsetTop, offsetBottom, viewportHeight}) || + !shouldFix && !isNullOrUndefined(offsetBottom) && viewportHeight - bottom <= offsetBottom + ) { + return setStyle({bottom: `${offsetBottom}px`}); + } } } diff --git a/components/table/demos/virtual.md b/components/table/demos/virtual.md index 1509d5527..1b889c61a 100644 --- a/components/table/demos/virtual.md +++ b/components/table/demos/virtual.md @@ -3,19 +3,21 @@ title: 虚拟表格 order: 36 --- -添加`virtual`属性,即可开启虚拟滚动模式,树形表格开启虚拟滚动需指定`rowKey` +添加`virtual`属性,并且指定滚动元素的高度(通过`fixHeader`指定)即可开启虚拟滚动模式 + +> 虚拟列表内部需要根据rowKey做缓存,不指定rowKey可能存在奇怪问题(默认rowKey为索引值) ```vdt import {Table, TableColumn} from 'kpc';

    表格

    - +

    树形表格

    - data.name}> +
    data.name} fixHeader="400">
    diff --git a/components/table/table.vdt b/components/table/table.vdt index 50316f5dc..68471de64 100644 --- a/components/table/table.vdt +++ b/components/table/table.vdt @@ -242,10 +242,12 @@ const {
    - + @@ -254,10 +256,9 @@ const {
    - + -
    Date: Wed, 8 Jan 2025 18:28:02 +0800 Subject: [PATCH 14/18] fix: optimize code --- components/select/index.md | 1 + components/select/menu.vdt | 4 +- components/table/index.md | 1 + components/table/table.ts | 4 +- components/table/table.vdt | 8 +- components/virtualList/container.ts | 23 +++--- components/virtualList/container.vdt | 7 +- components/virtualList/demos/delete.md | 62 ++++++++++++++++ components/virtualList/demos/scrollTo.md | 75 ------------------- components/virtualList/index.md | 12 ++- components/virtualList/index.spec.ts | 95 +++++++++++++++++++++++- components/virtualList/phantom.vdt | 5 +- components/virtualList/rows.vdt | 11 +-- components/virtualList/useRows.ts | 5 +- components/virtualList/useVirtualRows.ts | 50 +++++++++++-- components/virtualList/wrapper.vdt | 20 +++-- 16 files changed, 255 insertions(+), 128 deletions(-) create mode 100644 components/virtualList/demos/delete.md delete mode 100644 components/virtualList/demos/scrollTo.md diff --git a/components/select/index.md b/components/select/index.md index fe86180ce..302c6ff3e 100644 --- a/components/select/index.md +++ b/components/select/index.md @@ -35,6 +35,7 @@ sidebar: doc | position | 菜单弹出的位置,默认与触发器左侧对齐向下偏移`8px`的地方 | `Position` | `"left"` | `"bottom"` | `"right"` | `"top"` | `{my: 'left top+8', 'left bottom'}` | | flat | 是否展示扁平样式 | `boolean` | `false` | | draggable | 多选值是否支持拖动排序 | `boolean` | `false` | +| virtual | 是否开启虚拟列表 | `boolean` | `false` | ```ts type Position = { diff --git a/components/select/menu.vdt b/components/select/menu.vdt index 5d03ff60d..24da4a0f0 100644 --- a/components/select/menu.vdt +++ b/components/select/menu.vdt @@ -12,7 +12,7 @@ import {Tabs, Tab} from '../tabs'; import { VirtualList } from '../virtualList'; let {children, className} = this.get(); -const {card, searchable, multiple} = this.select.get(); +const {card, searchable, multiple, virtual} = this.select.get(); const { k } = this.config; const classNameObj = { @@ -46,7 +46,7 @@ if (card) {
    ); } else { - children = {children} + children = {children} } if (searchable) { diff --git a/components/table/index.md b/components/table/index.md index 0a2f72466..f73aebe08 100644 --- a/components/table/index.md +++ b/components/table/index.md @@ -48,6 +48,7 @@ sidebar: doc | fixFooter | `table`给定需要固定高度时,自定义footer固定 | `boolean` | `false` | | load | 指定异步加载节点数据的函数,该函数通过`Promise`返回数组来添加子节点数据 | (node: any) => Promise | void | `undefined` | | spreadArrowIndex | 指定树形表格展开Icon的所在列,默认在第一列 | `number` | `0` | +| virtual | 是否开启虚拟列表 | `boolean` | `false` | ```ts import {Props} from 'intact'; diff --git a/components/table/table.ts b/components/table/table.ts index f52b69a6e..d99512407 100644 --- a/components/table/table.ts +++ b/components/table/table.ts @@ -267,9 +267,9 @@ export class Table< // we can not use scrollIntoView with smooth, because it can only operate one element // at the same time // elem.scrollIntoView({behavior: 'smooth'}); - const headerHeight = (scrollElement.querySelector('thead') as HTMLElement).offsetHeight; + // const headerHeight = (scrollElement.querySelector('thead') as HTMLElement).offsetHeight; let scrollTop = scrollElement.scrollTop; - const offsetTop = tr.offsetTop - headerHeight; + const offsetTop = tr.offsetTop; const top = offsetTop - scrollTop; const topOneFrame = top / 60 / (100 / 1000); const step = () => { diff --git a/components/table/table.vdt b/components/table/table.vdt index 68471de64..b75ec8cb9 100644 --- a/components/table/table.vdt +++ b/components/table/table.vdt @@ -205,9 +205,9 @@ const tbody = ( return hidden || !spreaded; }); - /* return animation[0] ? */ - /* {rows} : */ - return {rows}; + return animation[0] && !virtual? + {rows} : + {rows}; })() } @@ -241,7 +241,7 @@ const { } = this.pagination;
    - + { private config = useConfigContext(); private virtualRows = useVirtualRows(); + // TODO // public scrollToIndex(index: number, behavior: ScrollBehavior = 'auto') { - // const { containerRef, getItemTop } = this.virtualState; - // if (containerRef?.value) { - // const top = getItemTop(index); - // containerRef.value.scrollTo({ - // top, - // behavior, - // }); - // } + // const { disabled } = this.get(); + // if (disabled) return; + + // let height = 0; + // for (let i = 0; i < index; i++) { + // height += this.virtualRows.getRowHeightByIndex(i); + // } + // const containerDom = findDomFromVNode(this.$lastInput!, true) as HTMLElement; + // containerDom.scrollTo({ + // top: height, + // behavior, + // }); // } } diff --git a/components/virtualList/container.vdt b/components/virtualList/container.vdt index 4c76770e8..037e4feac 100644 --- a/components/virtualList/container.vdt +++ b/components/virtualList/container.vdt @@ -8,14 +8,15 @@ const { k } = this.config; const { notifyRows, startIndex, length, getTotalHeight, translateY } = this.virtualRows; const classNameObj = { - [`${k}-virtual`]: true, - [`${k}-virtual-container`]: true, - [makeContainerStyles(k)]: true, + [`${k}-virtual`]: !disabled, + [`${k}-virtual-container`]: !disabled, + [makeContainerStyles(k)]: !disabled, [className]: className, } + + +
    + {$value.label} +
    +
    +
    +``` + +```styl +.fixed-height-item + height 30px + border-bottom 1px solid #eee + padding 5px +.k-btn + margin-bottom 10px +``` + +```ts +import {bind} from 'kpc/components/utils'; +interface Props { + data: any[] +} + +export default class extends Component { + static template = template; + + static defaults() { + return { + data: [], + } as Props + } + + init() { + const newData = []; + const variableHeightData = []; + for (let index = 0; index < 10000; index++) { + newData.push({ + value: index, + label: `测试${index}` + }); + } + this.set({data: newData}); + } + + @bind + removeItems() { + const data = this.get('data').slice(); + data.splice(0, 5); + this.set('data', data); + } +} +``` diff --git a/components/virtualList/demos/scrollTo.md b/components/virtualList/demos/scrollTo.md deleted file mode 100644 index 6b8529da3..000000000 --- a/components/virtualList/demos/scrollTo.md +++ /dev/null @@ -1,75 +0,0 @@ ---- -title: 滑动到固定位置 -order: 2 ---- - -可以通过实例方法`scrollToIndex`滚动到具体位置 - -```vdt -import { VirtualList, Button } from 'kpc'; - -
    -
    - - -
    -
    - -
    - {$value.label} -
    -
    -
    -
    -``` - -```styl -.fixed-height-item - height 30px - border-bottom 1px solid #eee - padding 5px -.k-btn - margin-right 8px -``` - -```ts -import {bind} from 'kpc/components/utils'; -interface Props { - virtualListData: any[] -} - -export default class extends Component { - static template = template; - - static defaults() { - return { - virtualListData: [], - } as Props; - } - - init() { - const arr = []; - for (let index = 0; index < 10000; index++) { - arr.push({ - value: index, - label: `测试${index}` - }); - } - this.set({virtualListData: arr}); - } - - scrollToMiddle() { - // 滚动到中间位置 - const list = this.refs.fixedList as any; - const middleIndex = Math.floor((this.get('virtualListData') as any[]).length / 2); - list.scrollToIndex(middleIndex); - } - - scrollToEnd() { - // 滚动到底部 - const list = this.refs.fixedList as any; - const lastIndex = (this.get('virtualListData') as any[]).length - 1; - list.scrollToIndex(lastIndex, 'smooth'); - } -} -``` diff --git a/components/virtualList/index.md b/components/virtualList/index.md index 94819ccfc..bfc2d7b39 100644 --- a/components/virtualList/index.md +++ b/components/virtualList/index.md @@ -9,13 +9,11 @@ sidebar: doc | 属性 | 说明 | 类型 | 默认值 | | --- | --- | --- | --- | -| estimatedItemHeight | 列表每行预估高度 | `number` | `40` | -| bufferSize | 虚拟列表缓冲区数量 | `number` | `6` | -| height | 虚拟滚动区域的高度,不设置则继承父级容器高度 | `number` | `string` | `undefined` | +| disabled | 是否禁用虚拟化 | `boolean` | `false` | -# 方法 + -| 方法名 | 说明 | 参数 | -| --- | --- | --- | -| scrollToIndex | 滚动到指定索引位置 | `(index: number, behavior?: 'auto' \| 'smooth') => void` | + + + diff --git a/components/virtualList/index.spec.ts b/components/virtualList/index.spec.ts index f2dbc8823..92fddda55 100644 --- a/components/virtualList/index.spec.ts +++ b/components/virtualList/index.spec.ts @@ -6,7 +6,7 @@ import CombinedDemo from '~/components/virtualList/demos/combined'; describe('VirtualList', () => { - // afterEach(() => unmount()); + afterEach(() => unmount()); it('should render virtual list correctly', async () => { const [instance, element] = mount(BasicDemo); @@ -155,5 +155,98 @@ describe('VirtualList', () => { expect(wrapper.firstElementChild!.textContent).to.not.equal(visibleFirstItem); expect(wrapper.children.length).to.be.greaterThan(0); }); + + it('should clean up height cache when items are deleted', async () => { + class Demo extends Component<{list: number[]}> { + static template = ` + const VirtualList = this.VirtualList; + +
    Item {$value}
    +
    + `; + static defaults() { + return { + list: Array.from({length: 100}, (_, i) => i) + } + } + private VirtualList = VirtualList; + } + + const [instance, element] = mount(Demo); + await wait(); + + const container = getElement('.k-virtual-container')!; + + // first cache some height + container.scrollTop = 300; + await wait(50); + + // get first visible item + const wrapper = getElement('.k-virtual-wrapper')!; + const firstVisibleItem = wrapper.firstElementChild!; + const firstVisibleIndex = parseInt(firstVisibleItem.textContent!.replace('Item ', '')); + + // delete visible items + const currentList = instance.get('list'); + const newList = currentList.filter(i => i > firstVisibleIndex + 5); + instance.set('list', newList); + await wait(50); + + // check scroll position is adjusted + const newFirstItem = wrapper.firstElementChild!; + expect(parseInt(newFirstItem.textContent!.replace('Item ', ''))).to.be.greaterThan(firstVisibleIndex); + + // scroll again to check render is normal + container.scrollTop += 100; + await wait(50); + + // check new render position is correct + const afterScrollItem = wrapper.firstElementChild!; + expect(afterScrollItem.textContent).to.not.equal(newFirstItem.textContent); + }); + + it('should update total height when deleting last items', async () => { + class Demo extends Component<{list: number[]}> { + static template = ` + const VirtualList = this.VirtualList; + +
    Item {$value}
    +
    + `; + static defaults() { + return { + list: Array.from({length: 100}, (_, i) => i) + } + } + private VirtualList = VirtualList; + } + + const [instance, element] = mount(Demo); + await wait(); + + const container = getElement('.k-virtual-container')!; + const phantom = getElement('.k-virtual-phantom')!; + + // record initial total height + const initialHeight = parseInt(phantom.style.height); + + // scroll to cache height + container.scrollTop = 300; + await wait(50); + + // delete last 5 items + const currentList = instance.get('list'); + const newList = currentList.slice(0, -5); + instance.set('list', newList); + await wait(50); + + // scroll to trigger update + container.scrollTop = 0; + await wait(50); + + // check total height is updated + const finalHeight = parseInt(phantom.style.height); + expect(finalHeight).to.equal(initialHeight - 5 * 30); // 每项高度30px + }); }); diff --git a/components/virtualList/phantom.vdt b/components/virtualList/phantom.vdt index c2b407afe..5f30e750f 100644 --- a/components/virtualList/phantom.vdt +++ b/components/virtualList/phantom.vdt @@ -14,7 +14,10 @@ const classNameObj = { } - {({ getTotalHeight }) => { + {({ getTotalHeight, disabled }) => { + if (disabled) { + return + } return createVNode(tagName || 'div', { ...getRestProps(this), className: cx(classNameObj), diff --git a/components/virtualList/rows.vdt b/components/virtualList/rows.vdt index f637d24a0..60ced3535 100644 --- a/components/virtualList/rows.vdt +++ b/components/virtualList/rows.vdt @@ -2,14 +2,15 @@ import { createFragment, createVNode } from 'intact'; import { isNullOrUndefined } from 'intact-shared'; import { context as VirtualRowsContext } from './useVirtualRows'; -const { tagName } = this.get(); +let { tagName, children } = this.get(); const rows = this.rows; - {({ notifyRows, startIndex, length }) => { - const children = rows.value.slice(startIndex, startIndex + length); - - notifyRows(rows); + {({ notifyRows, startIndex, length, disabled }) => { + if (!disabled) { + children = rows.value.slice(startIndex, startIndex + length); + notifyRows(rows.value); + } if (isNullOrUndefined(tagName)) { return createFragment(children, 8 /* ChildrenTypes.HasKeyedChildren */); diff --git a/components/virtualList/useRows.ts b/components/virtualList/useRows.ts index f4f2d7570..cf096b1e6 100644 --- a/components/virtualList/useRows.ts +++ b/components/virtualList/useRows.ts @@ -1,8 +1,5 @@ -import { useInstance, VNode, createRef, normalizeChildren, createFragment, createVNode, ChildrenTypes, createVoidVNode } from 'intact'; +import { useInstance, VNode, createRef, createFragment } from 'intact'; import { VirtualListRows } from './rows'; -import { useState, State } from '../../hooks/useState'; -import { useReceive } from '../../hooks/useReceive'; -import { isNullOrUndefined } from 'intact-shared'; export function useRows() { const instance = useInstance() as VirtualListRows; diff --git a/components/virtualList/useVirtualRows.ts b/components/virtualList/useVirtualRows.ts index 4d9ec25bf..f176e1458 100644 --- a/components/virtualList/useVirtualRows.ts +++ b/components/virtualList/useVirtualRows.ts @@ -20,9 +20,43 @@ export function useVirtualRows() { const startIndex = useState(0); const length = useState(MIN_LENGTH); - let rows = useState([]); - function notifyRows(_rows: State) { - rows = _rows; + let rows: VNode[] = []; + function notifyRows(_rows: VNode[]) { + const oldRows = rows; + const oldLength = rows.length; + rows = _rows; + // diff oldRows, newRows + const newKeys = new Set(_rows.map(row => row.key)); + + // clear delete key + oldRows + .map(row => row.key!) + .filter(key => !newKeys.has(key)) + .forEach(key => { + const height = rowsHeightMap.get(key); + if (height !== undefined) { + calculatedHeight -= height; + rowsHeightMap.delete(key); + } + }); + + // update rowAvgHeight + if (rowsHeightMap.size === 0) { + rowAvgHeight = calculatedHeight = 0; + } else { + rowAvgHeight = calculatedHeight / rowsHeightMap.size; + } + + if (_rows.length < oldLength) { + const maxStartIndex = Math.max(0, _rows.length - length.value); + if (startIndex.value > maxStartIndex) { + startIndex.set(maxStartIndex); + // 重新计算位置 + nextTick(() => { + handleScroll(); + }); + } + } } let calculatedHeight = 0; @@ -30,10 +64,10 @@ export function useVirtualRows() { function calculateRowsHeight() { for ( let i = startIndex.value; - i < startIndex.value + length.value && i < rows.value.length; + i < startIndex.value + length.value && i < rows.length; i++ ) { - const row = rows.value[i]; + const row = rows[i]; const key = row.key!; if (!rowsHeightMap.has(key)) { const rowDom = findDomFromVNode(row, true) as HTMLElement; @@ -70,7 +104,7 @@ export function useVirtualRows() { }); function getTotalHeight() { - return calculatedHeight + rowAvgHeight * (rows.value.length - rowsHeightMap.size); + return calculatedHeight + rowAvgHeight * (rows.length - rowsHeightMap.size); } const translateY = useState(0); @@ -83,7 +117,7 @@ export function useVirtualRows() { let accumulatedHeight = 0; let start = 0; - while (start < rows.value.length) { + while (start < rows.length) { accumulatedHeight += getRowHeightByIndex(start); if (accumulatedHeight > scrollTop) { break; @@ -103,7 +137,7 @@ export function useVirtualRows() { } function getRowHeightByIndex(index: number) { - const key = rows.value[index].key!; + const key = rows[index].key!; return rowsHeightMap.get(key) || rowAvgHeight; } diff --git a/components/virtualList/wrapper.vdt b/components/virtualList/wrapper.vdt index 6ee350722..cb7ca2d15 100644 --- a/components/virtualList/wrapper.vdt +++ b/components/virtualList/wrapper.vdt @@ -6,17 +6,23 @@ import { cx } from '@emotion/css'; const { children, className, tagName, style } = this.get(); const { k } = this.config; -const classNameObj = { - [`${k}-virtual-wrapper`]: true, - [className]: className, -} - - {({ translateY }) => { + {({ translateY, disabled }) => { + const classNameObj = { + [`${k}-virtual-wrapper`]: !disabled, + [className]: className, + } + + let _style = {}; + if (!disabled) { + _style = { + transform: `translateY(${translateY}px)` + }; + } return createVNode(tagName || 'div', { ...getRestProps(this), className: cx(classNameObj), - style: addStyle(style, {transform: `translateY(${translateY}px)`}) + style: addStyle(style, _style) }, children); }} From c5d15d94121068e606b242c01c44677377956bd4 Mon Sep 17 00:00:00 2001 From: Javey Date: Wed, 8 Jan 2025 20:45:26 +0800 Subject: [PATCH 15/18] refactor: diff rows --- components/virtualList/demos/delete.md | 8 ++++++++ components/virtualList/phantom.vdt | 5 ++--- components/virtualList/useVirtualRows.ts | 24 ++++++++++++------------ components/virtualList/wrapper.vdt | 8 ++------ 4 files changed, 24 insertions(+), 21 deletions(-) diff --git a/components/virtualList/demos/delete.md b/components/virtualList/demos/delete.md index 7428b6311..8e12170b8 100644 --- a/components/virtualList/demos/delete.md +++ b/components/virtualList/demos/delete.md @@ -8,6 +8,7 @@ import { VirtualList, Button } from 'kpc';
    +
    {$value.label} @@ -58,5 +59,12 @@ export default class extends Component { data.splice(0, 5); this.set('data', data); } + + @bind + removeLastItems() { + const data = this.get('data').slice(); + data.splice(data.length - 5); + this.set('data', data); + } } ``` diff --git a/components/virtualList/phantom.vdt b/components/virtualList/phantom.vdt index 5f30e750f..5e4ae077a 100644 --- a/components/virtualList/phantom.vdt +++ b/components/virtualList/phantom.vdt @@ -15,9 +15,8 @@ const classNameObj = { {({ getTotalHeight, disabled }) => { - if (disabled) { - return - } + if (disabled) return; + return createVNode(tagName || 'div', { ...getRestProps(this), className: cx(classNameObj), diff --git a/components/virtualList/useVirtualRows.ts b/components/virtualList/useVirtualRows.ts index f176e1458..b2d2536d8 100644 --- a/components/virtualList/useVirtualRows.ts +++ b/components/virtualList/useVirtualRows.ts @@ -2,6 +2,7 @@ import { createContext } from '../context'; import { useState, watchState, State } from '../../hooks/useState'; import { useInstance, nextTick, VNode, onMounted, onUnmounted, Key, findDomFromVNode } from 'intact'; import { VirtualListContainer } from './container'; +import { isNullOrUndefined } from 'intact-shared'; type ContextValue = { notifyRows: (rows: State) => void; @@ -15,11 +16,13 @@ const MIN_LENGTH = 10; const BUFFER_SIZE = 3; export function useVirtualRows() { - const instance = useInstance() as VirtualListContainer; + const instance = useInstance() as unknown as VirtualListContainer; const rowsHeightMap = new Map(); const startIndex = useState(0); const length = useState(MIN_LENGTH); + let calculatedHeight = 0; + let rowAvgHeight = 0; let rows: VNode[] = []; function notifyRows(_rows: VNode[]) { const oldRows = rows; @@ -28,17 +31,16 @@ export function useVirtualRows() { // diff oldRows, newRows const newKeys = new Set(_rows.map(row => row.key)); - // clear delete key - oldRows - .map(row => row.key!) - .filter(key => !newKeys.has(key)) - .forEach(key => { - const height = rowsHeightMap.get(key); - if (height !== undefined) { + for (let i = 0; i < oldLength; i++) { + const oldKey = oldRows[i].key!; + if (!newKeys.has(oldKey)) { + const height = rowsHeightMap.get(oldKey); + if (!isNullOrUndefined(height)) { calculatedHeight -= height; - rowsHeightMap.delete(key); + rowsHeightMap.delete(oldKey); } - }); + } + } // update rowAvgHeight if (rowsHeightMap.size === 0) { @@ -59,8 +61,6 @@ export function useVirtualRows() { } } - let calculatedHeight = 0; - let rowAvgHeight = 0; function calculateRowsHeight() { for ( let i = startIndex.value; diff --git a/components/virtualList/wrapper.vdt b/components/virtualList/wrapper.vdt index cb7ca2d15..f856319bd 100644 --- a/components/virtualList/wrapper.vdt +++ b/components/virtualList/wrapper.vdt @@ -13,12 +13,8 @@ const { k } = this.config; [className]: className, } - let _style = {}; - if (!disabled) { - _style = { - transform: `translateY(${translateY}px)` - }; - } + const _style = !disabled ? { transform: `translateY(${translateY}px)` } : {}; + return createVNode(tagName || 'div', { ...getRestProps(this), className: cx(classNameObj), From db1425fa0ee2bee10cf85b18b28fb62f36e96e96 Mon Sep 17 00:00:00 2001 From: Javey Date: Wed, 8 Jan 2025 20:51:49 +0800 Subject: [PATCH 16/18] docs(Table): 10000 rows for virtual demo --- components/table/demos/virtual.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/table/demos/virtual.md b/components/table/demos/virtual.md index 1b889c61a..5d5376143 100644 --- a/components/table/demos/virtual.md +++ b/components/table/demos/virtual.md @@ -53,7 +53,7 @@ export default class extends Component { } init() { const arr = []; - for (let index = 0; index < 100; index++) { + for (let index = 0; index < 10000; index++) { arr.push({ a: `Cell ${index}-1`, b: `Cell ${index}-2` @@ -93,7 +93,7 @@ export default class extends Component { return result; }; - const data = generateTreeData(100); + const data = generateTreeData(10000); this.set({ variableHeightData: data }); } From fd4168da7aaf0056dce37a28960829a5c9df828f Mon Sep 17 00:00:00 2001 From: zhangbing4 Date: Wed, 8 Jan 2025 21:26:34 +0800 Subject: [PATCH 17/18] fix: optimize test --- components/dialog/styles.ts | 4 ++-- components/virtualList/index.spec.ts | 27 +++++++++++++++++++++------ 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/components/dialog/styles.ts b/components/dialog/styles.ts index fa6cc17c2..883ddd0ee 100644 --- a/components/dialog/styles.ts +++ b/components/dialog/styles.ts @@ -52,8 +52,8 @@ const defaults = { padding: `0 24px`, bodyMarginTop: `-25px`, tipIconMarginBottom: '10px', - tipIconFontSize: '24px', - tipIconLineHeight: '24px', + tipIconFontSize: '40px', + tipIconLineHeight: '40px', // with title titleFontWeight: '500', diff --git a/components/virtualList/index.spec.ts b/components/virtualList/index.spec.ts index 92fddda55..041b52550 100644 --- a/components/virtualList/index.spec.ts +++ b/components/virtualList/index.spec.ts @@ -231,22 +231,37 @@ describe('VirtualList', () => { const initialHeight = parseInt(phantom.style.height); // scroll to cache height - container.scrollTop = 300; + container.scrollTop = initialHeight; await wait(50); + const wrapper = getElement('.k-virtual-wrapper')!; + const lastVisibleItem = wrapper.lastElementChild!; + const lastVisibleIndex = parseInt(lastVisibleItem.textContent!.replace('Item ', '')); + // delete last 5 items const currentList = instance.get('list'); const newList = currentList.slice(0, -5); instance.set('list', newList); await wait(50); - - // scroll to trigger update - container.scrollTop = 0; + container.scrollTop = container.scrollTop; + console.log('container.scrollTop', container.scrollTop); await wait(50); - - // check total height is updated + // 验证总高度已更新 const finalHeight = parseInt(phantom.style.height); expect(finalHeight).to.equal(initialHeight - 5 * 30); // 每项高度30px + + // 验证新的最后一个元素(原来的倒数第6个)在可视区域底部 + const newLastItem = wrapper.lastElementChild!; + const newLastIndex = parseInt(newLastItem.textContent!.replace('Item ', '')); + expect(newLastIndex).to.equal(94); // 新的最后一项应该是94(原来的倒数第6个) + + // 验证这个元素确实在可视区域底部 + const containerRect = container.getBoundingClientRect(); + const lastItemRect = newLastItem.getBoundingClientRect(); + console.log('containerRect', containerRect.bottom); + console.log('lastItemRect', lastItemRect.bottom); + const isAtBottom = containerRect.bottom - lastItemRect.bottom === 0; // 容器跟最后一个元素相对于视口的垂直位置,相等即为在底部 + expect(isAtBottom).to.be.true; }); }); From 1a2aa9ad87d2b9d26a4a279c6dfb1a2baaad3f02 Mon Sep 17 00:00:00 2001 From: zhangbing4 Date: Thu, 9 Jan 2025 11:56:10 +0800 Subject: [PATCH 18/18] fix: run test --- components/select/demos/virtual.md | 6 +- components/select/group.vdt | 4 +- components/table/index.spec.ts | 3 +- components/table/row.ts | 1 - components/table/row.vdt | 4 +- components/table/table.ts | 11 - components/table/useScroll.ts | 13 +- components/virtualList/index.spec.ts | 18 +- docs/changelog.md | 10 + .../__tests__/__snapshots__/Vue Next Demos.md | 108 +++--- test/__snapshots__/Cascader.md | 24 +- test/__snapshots__/Colorpicker.md | 32 +- test/__snapshots__/Demos.md | 319 ++++++++++-------- test/__snapshots__/Diagram.md | 4 +- test/__snapshots__/Dialog.md | 14 +- test/__snapshots__/Editable.md | 16 +- test/__snapshots__/Form.md | 22 +- test/__snapshots__/Input.md | 10 +- test/__snapshots__/Layout.md | 2 +- test/__snapshots__/Pagination.md | 4 +- test/__snapshots__/Select.md | 14 +- test/__snapshots__/Slider.md | 38 +-- test/__snapshots__/Spinner.md | 22 +- test/__snapshots__/Table.md | 84 ++--- test/__snapshots__/Tag.md | 4 +- test/__snapshots__/Timepicker.md | 6 +- test/__snapshots__/Transfer.md | 20 +- test/__snapshots__/Tree.md | 4 +- test/__snapshots__/VirtualList.md | 14 + 29 files changed, 464 insertions(+), 367 deletions(-) create mode 100644 test/__snapshots__/VirtualList.md diff --git a/components/select/demos/virtual.md b/components/select/demos/virtual.md index c543e44c4..24a27a288 100644 --- a/components/select/demos/virtual.md +++ b/components/select/demos/virtual.md @@ -3,13 +3,13 @@ title: 虚拟列表 order: 14 --- -`virtualMode`属性开启虚拟列表 +`virtual`属性开启虚拟列表 ```vdt import {Select, Option} from 'kpc';
    - @@ -35,7 +35,7 @@ export default class extends Component { init() { const arr = []; - for (let index = 0; index < 50; index++) { + for (let index = 0; index < 10000; index++) { arr.push({ value: index, label: `测试${index}` diff --git a/components/select/group.vdt b/components/select/group.vdt index 91485aeba..1fab469af 100644 --- a/components/select/group.vdt +++ b/components/select/group.vdt @@ -3,7 +3,7 @@ import {getRestProps} from '../utils'; import { VirtualList } from '../virtualList'; const {children, label, className} = this.get(); -const {card} = this.select.get(); +const {card, virtual} = this.select.get(); const { k } = this.config; const classNameObj = { @@ -16,5 +16,5 @@ const classNameObj = {
    {label}
    - {children} + {children}
    diff --git a/components/table/index.spec.ts b/components/table/index.spec.ts index d834fc40e..1f68284ee 100644 --- a/components/table/index.spec.ts +++ b/components/table/index.spec.ts @@ -489,13 +489,14 @@ describe('Table', () => { it('tree', async () => { const [instance, element] = mount(TreeDemo); - const table = instance.$lastInput!.children as Table; + const table = (instance.$lastInput!.children as any)[0].children as Table; // check all const checkbox = element.querySelector('.k-checkbox') as HTMLElement; checkbox.click(); await wait(); expect(element.innerHTML).to.matchSnapshot(); + debugger; expect(table.getCheckedData()).to.have.lengthOf(8); const arrow = element.querySelector('.k-table-arrow') as HTMLElement; diff --git a/components/table/row.ts b/components/table/row.ts index dfa7bc016..8e54a1702 100644 --- a/components/table/row.ts +++ b/components/table/row.ts @@ -42,7 +42,6 @@ export interface TableRowProps { animation: boolean spreadArrowIndex: number loaded: boolean - rowRef: RefObject draggable: boolean draggingKey: TableRowKey | null diff --git a/components/table/row.vdt b/components/table/row.vdt index fef8f7aaf..2ae2ddbe2 100644 --- a/components/table/row.vdt +++ b/components/table/row.vdt @@ -12,7 +12,7 @@ const { allDisabled, selected, /* hidden, */spreaded, hasChildren, indent, key, offsetMap, draggable, draggingKey, animation, loaded, - rowRef, spreadArrowIndex + spreadArrowIndex } = this.get(); const { k } = this.config; @@ -109,5 +109,5 @@ const rows = animation ? 'ev-dragover': draggable ? this.onRowDragOver : null, 'ev-dragend': draggable ? this.onRowDragEnd : null, 'draggable': draggable ? true : null, - }, undefined, rowRef)} + })} diff --git a/components/table/table.ts b/components/table/table.ts index d99512407..b015e10bd 100644 --- a/components/table/table.ts +++ b/components/table/table.ts @@ -67,8 +67,6 @@ export interface TableProps< pagination?: boolean | PaginationProps fixFooter?: boolean virtual?: boolean - estimatedRowHeight?: number; - bufferSize?: number; spreadArrowIndex?: number; load?: (value: T) => Promise | void } @@ -139,8 +137,6 @@ const typeDefs: Required>> = { pagination: [Boolean, Object], fixFooter: Boolean, virtual: Boolean, - estimatedRowHeight: Number, - bufferSize: Number, spreadArrowIndex: Number, load: Function, }; @@ -155,8 +151,6 @@ const defaults = (): Partial => ({ minColWidth: 40, animation: true, showIndeterminate: true, - estimatedRowHeight: 40, - bufferSize: 6, }); const events: Events = { @@ -191,11 +185,6 @@ export class Table< this.scroll.scrollRef, this.columns.getColumns, ); - // private virtual = useTableVirtual( - // this.pagination.data, - // this.scroll.scrollRef, - // this.scroll.callbacks, - // ); private resizable = useResizable( this.scroll.scrollRef, this.width.tableRef, diff --git a/components/table/useScroll.ts b/components/table/useScroll.ts index eba514ad8..2a2169dac 100644 --- a/components/table/useScroll.ts +++ b/components/table/useScroll.ts @@ -1,22 +1,17 @@ import {RefObject, onMounted, onBeforeUnmount, createRef} from 'intact'; -export type ScrollCallback = (scrollLeft: number, scrollTop?: number) => void; +export type ScrollCallback = (scrollLeft: number) => void; export function useScroll() { const callbacks: ScrollCallback[] = []; const scrollRef = createRef(); let scrollLeft = 0; - let scrollTop = 0; function onScroll() { - const element = scrollRef.value!; - const newScrollLeft = element.scrollLeft; - const newScrollTop = element.scrollTop; - - if (scrollLeft !== newScrollLeft || scrollTop !== newScrollTop) { + const newScrollLeft = scrollRef.value!.scrollLeft; + if (scrollLeft !== newScrollLeft) { scrollLeft = newScrollLeft; - scrollTop = newScrollTop; - callbacks.forEach(fn => fn(scrollLeft, scrollTop)); + callbacks.forEach(fn => fn(scrollLeft)); } } diff --git a/components/virtualList/index.spec.ts b/components/virtualList/index.spec.ts index 041b52550..3402c76a0 100644 --- a/components/virtualList/index.spec.ts +++ b/components/virtualList/index.spec.ts @@ -243,24 +243,20 @@ describe('VirtualList', () => { const newList = currentList.slice(0, -5); instance.set('list', newList); await wait(50); - container.scrollTop = container.scrollTop; - console.log('container.scrollTop', container.scrollTop); - await wait(50); - // 验证总高度已更新 + + // check total height is updated const finalHeight = parseInt(phantom.style.height); - expect(finalHeight).to.equal(initialHeight - 5 * 30); // 每项高度30px + expect(finalHeight).to.equal(initialHeight - 5 * 30); - // 验证新的最后一个元素(原来的倒数第6个)在可视区域底部 + // check new last item is at bottom const newLastItem = wrapper.lastElementChild!; const newLastIndex = parseInt(newLastItem.textContent!.replace('Item ', '')); - expect(newLastIndex).to.equal(94); // 新的最后一项应该是94(原来的倒数第6个) + expect(newLastIndex).to.equal(94); - // 验证这个元素确实在可视区域底部 + // check new last item is at bottom const containerRect = container.getBoundingClientRect(); const lastItemRect = newLastItem.getBoundingClientRect(); - console.log('containerRect', containerRect.bottom); - console.log('lastItemRect', lastItemRect.bottom); - const isAtBottom = containerRect.bottom - lastItemRect.bottom === 0; // 容器跟最后一个元素相对于视口的垂直位置,相等即为在底部 + const isAtBottom = Math.abs((containerRect.bottom - lastItemRect.bottom)) <= 1; expect(isAtBottom).to.be.true; }); }); diff --git a/docs/changelog.md b/docs/changelog.md index b7471724e..a96542398 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -3,6 +3,16 @@ title: 更新日志 order: 99 sidebar: doc --- +## v3.5.0 + +1. `Add` `VirtualList` 组件支持任意`list`结构虚拟列表化 [#971](https://github.com/ksc-fe/kpc/issues/971) +2. `Add` `Table`组件新增`virtual`属性,支持表格虚拟化 [#971](https://github.com/ksc-fe/kpc/issues/971) +2. `Add` `Select`组件新增`virtual`属性,支持选择框虚拟化 [#971](https://github.com/ksc-fe/kpc/issues/971) +3. `Fix` `Input`组件`autoWidth`输入空格不能自动撑开宽度 [#1046](https://github.com/ksc-fe/kpc/issues/1046) +4. `Fix` `Select`组件远程搜索时,不能输入空格 [#1047](https://github.com/ksc-fe/kpc/issues/1047) +5. `Fix` `Datepicker`传入`dayjs`对象报类型不匹配警告 [#1048](https://github.com/ksc-fe/kpc/issues/1048) +6. `Fix` `Dialog`组件`confirm`模式,UI调整 + ## v3.4.5 1. `Add` `Tip` 组件支持`Icon`及自定义`Icon` [#1043](https://github.com/ksc-fe/kpc/issues/1043) diff --git a/packages/kpc-vue-next/__tests__/__snapshots__/Vue Next Demos.md b/packages/kpc-vue-next/__tests__/__snapshots__/Vue Next Demos.md index f011f537f..f88383044 100644 --- a/packages/kpc-vue-next/__tests__/__snapshots__/Vue Next Demos.md +++ b/packages/kpc-vue-next/__tests__/__snapshots__/Vue Next Demos.md @@ -1276,6 +1276,12 @@ "
    请选择
    请选择
    请选择
    请选择


    请选择
    请选择
    请选择
    请选择
    " ``` +#### `Select virtual` + +``` +"
    请选择
    " +``` + #### `Select noBorder` ``` @@ -1375,7 +1381,7 @@ #### `Spin overlay` ``` -"
    表头1
    表头2
    第一行哈哈2
    第二行哈哈2
    第三行哈哈3
    " +"
    表头1
    表头2
    第一行哈哈2
    第二行哈哈2
    第三行哈哈3
    " ``` ## `spinner` @@ -1439,7 +1445,7 @@ #### `Split complex` ``` -"
    名称
    网段
    操作
    name 0127.0.0.0删除
    name 1127.0.0.1删除
    name 2127.0.0.2删除
    name 3127.0.0.3删除
    name 4127.0.0.4删除
    name 5127.0.0.5删除
    name 6127.0.0.6删除
    name 7127.0.0.7删除
    name 8127.0.0.8删除
    name 9127.0.0.9删除
    name 10127.0.0.10删除
    详情
    流量统计
    标签
    " +"
    名称
    网段
    操作
    name 0127.0.0.0删除
    name 1127.0.0.1删除
    name 2127.0.0.2删除
    name 3127.0.0.3删除
    name 4127.0.0.4删除
    name 5127.0.0.5删除
    name 6127.0.0.6删除
    name 7127.0.0.7删除
    name 8127.0.0.8删除
    name 9127.0.0.9删除
    name 10127.0.0.10删除
    详情
    流量统计
    标签
    " ``` #### `Split nested` @@ -1547,217 +1553,223 @@ #### `Table animation` ``` -"
    Title 1
    Title 3
    Cell 1-1Cell 1-3
    Cell 2-1Cell 2-3
    Title 1
    Title 3
    Cell 1-1Cell 1-3
    Cell 2-1Cell 2-3
    " +"
    Title 1
    Title 3
    Cell 1-1Cell 1-3
    Cell 2-1Cell 2-3
    Title 1
    Title 3
    Cell 1-1Cell 1-3
    Cell 2-1Cell 2-3
    " ``` #### `Table asyncTree` ``` -"
    Name
    Size
    Audios12MB
    Images14MB
    doc.pdf18MB
    " +"
    Name
    Size
    Audios12MB
    Images14MB
    doc.pdf18MB
    " ``` #### `Table basic` ``` -"
    Title 1
    Title 2
    Cell 1-1Cell 1-2
    Cell 2-1Cell 2-2
    " +"
    Title 1
    Title 2
    Cell 1-1Cell 1-2
    Cell 2-1Cell 2-2
    " ``` #### `Table checkType` ``` -"
    Title
    checkbox 1
    checkbox 2
    Title
    radio 1
    radio 2
    Title
    no check 1
    no check 2
    " +"
    Title
    checkbox 1
    checkbox 2
    Title
    radio 1
    radio 2
    Title
    no check 1
    no check 2
    " ``` #### `Table checkedKeys` ``` -"
    Radio without rowKey
    default checked by index
    unchecked
    Radio with rowKey
    default checked by rowKey
    unchecked
    checkbox without rowKey
    default checked by index
    unchecked
    default checked
    checkbox with rowKey
    default checked by rowKey
    unchecked
    " +"
    Radio without rowKey
    default checked by index
    unchecked
    Radio with rowKey
    default checked by rowKey
    unchecked
    checkbox without rowKey
    default checked by index
    unchecked
    default checked
    checkbox with rowKey
    default checked by rowKey
    unchecked
    " ``` #### `Table disableRow` ``` -"
    odd rows are uncheckable
    1
    2
    3
    4
    5
    " +"
    odd rows are uncheckable
    1
    2
    3
    4
    5
    " ``` #### `Table draggable` ``` -"
    Name
    IP
    Operation
    name 1127.0.0.1Remove
    name 2127.0.0.2Remove
    name 3127.0.0.3Remove
    name 4127.0.0.4Remove
    name 5127.0.0.5Remove
    name 6127.0.0.6Remove
    name 7127.0.0.7Remove
    name 8127.0.0.8Remove
    name 9127.0.0.9Remove
    name 10127.0.0.10Remove
    name 11127.0.0.11Remove
    name 12127.0.0.12Remove
    name 13127.0.0.13Remove
    name 14127.0.0.14Remove
    name 15127.0.0.15Remove
    name 16127.0.0.16Remove
    name 17127.0.0.17Remove
    name 18127.0.0.18Remove
    name 19127.0.0.19Remove
    name 20127.0.0.20Remove
    " +"
    Name
    IP
    Operation
    name 1127.0.0.1Remove
    name 2127.0.0.2Remove
    name 3127.0.0.3Remove
    name 4127.0.0.4Remove
    name 5127.0.0.5Remove
    name 6127.0.0.6Remove
    name 7127.0.0.7Remove
    name 8127.0.0.8Remove
    name 9127.0.0.9Remove
    name 10127.0.0.10Remove
    name 11127.0.0.11Remove
    name 12127.0.0.12Remove
    name 13127.0.0.13Remove
    name 14127.0.0.14Remove
    name 15127.0.0.15Remove
    name 16127.0.0.16Remove
    name 17127.0.0.17Remove
    name 18127.0.0.18Remove
    name 19127.0.0.19Remove
    name 20127.0.0.20Remove
    " ``` #### `Table ellipsis` ``` -"
    Title 1
    Title 2
    测试超长测试超长测试超长测试超长测试超长测试超长测试超长测试超长测试超长测试超长测试超长测试超长测试超长测试超长测试超长测试超长测试超长
    Cell 1-2
    Cell 2-1
    Cell 2-2
    " +"
    Title 1
    Title 2
    测试超长测试超长测试超长测试超长测试超长测试超长测试超长测试超长测试超长测试超长测试超长测试超长测试超长测试超长测试超长测试超长测试超长
    Cell 1-2
    Cell 2-1
    Cell 2-2
    " ``` #### `Table empty` ``` -"
    表头1
    表头2
    暂无数据
    表头1
    表头2
    没有数据
    " +"
    表头1
    表头2
    暂无数据
    表头1
    表头2
    没有数据
    " ``` #### `Table export` ``` -"
    定义该列单元格内容
    普通文本
    自定义导出文本
    操作
    第一行哈哈1自定义1删除
    第二行哈哈2自定义2删除
    " +"
    定义该列单元格内容
    普通文本
    自定义导出文本
    操作
    第一行哈哈1自定义1删除
    第二行哈哈2自定义2删除
    " ``` #### `Table fixColumn` ``` -"
    Name
    Column1
    Column2
    Column3
    Column4
    Action
    Johntesttesttesttestaction
    Tomtesttesttesttestaction
    Javeytesttesttesttestaction
    Name
    Column1
    Column2
    Column3
    Column4
    Action
    Johntesttesttesttestaction
    Tomtesttesttesttestaction
    Javeytesttesttesttestaction
    " +"
    Name
    Column1
    Column2
    Column3
    Column4
    Action
    Johntesttesttesttestaction
    Tomtesttesttesttestaction
    Javeytesttesttesttestaction
    Name
    Column1
    Column2
    Column3
    Column4
    Action
    Johntesttesttesttestaction
    Tomtesttesttesttestaction
    Javeytesttesttesttestaction
    " ``` #### `Table fixFooter` ``` -"
    footer不固定
    footer不固定
    下拉
    yeah!
    你好
    我很好
    自定义footer
    footer固定
    footer固定啦
    下拉
    yeah!
    你好
    我很好
    自定义footer
    footer,header同时固定
    footer固定啦
    下拉
    yeah!
    你好
    我很好
    自定义footer
    " +"
    footer不固定
    footer不固定
    下拉
    yeah!
    你好
    我很好
    自定义footer
    footer固定
    footer固定啦
    下拉
    yeah!
    你好
    我很好
    自定义footer
    footer,header同时固定
    footer固定啦
    下拉
    yeah!
    你好
    我很好
    自定义footer
    " ``` #### `Table fixHeader` ``` -"
    100px
    表头固定,但内容没有超出最高高度
    100px
    表头固定啦
    下拉
    yeah!
    " +"
    100px
    表头固定,但内容没有超出最高高度
    Name
    IP
    name 1127.0.0.1
    name 2127.0.0.2
    name 3127.0.0.3
    name 4127.0.0.4
    name 5127.0.0.5
    name 6127.0.0.6
    name 7127.0.0.7
    name 8127.0.0.8
    name 9127.0.0.9
    name 10127.0.0.10
    name 11127.0.0.11
    name 12127.0.0.12
    name 13127.0.0.13
    name 14127.0.0.14
    name 15127.0.0.15
    name 16127.0.0.16
    name 17127.0.0.17
    name 18127.0.0.18
    name 19127.0.0.19
    name 20127.0.0.20
    name 21127.0.0.21
    name 22127.0.0.22
    name 23127.0.0.23
    name 24127.0.0.24
    name 25127.0.0.25
    name 26127.0.0.26
    name 27127.0.0.27
    name 28127.0.0.28
    name 29127.0.0.29
    name 30127.0.0.30
    name 31127.0.0.31
    name 32127.0.0.32
    name 33127.0.0.33
    name 34127.0.0.34
    name 35127.0.0.35
    name 36127.0.0.36
    name 37127.0.0.37
    name 38127.0.0.38
    name 39127.0.0.39
    name 40127.0.0.40
    name 41127.0.0.41
    name 42127.0.0.42
    name 43127.0.0.43
    name 44127.0.0.44
    name 45127.0.0.45
    name 46127.0.0.46
    name 47127.0.0.47
    name 48127.0.0.48
    name 49127.0.0.49
    name 50127.0.0.50
    name 51127.0.0.51
    name 52127.0.0.52
    name 53127.0.0.53
    name 54127.0.0.54
    name 55127.0.0.55
    name 56127.0.0.56
    name 57127.0.0.57
    name 58127.0.0.58
    name 59127.0.0.59
    name 60127.0.0.60
    name 61127.0.0.61
    name 62127.0.0.62
    name 63127.0.0.63
    name 64127.0.0.64
    name 65127.0.0.65
    name 66127.0.0.66
    name 67127.0.0.67
    name 68127.0.0.68
    name 69127.0.0.69
    name 70127.0.0.70
    name 71127.0.0.71
    name 72127.0.0.72
    name 73127.0.0.73
    name 74127.0.0.74
    name 75127.0.0.75
    name 76127.0.0.76
    name 77127.0.0.77
    name 78127.0.0.78
    name 79127.0.0.79
    name 80127.0.0.80
    name 81127.0.0.81
    name 82127.0.0.82
    name 83127.0.0.83
    name 84127.0.0.84
    name 85127.0.0.85
    name 86127.0.0.86
    name 87127.0.0.87
    name 88127.0.0.88
    name 89127.0.0.89
    name 90127.0.0.90
    name 91127.0.0.91
    name 92127.0.0.92
    name 93127.0.0.93
    name 94127.0.0.94
    name 95127.0.0.95
    name 96127.0.0.96
    name 97127.0.0.97
    name 98127.0.0.98
    name 99127.0.0.99
    name 100127.0.0.100
    " ``` #### `Table footer` ``` -"
    Title 1
    Title 2
    Cell 1-1Cell 1-2
    Cell 2-1Cell 2-2
    自定义底部
    " +"
    Title 1
    Title 2
    Cell 1-1Cell 1-2
    Cell 2-1Cell 2-2
    自定义底部
    " ``` #### `Table group` ``` -"
    名称
    状态
    主机1{data.status === 'active' ? '运行中' : '已关闭'}
    主机2{data.status === 'active' ? '运行中' : '已关闭'}
    主机3{data.status === 'active' ? '运行中' : '已关闭'}
    名称
    状态
    主机1{data.status === 'active' ? '运行中' : '已关闭'}
    主机2{data.status === 'active' ? '运行中' : '已关闭'}
    主机3{data.status === 'active' ? '运行中' : '已关闭'}
    " +"
    名称
    状态
    主机1{data.status === 'active' ? '运行中' : '已关闭'}
    主机2{data.status === 'active' ? '运行中' : '已关闭'}
    主机3{data.status === 'active' ? '运行中' : '已关闭'}
    名称
    状态
    主机1{data.status === 'active' ? '运行中' : '已关闭'}
    主机2{data.status === 'active' ? '运行中' : '已关闭'}
    主机3{data.status === 'active' ? '运行中' : '已关闭'}
    " ``` #### `Table groupHeader` ``` -"
    Weekday
    Forenoon
    Afternoon
    Time
    Classes
    Time
    Class 5
    Class 6
    Class 7
    Class 1
    Class 2
    Class 3
    Class 4
    Monday08:00 ~ 12:00EnglishMathematicsChineseHistory14:00 ~ 17:00GeopraghyEnglishMathematics
    Tuesday08:00 ~ 12:00MathematicsChineseHistoryGeopraghy14:00 ~ 17:00EnglishMathematicsChinese
    Wendesday08:00 ~ 12:00ChineseHistoryGeopraghyEnglish14:00 ~ 17:00MathematicsChineseHistory
    Thursday08:00 ~ 12:00HistoryGeopraghyEnglishMathematics14:00 ~ 17:00ChineseHistoryGeopraghy
    Friday08:00 ~ 12:00GeopraghyEnglishMathematicsChinese14:00 ~ 17:00HistoryGeopraghyEnglish
    " +"
    Weekday
    Forenoon
    Afternoon
    Time
    Classes
    Time
    Class 5
    Class 6
    Class 7
    Class 1
    Class 2
    Class 3
    Class 4
    Monday08:00 ~ 12:00EnglishMathematicsChineseHistory14:00 ~ 17:00GeopraghyEnglishMathematics
    Tuesday08:00 ~ 12:00MathematicsChineseHistoryGeopraghy14:00 ~ 17:00EnglishMathematicsChinese
    Wendesday08:00 ~ 12:00ChineseHistoryGeopraghyEnglish14:00 ~ 17:00MathematicsChineseHistory
    Thursday08:00 ~ 12:00HistoryGeopraghyEnglishMathematics14:00 ~ 17:00ChineseHistoryGeopraghy
    Friday08:00 ~ 12:00GeopraghyEnglishMathematicsChinese14:00 ~ 17:00HistoryGeopraghyEnglish
    " ``` #### `Table hidden` ``` -"
    Title 1
    Title 2
    Title 3
    Cell 1-1Cell 1-2cell 1-3
    Cell 2-1Cell 2-2cell 2-3

    无数据展示

    Title 1
    Title 2
    Title 3
    暂无数据
    " +"
    Title 1
    Title 2
    Title 3
    Cell 1-1Cell 1-2cell 1-3
    Cell 2-1Cell 2-2cell 2-3

    无数据展示

    Title 1
    Title 2
    Title 3
    暂无数据
    " ``` #### `Table hideHeader` ``` -"

    hideHeader:

    Cell 1-1Cell 1-2
    Cell 2-1Cell 2-2
    " +"

    hideHeader:

    Cell 1-1Cell 1-2
    Cell 2-1Cell 2-2
    " ``` #### `Table loading` ``` -"
    表头1
    表头2
    第一行哈哈1
    第二行哈哈2
    第三行哈哈3
    " +"
    表头1
    表头2
    第一行哈哈1
    第二行哈哈2
    第三行哈哈3
    " ``` #### `Table mergeCell` ``` -"
    Weekday
    Forenoon
    Afternoon
    Time
    Class 1
    Class 2
    Class 3
    Class 4
    Class 5
    Class 6
    Class 7
    Time
    Monday08:00 ~ 12:00EnglishMathematicsHistoryEnglishHistoryEnglishHistory14:00 ~ 17:00
    TuesdayEnglishGeopraghyHistoryEnglishHistoryMathematics
    WendesdayHistoryChineseHistoryEnglishChineseHistory
    ThursdayHistoryEnglishGeopraghyMathematicsGeopraghyEnglishMathematics
    FridayGeopraghyEnglishGeopraghyEnglish
    SaturdayGeopraghyEnglishGeopraghyEnglish
    Weekday
    Forenoon
    Afternoon
    Time
    Class 1
    Class 2
    Class 3
    Class 4
    Class 5
    Class 6
    Class 7
    Time
    Monday08:00 ~ 12:00EnglishMathematicsHistoryEnglishHistoryEnglishHistory14:00 ~ 17:00
    TuesdayEnglishGeopraghyHistoryEnglishHistoryMathematics
    WendesdayHistoryChineseHistoryEnglishChineseHistory
    ThursdayHistoryEnglishGeopraghyMathematicsGeopraghyEnglishMathematics
    FridayGeopraghyEnglishGeopraghyEnglish
    SaturdayGeopraghyEnglishGeopraghyEnglish
    " +"
    Weekday
    Forenoon
    Afternoon
    Time
    Class 1
    Class 2
    Class 3
    Class 4
    Class 5
    Class 6
    Class 7
    Time
    Monday08:00 ~ 12:00EnglishMathematicsHistoryEnglishHistoryEnglishHistory14:00 ~ 17:00
    TuesdayEnglishGeopraghyHistoryEnglishHistoryMathematics
    WendesdayHistoryChineseHistoryEnglishChineseHistory
    ThursdayHistoryEnglishGeopraghyMathematicsGeopraghyEnglishMathematics
    FridayGeopraghyEnglishGeopraghyEnglish
    SaturdayGeopraghyEnglishGeopraghyEnglish
    Weekday
    Forenoon
    Afternoon
    Time
    Class 1
    Class 2
    Class 3
    Class 4
    Class 5
    Class 6
    Class 7
    Time
    Monday08:00 ~ 12:00EnglishMathematicsHistoryEnglishHistoryEnglishHistory14:00 ~ 17:00
    TuesdayEnglishGeopraghyHistoryEnglishHistoryMathematics
    WendesdayHistoryChineseHistoryEnglishChineseHistory
    ThursdayHistoryEnglishGeopraghyMathematicsGeopraghyEnglishMathematics
    FridayGeopraghyEnglishGeopraghyEnglish
    SaturdayGeopraghyEnglishGeopraghyEnglish
    " ``` #### `Table pagination` ``` -"
    Name
    IP
    name 1127.0.0.1
    name 2127.0.0.2
    name 3127.0.0.3
    name 4127.0.0.4
    name 5127.0.0.5
    name 6127.0.0.6
    name 7127.0.0.7
    name 8127.0.0.8
    name 9127.0.0.9
    name 10127.0.0.10
    共 20 条
    10 条 / 页
    " +"
    Name
    IP
    name 1127.0.0.1
    name 2127.0.0.2
    name 3127.0.0.3
    name 4127.0.0.4
    name 5127.0.0.5
    name 6127.0.0.6
    name 7127.0.0.7
    name 8127.0.0.8
    name 9127.0.0.9
    name 10127.0.0.10
    共 20 条
    10 条 / 页
    " ``` #### `Table resizable` ``` -"
    通过minColWidth控制最小宽度100px
    隐藏列
    单独设置最小宽度300px
    标题
    ABC
    ABC
    " +"
    通过minColWidth控制最小宽度100px
    隐藏列
    单独设置最小宽度300px
    标题
    ABC
    ABC
    " ``` #### `Table rowCheckable` ``` -"
    点击整行不选中
    B
    AB
    A点我不会选中整行
    点击整行选中
    B
    A任何位置都可以哦~
    A点我会选中整行
    " +"
    点击整行不选中
    B
    AB
    A点我不会选中整行
    点击整行选中
    B
    A任何位置都可以哦~
    A点我会选中整行
    " ``` #### `Table rowClassName` ``` -"
    rowClassName
    1
    2
    3
    4
    5
    stripe
    1
    2
    3
    4
    5
    " +"
    rowClassName
    1
    2
    3
    4
    5
    stripe
    1
    2
    3
    4
    5
    " ``` #### `Table rowExpandable` ``` -"
    点击整行展开内容
    Javey
    Jiawei
    点击+,展开内容
    Javey
    Jiawei
    " +"
    点击整行展开内容
    Javey
    Jiawei
    点击+,展开内容
    Javey
    Jiawei
    " ``` #### `Table scheme` ``` -"
    定义该列单元格内容
    key形式
    key为一个路径字符串
    没有这个key,则返回空
    虚拟DOM获取到了item.bitem.c.c1
    " +"
    定义该列单元格内容
    key形式
    key为一个路径字符串
    没有这个key,则返回空
    虚拟DOM获取到了item.bitem.c.c1
    " ``` #### `Table scrollToRow` ``` -"
    Name
    IP
    Operation
    name 1127.0.0.1Remove
    name 2127.0.0.2Remove
    name 3127.0.0.3Remove
    name 4127.0.0.4Remove
    name 5127.0.0.5Remove
    name 6127.0.0.6Remove
    name 7127.0.0.7Remove
    name 8127.0.0.8Remove
    name 9127.0.0.9Remove
    name 10127.0.0.10Remove
    name 11127.0.0.11Remove
    name 12127.0.0.12Remove
    name 13127.0.0.13Remove
    name 14127.0.0.14Remove
    name 15127.0.0.15Remove
    name 16127.0.0.16Remove
    name 17127.0.0.17Remove
    name 18127.0.0.18Remove
    name 19127.0.0.19Remove
    name 20127.0.0.20Remove
    name 21127.0.0.21Remove
    name 22127.0.0.22Remove
    name 23127.0.0.23Remove
    name 24127.0.0.24Remove
    name 25127.0.0.25Remove
    name 26127.0.0.26Remove
    name 27127.0.0.27Remove
    name 28127.0.0.28Remove
    name 29127.0.0.29Remove
    name 30127.0.0.30Remove
    name 31127.0.0.31Remove
    name 32127.0.0.32Remove
    name 33127.0.0.33Remove
    name 34127.0.0.34Remove
    name 35127.0.0.35Remove
    name 36127.0.0.36Remove
    name 37127.0.0.37Remove
    name 38127.0.0.38Remove
    name 39127.0.0.39Remove
    name 40127.0.0.40Remove
    name 41127.0.0.41Remove
    name 42127.0.0.42Remove
    name 43127.0.0.43Remove
    name 44127.0.0.44Remove
    name 45127.0.0.45Remove
    name 46127.0.0.46Remove
    name 47127.0.0.47Remove
    name 48127.0.0.48Remove
    name 49127.0.0.49Remove
    name 50127.0.0.50Remove
    name 51127.0.0.51Remove
    name 52127.0.0.52Remove
    name 53127.0.0.53Remove
    name 54127.0.0.54Remove
    name 55127.0.0.55Remove
    name 56127.0.0.56Remove
    name 57127.0.0.57Remove
    name 58127.0.0.58Remove
    name 59127.0.0.59Remove
    name 60127.0.0.60Remove
    name 61127.0.0.61Remove
    name 62127.0.0.62Remove
    name 63127.0.0.63Remove
    name 64127.0.0.64Remove
    name 65127.0.0.65Remove
    name 66127.0.0.66Remove
    name 67127.0.0.67Remove
    name 68127.0.0.68Remove
    name 69127.0.0.69Remove
    name 70127.0.0.70Remove
    name 71127.0.0.71Remove
    name 72127.0.0.72Remove
    name 73127.0.0.73Remove
    name 74127.0.0.74Remove
    name 75127.0.0.75Remove
    name 76127.0.0.76Remove
    name 77127.0.0.77Remove
    name 78127.0.0.78Remove
    name 79127.0.0.79Remove
    name 80127.0.0.80Remove
    name 81127.0.0.81Remove
    name 82127.0.0.82Remove
    name 83127.0.0.83Remove
    name 84127.0.0.84Remove
    name 85127.0.0.85Remove
    name 86127.0.0.86Remove
    name 87127.0.0.87Remove
    name 88127.0.0.88Remove
    name 89127.0.0.89Remove
    name 90127.0.0.90Remove
    name 91127.0.0.91Remove
    name 92127.0.0.92Remove
    name 93127.0.0.93Remove
    name 94127.0.0.94Remove
    name 95127.0.0.95Remove
    name 96127.0.0.96Remove
    name 97127.0.0.97Remove
    name 98127.0.0.98Remove
    name 99127.0.0.99Remove
    name 100127.0.0.100Remove
    " +"
    Name
    IP
    Operation
    name 1127.0.0.1Remove
    name 2127.0.0.2Remove
    name 3127.0.0.3Remove
    name 4127.0.0.4Remove
    name 5127.0.0.5Remove
    name 6127.0.0.6Remove
    name 7127.0.0.7Remove
    name 8127.0.0.8Remove
    name 9127.0.0.9Remove
    name 10127.0.0.10Remove
    name 11127.0.0.11Remove
    name 12127.0.0.12Remove
    name 13127.0.0.13Remove
    name 14127.0.0.14Remove
    name 15127.0.0.15Remove
    name 16127.0.0.16Remove
    name 17127.0.0.17Remove
    name 18127.0.0.18Remove
    name 19127.0.0.19Remove
    name 20127.0.0.20Remove
    name 21127.0.0.21Remove
    name 22127.0.0.22Remove
    name 23127.0.0.23Remove
    name 24127.0.0.24Remove
    name 25127.0.0.25Remove
    name 26127.0.0.26Remove
    name 27127.0.0.27Remove
    name 28127.0.0.28Remove
    name 29127.0.0.29Remove
    name 30127.0.0.30Remove
    name 31127.0.0.31Remove
    name 32127.0.0.32Remove
    name 33127.0.0.33Remove
    name 34127.0.0.34Remove
    name 35127.0.0.35Remove
    name 36127.0.0.36Remove
    name 37127.0.0.37Remove
    name 38127.0.0.38Remove
    name 39127.0.0.39Remove
    name 40127.0.0.40Remove
    name 41127.0.0.41Remove
    name 42127.0.0.42Remove
    name 43127.0.0.43Remove
    name 44127.0.0.44Remove
    name 45127.0.0.45Remove
    name 46127.0.0.46Remove
    name 47127.0.0.47Remove
    name 48127.0.0.48Remove
    name 49127.0.0.49Remove
    name 50127.0.0.50Remove
    name 51127.0.0.51Remove
    name 52127.0.0.52Remove
    name 53127.0.0.53Remove
    name 54127.0.0.54Remove
    name 55127.0.0.55Remove
    name 56127.0.0.56Remove
    name 57127.0.0.57Remove
    name 58127.0.0.58Remove
    name 59127.0.0.59Remove
    name 60127.0.0.60Remove
    name 61127.0.0.61Remove
    name 62127.0.0.62Remove
    name 63127.0.0.63Remove
    name 64127.0.0.64Remove
    name 65127.0.0.65Remove
    name 66127.0.0.66Remove
    name 67127.0.0.67Remove
    name 68127.0.0.68Remove
    name 69127.0.0.69Remove
    name 70127.0.0.70Remove
    name 71127.0.0.71Remove
    name 72127.0.0.72Remove
    name 73127.0.0.73Remove
    name 74127.0.0.74Remove
    name 75127.0.0.75Remove
    name 76127.0.0.76Remove
    name 77127.0.0.77Remove
    name 78127.0.0.78Remove
    name 79127.0.0.79Remove
    name 80127.0.0.80Remove
    name 81127.0.0.81Remove
    name 82127.0.0.82Remove
    name 83127.0.0.83Remove
    name 84127.0.0.84Remove
    name 85127.0.0.85Remove
    name 86127.0.0.86Remove
    name 87127.0.0.87Remove
    name 88127.0.0.88Remove
    name 89127.0.0.89Remove
    name 90127.0.0.90Remove
    name 91127.0.0.91Remove
    name 92127.0.0.92Remove
    name 93127.0.0.93Remove
    name 94127.0.0.94Remove
    name 95127.0.0.95Remove
    name 96127.0.0.96Remove
    name 97127.0.0.97Remove
    name 98127.0.0.98Remove
    name 99127.0.0.99Remove
    name 100127.0.0.100Remove
    " ``` #### `Table selectedKeys` ``` -"
    表头1
    表头2
    第一行哈哈
    第二行哈哈
    表头1
    表头2
    第一行哈哈
    第二行哈哈
    " +"
    表头1
    表头2
    第一行哈哈
    第二行哈哈
    表头1
    表头2
    第一行哈哈
    第二行哈哈
    " ``` #### `Table showIndeterminate` ``` -"
    表头1
    表头2
    第一行哈哈2
    第二行哈哈2
    " +"
    表头1
    表头2
    第一行哈哈2
    第二行哈哈2
    " ``` #### `Table sort` ``` -"
    姓名
    年龄
    aa1
    cc5
    bb9
    " +"
    姓名
    年龄
    aa1
    cc5
    bb9
    " ``` #### `Table stickHeader` ``` -"
    姓名
    性别
    Javeymale
    Kanilyfemale
    " +"
    姓名
    性别
    Javeymale
    Kanilyfemale
    " ``` #### `Table stickScrollbar` ``` -"
    Name
    Column1
    Column2
    Column3
    Column4
    Action
    Johntesttesttesttestaction
    Tomtesttesttesttestaction
    Javeytesttesttesttestaction
    " +"
    Name
    Column1
    Column2
    Column3
    Column4
    Action
    Johntesttesttesttestaction
    Tomtesttesttesttestaction
    Javeytesttesttesttestaction
    " ``` #### `Table title` ``` -"
    自定义表头内容
    表头2
    第一行哈哈2
    第二行哈哈2
    " +"
    自定义表头内容
    表头2
    第一行哈哈2
    第二行哈哈2
    " ``` #### `Table tooltip` ``` -"
    表头1
    表头2
    第一行哈哈1
    第二行哈哈2
    第三行哈哈3
    " +"
    表头1
    表头2
    第一行哈哈1
    第二行哈哈2
    第三行哈哈3
    " ``` #### `Table tree` ``` -"
    Name
    Size
    Audios12MB
    Images14MB
    doc.pdf18MB

    自定义展开Icon位置

    Name
    Size
    Audios12MB
    Images14MB
    doc.pdf18MB
    " +"
    Name
    Size
    Audios12MB
    Images14MB
    doc.pdf18MB

    自定义展开Icon位置

    Name
    Size
    Audios12MB
    Images14MB
    doc.pdf18MB
    " ``` #### `Table type` ``` -"
    Type
    Value
    typeborder
    Type
    Value
    typegrid
    " +"
    Type
    Value
    typeborder
    Type
    Value
    typegrid
    " +``` + +#### `Table virtual` + +``` +"

    表格

    Title 1
    Title 2
    暂无数据

    树形表格

    Title 1
    Title 2
    暂无数据
    " ``` ## `tabs` @@ -2158,6 +2170,26 @@ "
    " ``` +## `virtualList` + +#### `VirtualList basic` + +``` +"

    1. 定高元素

    2. 不定高元素

    " +``` + +#### `VirtualList combined` + +``` +"
      " +``` + +#### `VirtualList delete` + +``` +"
      " +``` + ## `wave` #### `Wave button` diff --git a/test/__snapshots__/Cascader.md b/test/__snapshots__/Cascader.md index 7eec7b12d..2efdc495a 100644 --- a/test/__snapshots__/Cascader.md +++ b/test/__snapshots__/Cascader.md @@ -47,37 +47,37 @@ "
      无数据
      " ``` -#### `filter` +#### `no data for init data` ``` -"
      北京
      湖南
      " +"
      无数据
      " ``` -``` -"
      湖南 / 长沙市 / 岳麓区
      " -``` +#### `specify fields` ``` -"
      无匹配数据
      " +"
      北京
      湖南
      " ``` ``` -"
      湖南 / 岳阳市 / 岳阳楼区
      湖南 / 岳阳市 / 岳阳
      " +"
      请选择
      北京 / 海淀区
      " ``` -#### `no data for init data` +#### `filter` ``` -"
      无数据
      " +"
      北京
      湖南
      " ``` -#### `specify fields` +``` +"
      湖南 / 长沙市 / 岳麓区
      " +``` ``` -"
      北京
      湖南
      " +"
      无匹配数据
      " ``` ``` -"
      请选择
      北京 / 海淀区
      " +"
      湖南 / 岳阳市 / 岳阳楼区
      湖南 / 岳阳市 / 岳阳
      " ``` diff --git a/test/__snapshots__/Colorpicker.md b/test/__snapshots__/Colorpicker.md index 8648087b8..9503a38a1 100644 --- a/test/__snapshots__/Colorpicker.md +++ b/test/__snapshots__/Colorpicker.md @@ -3,17 +3,17 @@ #### `should select color by saturation` ``` -"
      36.26373626373627
      1
      Hex
      R -
      G -
      B -
      A
      " +"
      36.26373626373627
      1
      Hex
      R +
      G +
      B +
      A
      " ``` ``` -"
      36.26373626373627
      1
      Hex
      R -
      G -
      B -
      A
      " +"
      36.26373626373627
      1
      Hex
      R +
      G +
      B +
      A
      " ``` ``` @@ -27,18 +27,18 @@ #### `should change color by input` ``` -"
      0
      1
      Hex
      R -
      G -
      B -
      A
      " +"
      0
      1
      Hex
      R +
      G +
      B +
      A
      " ``` #### `should change to hsv mode` ``` -"
      36.26373626373627
      1
      Hex
      H -
      S - %
      L - %
      A
      " +"
      36.26373626373627
      1
      Hex
      H +
      S + %
      L + %
      A
      " ``` diff --git a/test/__snapshots__/Demos.md b/test/__snapshots__/Demos.md index 310ec71a2..ceacd8977 100644 --- a/test/__snapshots__/Demos.md +++ b/test/__snapshots__/Demos.md @@ -186,7 +186,7 @@ #### `Cascader filterable` ``` -"


      请输入或选择
      " +"


      请输入或选择
      " ``` #### `Cascader loadData` @@ -357,76 +357,76 @@ #### `Datepicker basic` ``` -"
      " +"
      " ``` #### `Datepicker clearable` ``` -"
      " +"
      " ``` #### `Datepicker datetime` ``` -"
      " +"
      " ``` #### `Datepicker disabledDate` ``` -"




      " +"




      " ``` #### `Datepicker format` ``` -"
      - value:
      - value:
      +"
      + value:
      + value:
      value:
      " ``` #### `Datepicker maxMin` ``` -"
      ~ -


      ~ -
      " +"
      ~ +


      ~ +
      " ``` #### `Datepicker multiple` ``` -"
      请选择日期
      - You selected: []

      请选择日期和时间
      - You selected: []

      请选择年份
      - You selected: []

      请选择月份
      - You selected: []

      开始日期 ~ 结束日期
      - You selected: []

      开始时间 ~ 结束时间
      +"
      请选择日期
      + You selected: []

      请选择日期和时间
      + You selected: []

      请选择年份
      + You selected: []

      请选择月份
      + You selected: []

      开始日期 ~ 结束日期
      + You selected: []

      开始时间 ~ 结束时间
      You selected: []

      " ``` #### `Datepicker range` ``` -"
      - You selected: null

      - You selected: null

      - You selected: null

      +"
      + You selected: null

      + You selected: null

      + You selected: null

      You selected: null

      " ``` #### `Datepicker shortcuts` ``` -"
      " +"
      " ``` #### `Datepicker yearMonth` ``` -"
      " +"
      " ``` ## `descriptions` @@ -472,13 +472,13 @@ #### `Diagram layout` ``` -"
      " +"
      " ``` #### `Diagram line` ``` -"
      x: 10
      01000

      y: 100
      01000

      x: 100
      01000

      y: 150
      01000

      x: 0
      01

      y: 0
      01

      x: 0
      01

      y: 0
      01

      A
      B
      C
      " +"
      x: 10
      01000

      y: 100
      01000

      x: 100
      01000

      y: 150
      01000

      x: 0
      01

      y: 0
      01

      x: 0
      01

      y: 0
      01

      A
      B
      C
      " ``` #### `Diagram select` @@ -669,7 +669,7 @@ ``` "
      " + click
      " ``` ## `editable` @@ -723,37 +723,37 @@ #### `Form basic` ``` -"
      请选择
      0
      0100
      " +"
      请选择
      0
      0100
      " ``` #### `Form custom` ``` -"
      " +"
      " ``` #### `Form label` ``` -"
      " +"
      " ``` #### `Form layout` ``` -"
      请选择
      " +"
      请选择
      " ``` #### `Form remote` ``` -"
      " +"
      " ``` #### `Form variable` ``` -"
      " +"
      " ``` ## `grid` @@ -829,7 +829,7 @@ #### `Icon icons` ``` -"
      k-icon-information-fill
      k-icon-warning-fill
      k-icon-success-fill
      k-icon-error-fill
      k-icon-question-fill
      k-icon-heart-fill
      k-icon-heart
      k-icon-notification-fill
      k-icon-notification
      k-icon-information
      k-icon-alert
      k-icon-question
      k-icon-zoom-in
      k-icon-zoom-out
      k-icon-close-outline
      k-icon-check-outline
      k-icon-time
      k-icon-right-circled
      k-icon-down-circled
      k-icon-right-circled
      k-icon-up-circled
      k-icon-right-squared
      k-icon-down-squared
      k-icon-left-squared
      k-icon-up-squared
      k-icon-right
      k-icon-down
      k-icon-left
      k-icon-up
      k-icon-right-bold
      k-icon-down-bold
      k-icon-left-bold
      k-icon-up-bold
      k-icon-sort
      k-icon-sort-bold
      k-icon-close
      k-icon-close-bold
      k-icon-check
      k-icon-check-bold
      k-icon-add
      k-icon-add-bold
      k-icon-minus
      k-icon-minus-bold
      k-icon-share
      k-icon-tag
      k-icon-clone
      k-icon-cloud
      k-icon-pin
      k-icon-home
      k-icon-cut
      k-icon-server
      k-icon-internet
      k-icon-mail
      k-icon-paper
      k-icon-phone
      k-icon-panel
      k-icon-alarm
      k-icon-earphone
      k-icon-settings-horizontal
      k-icon-settings-vertical
      k-icon-settings
      k-icon-message
      k-icon-return-right
      k-icon-picture
      k-icon-logout
      k-icon-all
      k-icon-drag
      k-icon-more
      k-icon-more-circled
      k-icon-folder
      k-icon-folder-open
      k-icon-lock
      k-icon-unlock
      k-icon-user
      k-icon-users
      k-icon-edit
      k-icon-location
      k-icon-delete
      k-icon-calendar
      k-icon-search
      k-icon-batchsearch
      k-icon-hidden
      k-icon-visible
      k-icon-refresh
      k-icon-upload
      k-icon-download
      k-icon-start
      k-icon-stop
      k-icon-sortorder
      " +"
      k-icon-information-fill
      k-icon-warning-fill
      k-icon-success-fill
      k-icon-error-fill
      k-icon-question-fill
      k-icon-heart-fill
      k-icon-heart
      k-icon-notification-fill
      k-icon-notification
      k-icon-information
      k-icon-alert
      k-icon-question
      k-icon-zoom-in
      k-icon-zoom-out
      k-icon-close-outline
      k-icon-check-outline
      k-icon-time
      k-icon-right-circled
      k-icon-down-circled
      k-icon-right-circled
      k-icon-up-circled
      k-icon-right-squared
      k-icon-down-squared
      k-icon-left-squared
      k-icon-up-squared
      k-icon-right
      k-icon-down
      k-icon-left
      k-icon-up
      k-icon-right-bold
      k-icon-down-bold
      k-icon-left-bold
      k-icon-up-bold
      k-icon-sort
      k-icon-sort-bold
      k-icon-close
      k-icon-close-bold
      k-icon-check
      k-icon-check-bold
      k-icon-add
      k-icon-add-bold
      k-icon-minus
      k-icon-minus-bold
      k-icon-share
      k-icon-tag
      k-icon-clone
      k-icon-cloud
      k-icon-pin
      k-icon-home
      k-icon-cut
      k-icon-server
      k-icon-internet
      k-icon-mail
      k-icon-paper
      k-icon-phone
      k-icon-panel
      k-icon-alarm
      k-icon-earphone
      k-icon-settings-horizontal
      k-icon-settings-vertical
      k-icon-settings
      k-icon-message
      k-icon-return-right
      k-icon-picture
      k-icon-logout
      k-icon-all
      k-icon-drag
      k-icon-more
      k-icon-more-circled
      k-icon-folder
      k-icon-folder-open
      k-icon-lock
      k-icon-unlock
      k-icon-user
      k-icon-users
      k-icon-edit
      k-icon-location
      k-icon-delete
      k-icon-calendar
      k-icon-search
      k-icon-batchsearch
      k-icon-hidden
      k-icon-visible
      k-icon-refresh
      k-icon-upload
      k-icon-download
      k-icon-start
      k-icon-stop
      k-icon-sortorder
      " ``` ## `input` @@ -837,79 +837,79 @@ #### `Input autoRows` ``` -"


      " +"


      " ``` #### `Input autowidth` ``` -"
      auto width


      default value
      " +"
      auto width


      default value
      " ``` #### `Input basic` ``` -"

      " +"

      " ``` #### `Input blocks` ``` -"
      http://
      .com







      http://
      .com
      " +"
      http://
      .com







      http://
      .com
      " ``` #### `Input clearable` ``` -"



      " +"



      " ``` #### `Input flat` ``` -"


      " +"


      " ``` #### `Input frozen` ``` -"

      " +"

      " ``` #### `Input inline` ``` -"
      " +"
      " ``` #### `Input password` ``` -"
      " +"
      " ``` #### `Input search` ``` -"

      " +"

      " ``` #### `Input showCount` ``` -"
      0


      0 / 10


      0 / 100
      " +"
      0


      0 / 10


      0 / 100
      " ``` #### `Input size` ``` -"



      http://
      .com







      " +"



      http://
      .com







      " ``` #### `Input textarea` ``` -"


      " +"


      " ``` ## `layout` @@ -917,7 +917,7 @@ #### `Layout aside` ``` -"
      LOGO
      menu 1 +"
      LOGO
      menu 1
      sub menu 1
      sub menu 2
      sub menu 3
      sub menu 4
      menu 2
      menu 3
      sub menu 1
      sub menu 2
      sub menu 3
      sub menu 4
      menu 4
      Home
      Detail
      content
      " ``` @@ -925,7 +925,7 @@ #### `Layout asideFix` ``` -"
      LOGO
      menu 1 +"
      LOGO
      menu 1
      sub menu 1
      sub menu 2
      sub menu 3
      sub menu 4
      menu 2
      menu 3
      sub menu 1
      sub menu 2
      sub menu 3
      sub menu 4
      menu 4
      Home
      Detail
      content @@ -1135,7 +1135,7 @@ ``` "
      LOGO
      menu 1
      menu 2
      menu 3 -
      menu 4
      menu 1 +
      menu 4
      menu 1
      sub menu 1
      sub menu 2
      sub menu 3
      sub menu 4
      menu 2
      menu 3
      sub menu 1
      sub menu 2
      sub menu 3
      sub menu 4
      menu 4
      Home
      Detail
      content
      content
      content
      content
      content
      content
      content
      content
      content
      content
      content
      content
      content
      content
      content
      content
      content
      " ``` @@ -1145,7 +1145,7 @@ ``` "
      LOGO
      menu 1
      menu 2
      menu 3 -
      menu 4
      menu 1 +
      menu 4
      menu 1
      sub menu 1
      sub menu 2
      sub menu 3
      sub menu 4
      menu 2
      menu 3
      sub menu 1
      sub menu 2
      sub menu 3
      sub menu 4
      menu 4
      Home
      Detail
      content @@ -1355,7 +1355,7 @@ ``` "
      LOGO
      menu 1
      menu 2
      menu 3 -
      menu 4
      menu 1 +
      menu 4
      menu 1
      sub menu 1
      sub menu 2
      sub menu 3
      sub menu 4
      menu 2
      menu 3
      sub menu 1
      sub menu 2
      sub menu 3
      sub menu 4
      menu 4
      Home
      Detail
      切换主题:
      " @@ -1495,25 +1495,25 @@ #### `Pagination disable` ``` -"
      共 200 条
      10 条 / 页
      共 200 条
      10 条 / 页
      前往
      " +"
      共 200 条
      10 条 / 页
      共 200 条
      10 条 / 页
      前往
      " ``` #### `Pagination flat` ``` -"
      共 200 条
      10 条 / 页
      前往
      共 200 条
      10 条 / 页
      前往
      共 200 条
      10 条 / 页
      前往
      共 200 条
      10 条 / 页
      前往
      " +"
      共 200 条
      10 条 / 页
      前往
      共 200 条
      10 条 / 页
      前往
      共 200 条
      10 条 / 页
      前往
      共 200 条
      10 条 / 页
      前往
      " ``` #### `Pagination goto` ``` -"
      共 200 条
      10 条 / 页
      前往
      " +"
      共 200 条
      10 条 / 页
      前往
      " ``` #### `Pagination simple` ``` -"
      1
      / 20
      1
      / 20
      1
      / 20
      1
      / 20
      " +"
      1
      / 20
      1
      / 20
      1
      / 20
      1
      / 20
      " ``` #### `Pagination size` @@ -1745,16 +1745,16 @@ #### `Select creatable` ``` -"
      +"
      Day:

      - +0
      请输入或选择
      + +0
      请输入或选择
      Days: []
      " ``` #### `Select custom` ``` -"
      " +"
      " ``` #### `Select customMenu` @@ -1766,7 +1766,7 @@ #### `Select disabled` ``` -"
      请选择
      星期一
      星期二
      " +"
      请选择
      星期一
      星期二
      " ``` #### `Select draggable` @@ -1779,7 +1779,7 @@ #### `Select filterable` ``` -"
      请选择
      " +"
      请选择
      " ``` #### `Select flat` @@ -1799,13 +1799,13 @@ #### `Select group` ``` -"
      " +"
      " ``` #### `Select immutable` ``` -"
      星期二
      星期天
      星期三
      +"
      星期二
      星期天
      星期三
      You selected: [\"Tuesday\",\"Sunday\",\"Wednesday\"]
      " ``` @@ -1826,13 +1826,13 @@ ``` "
      星期一
      星期二
      - +2
      " + +2
      " ``` #### `Select remote` ``` -"
      请选择
      " +"
      请选择
      " ``` #### `Select searchable` @@ -1847,6 +1847,12 @@ "
      请选择
      请选择
      请选择
      请选择


      请选择
      请选择
      请选择
      请选择
      " ``` +#### `Select virtual` + +``` +"
      请选择
      " +``` + #### `Select noBorder` ``` @@ -1896,19 +1902,19 @@ #### `Slider basic` ``` -"
      60
      0MB100MB
      277
      50500
      " +"
      60
      0MB100MB
      277
      50500
      " ``` #### `Slider disabled` ``` -"
      50
      0100
      55
      80
      50100
      -
      " +"
      50
      0100
      55
      80
      50100
      -
      " ``` #### `Slider dynamicStep` ``` -"
      0
      0500
      " +"
      0
      0500
      " ``` #### `Slider marks` @@ -1920,25 +1926,25 @@ #### `Slider points` ``` -"
      45
      0100
      0.3
      0.6
      01
      -
      45
      0100
      " +"
      45
      0100
      0.3
      0.6
      01
      -
      45
      0100
      " ``` #### `Slider range` ``` -"
      50
      76
      0100
      -
      " +"
      50
      76
      0100
      -
      " ``` #### `Slider step` ``` -"
      0
      50
      50500
      0.234234
      01
      " +"
      0
      50
      50500
      0.234234
      01
      " ``` #### `Slider tooltip` ``` -"
      277
      50500
      50
      76
      0100
      -
      123456月7891年2年3年
      300
      50500
      " +"
      277
      50500
      50
      76
      0100
      -
      123456月7891年2年3年
      300
      50500
      " ``` ## `spin` @@ -1952,7 +1958,7 @@ #### `Spin overlay` ``` -"
      表头1
      表头2
      第一行哈哈2
      第二行哈哈2
      第三行哈哈3
      " +"
      表头1
      表头2
      第一行哈哈2
      第二行哈哈2
      第三行哈哈3
      " ``` ## `spinner` @@ -1960,49 +1966,49 @@ #### `Spinner basic` ``` -"
      " +"
      " ``` #### `Spinner dynamicStep` ``` -"
      " +"
      " ``` #### `Spinner forceStep` ``` -"
      " +"
      " ``` #### `Spinner formatter` ``` -"
      " +"
      " ``` #### `Spinner precision` ``` -"
      " +"
      " ``` #### `Spinner size` ``` -"
      " +"
      " ``` #### `Spinner step` ``` -"
      " +"
      " ``` #### `Spinner vertical` ``` -"
      " +"
      " ``` ## `split` @@ -2016,7 +2022,7 @@ #### `Split complex` ``` -"
      名称
      网段
      操作
      name 0127.0.0.0删除
      name 1127.0.0.1删除
      name 2127.0.0.2删除
      name 3127.0.0.3删除
      name 4127.0.0.4删除
      name 5127.0.0.5删除
      name 6127.0.0.6删除
      name 7127.0.0.7删除
      name 8127.0.0.8删除
      name 9127.0.0.9删除
      name 10127.0.0.10删除
      详情
      流量统计
      标签
      " +"
      名称
      网段
      操作
      name 0127.0.0.0删除
      name 1127.0.0.1删除
      name 2127.0.0.2删除
      name 3127.0.0.3删除
      name 4127.0.0.4删除
      name 5127.0.0.5删除
      name 6127.0.0.6删除
      name 7127.0.0.7删除
      name 8127.0.0.8删除
      name 9127.0.0.9删除
      name 10127.0.0.10删除
      详情
      流量统计
      标签
      " ``` #### `Split nested` @@ -2130,13 +2136,13 @@ ``` "
      Title 1
      Title 3
      Cell 1-1Cell 1-3
      Cell 2-1Cell 2-3
      Title 1
      Title 3
      Cell 1-1Cell 1-3
      Cell 2-1Cell 2-3
      " +
      Title 1
      Title 3
      Cell 1-1Cell 1-3
      Cell 2-1Cell 2-3
      Title 1
      Title 3
      Cell 1-1Cell 1-3
      Cell 2-1Cell 2-3
      " ``` #### `Table asyncTree` ``` -"
      Name
      Size
      Audios12MB +"
      Name
      Size
      Audios12MB
      Images14MB
      doc.pdf18MB
      " @@ -2145,202 +2151,202 @@ #### `Table basic` ``` -"
      Title 1
      Title 2
      Cell 1-1Cell 1-2
      Cell 2-1Cell 2-2
      " +"
      Title 1
      Title 2
      Cell 1-1Cell 1-2
      Cell 2-1Cell 2-2
      " ``` #### `Table checkType` ``` -"
      Title
      checkbox 1
      checkbox 2
      Title
      radio 1
      radio 2
      Title
      no check 1
      no check 2
      " +"
      Title
      checkbox 1
      checkbox 2
      Title
      radio 1
      radio 2
      Title
      no check 1
      no check 2
      " ``` #### `Table checkedKeys` ``` -"
      Radio without rowKey
      default checked by index
      unchecked
      Radio with rowKey
      default checked by rowKey
      unchecked
      checkbox without rowKey
      default checked by index
      unchecked
      default checked
      checkbox with rowKey
      default checked by rowKey
      unchecked
      " +"
      Radio without rowKey
      default checked by index
      unchecked
      Radio with rowKey
      default checked by rowKey
      unchecked
      checkbox without rowKey
      default checked by index
      unchecked
      default checked
      checkbox with rowKey
      default checked by rowKey
      unchecked
      " ``` #### `Table disableRow` ``` -"
      odd rows are uncheckable
      1
      2
      3
      4
      5
      " +"
      odd rows are uncheckable
      1
      2
      3
      4
      5
      " ``` #### `Table draggable` ``` -"
      Name
      IP
      Operation
      name 1127.0.0.1Remove
      name 2127.0.0.2Remove
      name 3127.0.0.3Remove
      name 4127.0.0.4Remove
      name 5127.0.0.5Remove
      name 6127.0.0.6Remove
      name 7127.0.0.7Remove
      name 8127.0.0.8Remove
      name 9127.0.0.9Remove
      name 10127.0.0.10Remove
      name 11127.0.0.11Remove
      name 12127.0.0.12Remove
      name 13127.0.0.13Remove
      name 14127.0.0.14Remove
      name 15127.0.0.15Remove
      name 16127.0.0.16Remove
      name 17127.0.0.17Remove
      name 18127.0.0.18Remove
      name 19127.0.0.19Remove
      name 20127.0.0.20Remove
      " +"
      Name
      IP
      Operation
      name 1127.0.0.1Remove
      name 2127.0.0.2Remove
      name 3127.0.0.3Remove
      name 4127.0.0.4Remove
      name 5127.0.0.5Remove
      name 6127.0.0.6Remove
      name 7127.0.0.7Remove
      name 8127.0.0.8Remove
      name 9127.0.0.9Remove
      name 10127.0.0.10Remove
      name 11127.0.0.11Remove
      name 12127.0.0.12Remove
      name 13127.0.0.13Remove
      name 14127.0.0.14Remove
      name 15127.0.0.15Remove
      name 16127.0.0.16Remove
      name 17127.0.0.17Remove
      name 18127.0.0.18Remove
      name 19127.0.0.19Remove
      name 20127.0.0.20Remove
      " ``` #### `Table ellipsis` ``` -"
      Title 1
      Title 2
      测试超长测试超长测试超长测试超长测试超长测试超长测试超长测试超长测试超长测试超长测试超长测试超长测试超长测试超长测试超长测试超长测试超长
      Cell 1-2
      Cell 2-1
      Cell 2-2
      " +"
      Title 1
      Title 2
      测试超长测试超长测试超长测试超长测试超长测试超长测试超长测试超长测试超长测试超长测试超长测试超长测试超长测试超长测试超长测试超长测试超长
      Cell 1-2
      Cell 2-1
      Cell 2-2
      " ``` #### `Table empty` ``` -"
      表头1
      表头2
      暂无数据
      表头1
      表头2
      没有数据
      " +"
      表头1
      表头2
      暂无数据
      表头1
      表头2
      没有数据
      " ``` #### `Table export` ``` -"
      定义该列单元格内容
      普通文本
      自定义导出文本
      操作
      第一行哈哈1自定义1删除
      第二行哈哈2自定义2删除
      " +"
      定义该列单元格内容
      普通文本
      自定义导出文本
      操作
      第一行哈哈1自定义1删除
      第二行哈哈2自定义2删除
      " ``` #### `Table fixColumn` ``` -"
      Name
      Column1
      Column2
      Column3
      Column4
      Action
      Johntesttesttesttestaction
      Tomtesttesttesttestaction
      Javeytesttesttesttestaction
      Name
      Column1
      Column2
      Column3
      Column4
      Action
      Johntesttesttesttestaction
      Tomtesttesttesttestaction
      Javeytesttesttesttestaction
      " +"
      Name
      Column1
      Column2
      Column3
      Column4
      Action
      Johntesttesttesttestaction
      Tomtesttesttesttestaction
      Javeytesttesttesttestaction
      Name
      Column1
      Column2
      Column3
      Column4
      Action
      Johntesttesttesttestaction
      Tomtesttesttesttestaction
      Javeytesttesttesttestaction
      " ``` #### `Table fixFooter` ``` -"
      footer不固定
      footer不固定
      下拉
      yeah!
      你好
      我很好
      自定义footer
      footer固定
      footer固定啦
      下拉
      yeah!
      你好
      我很好
      自定义footer
      footer,header同时固定
      footer固定啦
      下拉
      yeah!
      你好
      我很好
      自定义footer
      " +"
      footer不固定
      footer不固定
      下拉
      yeah!
      你好
      我很好
      自定义footer
      footer固定
      footer固定啦
      下拉
      yeah!
      你好
      我很好
      自定义footer
      footer,header同时固定
      footer固定啦
      下拉
      yeah!
      你好
      我很好
      自定义footer
      " ``` #### `Table fixHeader` ``` -"
      100px
      表头固定,但内容没有超出最高高度
      100px
      表头固定啦
      下拉
      yeah!
      " +"
      100px
      表头固定,但内容没有超出最高高度
      Name
      IP
      name 1127.0.0.1
      name 2127.0.0.2
      name 3127.0.0.3
      name 4127.0.0.4
      name 5127.0.0.5
      name 6127.0.0.6
      name 7127.0.0.7
      name 8127.0.0.8
      name 9127.0.0.9
      name 10127.0.0.10
      name 11127.0.0.11
      name 12127.0.0.12
      name 13127.0.0.13
      name 14127.0.0.14
      name 15127.0.0.15
      name 16127.0.0.16
      name 17127.0.0.17
      name 18127.0.0.18
      name 19127.0.0.19
      name 20127.0.0.20
      name 21127.0.0.21
      name 22127.0.0.22
      name 23127.0.0.23
      name 24127.0.0.24
      name 25127.0.0.25
      name 26127.0.0.26
      name 27127.0.0.27
      name 28127.0.0.28
      name 29127.0.0.29
      name 30127.0.0.30
      name 31127.0.0.31
      name 32127.0.0.32
      name 33127.0.0.33
      name 34127.0.0.34
      name 35127.0.0.35
      name 36127.0.0.36
      name 37127.0.0.37
      name 38127.0.0.38
      name 39127.0.0.39
      name 40127.0.0.40
      name 41127.0.0.41
      name 42127.0.0.42
      name 43127.0.0.43
      name 44127.0.0.44
      name 45127.0.0.45
      name 46127.0.0.46
      name 47127.0.0.47
      name 48127.0.0.48
      name 49127.0.0.49
      name 50127.0.0.50
      name 51127.0.0.51
      name 52127.0.0.52
      name 53127.0.0.53
      name 54127.0.0.54
      name 55127.0.0.55
      name 56127.0.0.56
      name 57127.0.0.57
      name 58127.0.0.58
      name 59127.0.0.59
      name 60127.0.0.60
      name 61127.0.0.61
      name 62127.0.0.62
      name 63127.0.0.63
      name 64127.0.0.64
      name 65127.0.0.65
      name 66127.0.0.66
      name 67127.0.0.67
      name 68127.0.0.68
      name 69127.0.0.69
      name 70127.0.0.70
      name 71127.0.0.71
      name 72127.0.0.72
      name 73127.0.0.73
      name 74127.0.0.74
      name 75127.0.0.75
      name 76127.0.0.76
      name 77127.0.0.77
      name 78127.0.0.78
      name 79127.0.0.79
      name 80127.0.0.80
      name 81127.0.0.81
      name 82127.0.0.82
      name 83127.0.0.83
      name 84127.0.0.84
      name 85127.0.0.85
      name 86127.0.0.86
      name 87127.0.0.87
      name 88127.0.0.88
      name 89127.0.0.89
      name 90127.0.0.90
      name 91127.0.0.91
      name 92127.0.0.92
      name 93127.0.0.93
      name 94127.0.0.94
      name 95127.0.0.95
      name 96127.0.0.96
      name 97127.0.0.97
      name 98127.0.0.98
      name 99127.0.0.99
      name 100127.0.0.100
      " ``` #### `Table footer` ``` -"
      Title 1
      Title 2
      Cell 1-1Cell 1-2
      Cell 2-1Cell 2-2
      自定义底部
      " +"
      Title 1
      Title 2
      Cell 1-1Cell 1-2
      Cell 2-1Cell 2-2
      自定义底部
      " ``` #### `Table group` ``` -"
      名称
      状态
      主机1运行中
      主机2已关闭
      主机3运行中
      名称
      状态
      主机1运行中
      主机2已关闭
      主机3运行中
      " +"
      名称
      状态
      主机1运行中
      主机2已关闭
      主机3运行中
      名称
      状态
      主机1运行中
      主机2已关闭
      主机3运行中
      " ``` #### `Table groupHeader` ``` -"
      Weekday
      Forenoon
      Afternoon
      Time
      Classes
      Time
      Class 5
      Class 6
      Class 7
      Class 1
      Class 2
      Class 3
      Class 4
      Monday08:00 ~ 12:00EnglishMathematicsChineseHistory14:00 ~ 17:00GeopraghyEnglishMathematics
      Tuesday08:00 ~ 12:00MathematicsChineseHistoryGeopraghy14:00 ~ 17:00EnglishMathematicsChinese
      Wendesday08:00 ~ 12:00ChineseHistoryGeopraghyEnglish14:00 ~ 17:00MathematicsChineseHistory
      Thursday08:00 ~ 12:00HistoryGeopraghyEnglishMathematics14:00 ~ 17:00ChineseHistoryGeopraghy
      Friday08:00 ~ 12:00GeopraghyEnglishMathematicsChinese14:00 ~ 17:00HistoryGeopraghyEnglish
      " +"
      Weekday
      Forenoon
      Afternoon
      Time
      Classes
      Time
      Class 5
      Class 6
      Class 7
      Class 1
      Class 2
      Class 3
      Class 4
      Monday08:00 ~ 12:00EnglishMathematicsChineseHistory14:00 ~ 17:00GeopraghyEnglishMathematics
      Tuesday08:00 ~ 12:00MathematicsChineseHistoryGeopraghy14:00 ~ 17:00EnglishMathematicsChinese
      Wendesday08:00 ~ 12:00ChineseHistoryGeopraghyEnglish14:00 ~ 17:00MathematicsChineseHistory
      Thursday08:00 ~ 12:00HistoryGeopraghyEnglishMathematics14:00 ~ 17:00ChineseHistoryGeopraghy
      Friday08:00 ~ 12:00GeopraghyEnglishMathematicsChinese14:00 ~ 17:00HistoryGeopraghyEnglish
      " ``` #### `Table hidden` ``` -"
      Title 1
      Title 2
      Title 3
      Cell 1-1Cell 1-2cell 1-3
      Cell 2-1Cell 2-2cell 2-3

      无数据展示

      Title 1
      Title 2
      Title 3
      暂无数据
      " +"
      Title 1
      Title 2
      Title 3
      Cell 1-1Cell 1-2cell 1-3
      Cell 2-1Cell 2-2cell 2-3

      无数据展示

      Title 1
      Title 2
      Title 3
      暂无数据
      " ``` #### `Table hideHeader` ``` -"

      hideHeader:

      Cell 1-1Cell 1-2
      Cell 2-1Cell 2-2
      " +"

      hideHeader:

      Cell 1-1Cell 1-2
      Cell 2-1Cell 2-2
      " ``` #### `Table loading` ``` -"
      表头1
      表头2
      第一行哈哈1
      第二行哈哈2
      第三行哈哈3
      " +"
      表头1
      表头2
      第一行哈哈1
      第二行哈哈2
      第三行哈哈3
      " ``` #### `Table mergeCell` ``` -"
      Weekday
      Forenoon
      Afternoon
      Time
      Class 1
      Class 2
      Class 3
      Class 4
      Class 5
      Class 6
      Class 7
      Time
      Monday08:00 ~ 12:00EnglishMathematicsHistoryEnglishHistoryEnglishHistory14:00 ~ 17:00
      TuesdayEnglishGeopraghyHistoryEnglishHistoryMathematics
      WendesdayHistoryChineseHistoryEnglishChineseHistory
      ThursdayHistoryEnglishGeopraghyMathematicsGeopraghyEnglishMathematics
      FridayGeopraghyEnglishGeopraghyEnglish
      SaturdayGeopraghyEnglishGeopraghyEnglish
      Weekday
      Forenoon
      Afternoon
      Time
      Class 1
      Class 2
      Class 3
      Class 4
      Class 5
      Class 6
      Class 7
      Time
      Monday08:00 ~ 12:00EnglishMathematicsHistoryEnglishHistoryEnglishHistory14:00 ~ 17:00
      TuesdayEnglishGeopraghyHistoryEnglishHistoryMathematics
      WendesdayHistoryChineseHistoryEnglishChineseHistory
      ThursdayHistoryEnglishGeopraghyMathematicsGeopraghyEnglishMathematics
      FridayGeopraghyEnglishGeopraghyEnglish
      SaturdayGeopraghyEnglishGeopraghyEnglish
      " +"
      Weekday
      Forenoon
      Afternoon
      Time
      Class 1
      Class 2
      Class 3
      Class 4
      Class 5
      Class 6
      Class 7
      Time
      Monday08:00 ~ 12:00EnglishMathematicsHistoryEnglishHistoryEnglishHistory14:00 ~ 17:00
      TuesdayEnglishGeopraghyHistoryEnglishHistoryMathematics
      WendesdayHistoryChineseHistoryEnglishChineseHistory
      ThursdayHistoryEnglishGeopraghyMathematicsGeopraghyEnglishMathematics
      FridayGeopraghyEnglishGeopraghyEnglish
      SaturdayGeopraghyEnglishGeopraghyEnglish
      Weekday
      Forenoon
      Afternoon
      Time
      Class 1
      Class 2
      Class 3
      Class 4
      Class 5
      Class 6
      Class 7
      Time
      Monday08:00 ~ 12:00EnglishMathematicsHistoryEnglishHistoryEnglishHistory14:00 ~ 17:00
      TuesdayEnglishGeopraghyHistoryEnglishHistoryMathematics
      WendesdayHistoryChineseHistoryEnglishChineseHistory
      ThursdayHistoryEnglishGeopraghyMathematicsGeopraghyEnglishMathematics
      FridayGeopraghyEnglishGeopraghyEnglish
      SaturdayGeopraghyEnglishGeopraghyEnglish
      " ``` #### `Table pagination` ``` -"
      Name
      IP
      name 1127.0.0.1
      name 2127.0.0.2
      name 3127.0.0.3
      name 4127.0.0.4
      name 5127.0.0.5
      name 6127.0.0.6
      name 7127.0.0.7
      name 8127.0.0.8
      name 9127.0.0.9
      name 10127.0.0.10
      共 20 条
      10 条 / 页
      " +"
      Name
      IP
      name 1127.0.0.1
      name 2127.0.0.2
      name 3127.0.0.3
      name 4127.0.0.4
      name 5127.0.0.5
      name 6127.0.0.6
      name 7127.0.0.7
      name 8127.0.0.8
      name 9127.0.0.9
      name 10127.0.0.10
      共 20 条
      10 条 / 页
      " ``` #### `Table resizable` ``` -"
      通过minColWidth控制最小宽度100px
      隐藏列
      单独设置最小宽度300px
      标题
      ABC
      ABC
      " +"
      通过minColWidth控制最小宽度100px
      隐藏列
      单独设置最小宽度300px
      标题
      ABC
      ABC
      " ``` #### `Table rowCheckable` ``` -"
      点击整行不选中
      B
      AB
      A点我不会选中整行
      点击整行选中
      B
      A任何位置都可以哦~
      A点我会选中整行
      " +"
      点击整行不选中
      B
      AB
      A点我不会选中整行
      点击整行选中
      B
      A任何位置都可以哦~
      A点我会选中整行
      " ``` #### `Table rowClassName` ``` -"
      rowClassName
      1
      2
      3
      4
      5
      stripe
      1
      2
      3
      4
      5
      " +"
      rowClassName
      1
      2
      3
      4
      5
      stripe
      1
      2
      3
      4
      5
      " ``` #### `Table rowExpandable` ``` -"
      点击整行展开内容
      Javey
      Jiawei
      点击+,展开内容
      Javey
      Jiawei
      " +"
      点击整行展开内容
      Javey
      Jiawei
      点击+,展开内容
      Javey
      Jiawei
      " ``` #### `Table scheme` ``` -"
      定义该列单元格内容
      key形式
      key为一个路径字符串
      没有这个key,则返回空
      虚拟DOM获取到了item.bitem.c.c1
      " +"
      定义该列单元格内容
      key形式
      key为一个路径字符串
      没有这个key,则返回空
      虚拟DOM获取到了item.bitem.c.c1
      " ``` #### `Table scrollToRow` ``` -"
      Name
      IP
      Operation
      name 1127.0.0.1Remove
      name 2127.0.0.2Remove
      name 3127.0.0.3Remove
      name 4127.0.0.4Remove
      name 5127.0.0.5Remove
      name 6127.0.0.6Remove
      name 7127.0.0.7Remove
      name 8127.0.0.8Remove
      name 9127.0.0.9Remove
      name 10127.0.0.10Remove
      name 11127.0.0.11Remove
      name 12127.0.0.12Remove
      name 13127.0.0.13Remove
      name 14127.0.0.14Remove
      name 15127.0.0.15Remove
      name 16127.0.0.16Remove
      name 17127.0.0.17Remove
      name 18127.0.0.18Remove
      name 19127.0.0.19Remove
      name 20127.0.0.20Remove
      name 21127.0.0.21Remove
      name 22127.0.0.22Remove
      name 23127.0.0.23Remove
      name 24127.0.0.24Remove
      name 25127.0.0.25Remove
      name 26127.0.0.26Remove
      name 27127.0.0.27Remove
      name 28127.0.0.28Remove
      name 29127.0.0.29Remove
      name 30127.0.0.30Remove
      name 31127.0.0.31Remove
      name 32127.0.0.32Remove
      name 33127.0.0.33Remove
      name 34127.0.0.34Remove
      name 35127.0.0.35Remove
      name 36127.0.0.36Remove
      name 37127.0.0.37Remove
      name 38127.0.0.38Remove
      name 39127.0.0.39Remove
      name 40127.0.0.40Remove
      name 41127.0.0.41Remove
      name 42127.0.0.42Remove
      name 43127.0.0.43Remove
      name 44127.0.0.44Remove
      name 45127.0.0.45Remove
      name 46127.0.0.46Remove
      name 47127.0.0.47Remove
      name 48127.0.0.48Remove
      name 49127.0.0.49Remove
      name 50127.0.0.50Remove
      name 51127.0.0.51Remove
      name 52127.0.0.52Remove
      name 53127.0.0.53Remove
      name 54127.0.0.54Remove
      name 55127.0.0.55Remove
      name 56127.0.0.56Remove
      name 57127.0.0.57Remove
      name 58127.0.0.58Remove
      name 59127.0.0.59Remove
      name 60127.0.0.60Remove
      name 61127.0.0.61Remove
      name 62127.0.0.62Remove
      name 63127.0.0.63Remove
      name 64127.0.0.64Remove
      name 65127.0.0.65Remove
      name 66127.0.0.66Remove
      name 67127.0.0.67Remove
      name 68127.0.0.68Remove
      name 69127.0.0.69Remove
      name 70127.0.0.70Remove
      name 71127.0.0.71Remove
      name 72127.0.0.72Remove
      name 73127.0.0.73Remove
      name 74127.0.0.74Remove
      name 75127.0.0.75Remove
      name 76127.0.0.76Remove
      name 77127.0.0.77Remove
      name 78127.0.0.78Remove
      name 79127.0.0.79Remove
      name 80127.0.0.80Remove
      name 81127.0.0.81Remove
      name 82127.0.0.82Remove
      name 83127.0.0.83Remove
      name 84127.0.0.84Remove
      name 85127.0.0.85Remove
      name 86127.0.0.86Remove
      name 87127.0.0.87Remove
      name 88127.0.0.88Remove
      name 89127.0.0.89Remove
      name 90127.0.0.90Remove
      name 91127.0.0.91Remove
      name 92127.0.0.92Remove
      name 93127.0.0.93Remove
      name 94127.0.0.94Remove
      name 95127.0.0.95Remove
      name 96127.0.0.96Remove
      name 97127.0.0.97Remove
      name 98127.0.0.98Remove
      name 99127.0.0.99Remove
      name 100127.0.0.100Remove
      " +"
      Name
      IP
      Operation
      name 1127.0.0.1Remove
      name 2127.0.0.2Remove
      name 3127.0.0.3Remove
      name 4127.0.0.4Remove
      name 5127.0.0.5Remove
      name 6127.0.0.6Remove
      name 7127.0.0.7Remove
      name 8127.0.0.8Remove
      name 9127.0.0.9Remove
      name 10127.0.0.10Remove
      name 11127.0.0.11Remove
      name 12127.0.0.12Remove
      name 13127.0.0.13Remove
      name 14127.0.0.14Remove
      name 15127.0.0.15Remove
      name 16127.0.0.16Remove
      name 17127.0.0.17Remove
      name 18127.0.0.18Remove
      name 19127.0.0.19Remove
      name 20127.0.0.20Remove
      name 21127.0.0.21Remove
      name 22127.0.0.22Remove
      name 23127.0.0.23Remove
      name 24127.0.0.24Remove
      name 25127.0.0.25Remove
      name 26127.0.0.26Remove
      name 27127.0.0.27Remove
      name 28127.0.0.28Remove
      name 29127.0.0.29Remove
      name 30127.0.0.30Remove
      name 31127.0.0.31Remove
      name 32127.0.0.32Remove
      name 33127.0.0.33Remove
      name 34127.0.0.34Remove
      name 35127.0.0.35Remove
      name 36127.0.0.36Remove
      name 37127.0.0.37Remove
      name 38127.0.0.38Remove
      name 39127.0.0.39Remove
      name 40127.0.0.40Remove
      name 41127.0.0.41Remove
      name 42127.0.0.42Remove
      name 43127.0.0.43Remove
      name 44127.0.0.44Remove
      name 45127.0.0.45Remove
      name 46127.0.0.46Remove
      name 47127.0.0.47Remove
      name 48127.0.0.48Remove
      name 49127.0.0.49Remove
      name 50127.0.0.50Remove
      name 51127.0.0.51Remove
      name 52127.0.0.52Remove
      name 53127.0.0.53Remove
      name 54127.0.0.54Remove
      name 55127.0.0.55Remove
      name 56127.0.0.56Remove
      name 57127.0.0.57Remove
      name 58127.0.0.58Remove
      name 59127.0.0.59Remove
      name 60127.0.0.60Remove
      name 61127.0.0.61Remove
      name 62127.0.0.62Remove
      name 63127.0.0.63Remove
      name 64127.0.0.64Remove
      name 65127.0.0.65Remove
      name 66127.0.0.66Remove
      name 67127.0.0.67Remove
      name 68127.0.0.68Remove
      name 69127.0.0.69Remove
      name 70127.0.0.70Remove
      name 71127.0.0.71Remove
      name 72127.0.0.72Remove
      name 73127.0.0.73Remove
      name 74127.0.0.74Remove
      name 75127.0.0.75Remove
      name 76127.0.0.76Remove
      name 77127.0.0.77Remove
      name 78127.0.0.78Remove
      name 79127.0.0.79Remove
      name 80127.0.0.80Remove
      name 81127.0.0.81Remove
      name 82127.0.0.82Remove
      name 83127.0.0.83Remove
      name 84127.0.0.84Remove
      name 85127.0.0.85Remove
      name 86127.0.0.86Remove
      name 87127.0.0.87Remove
      name 88127.0.0.88Remove
      name 89127.0.0.89Remove
      name 90127.0.0.90Remove
      name 91127.0.0.91Remove
      name 92127.0.0.92Remove
      name 93127.0.0.93Remove
      name 94127.0.0.94Remove
      name 95127.0.0.95Remove
      name 96127.0.0.96Remove
      name 97127.0.0.97Remove
      name 98127.0.0.98Remove
      name 99127.0.0.99Remove
      name 100127.0.0.100Remove
      " ``` #### `Table selectedKeys` ``` -"
      表头1
      表头2
      第一行哈哈
      第二行哈哈
      表头1
      表头2
      第一行哈哈
      第二行哈哈
      " +"
      表头1
      表头2
      第一行哈哈
      第二行哈哈
      表头1
      表头2
      第一行哈哈
      第二行哈哈
      " ``` #### `Table showIndeterminate` ``` -"
      表头1
      表头2
      第一行哈哈2
      第二行哈哈2
      " +"
      表头1
      表头2
      第一行哈哈2
      第二行哈哈2
      " ``` #### `Table sort` ``` -"
      姓名
      年龄
      aa1
      cc5
      bb9
      " +"
      姓名
      年龄
      aa1
      cc5
      bb9
      " ``` #### `Table stickHeader` ``` -"
      姓名
      性别
      Javeymale
      Kanilyfemale
      " +"
      姓名
      性别
      Javeymale
      Kanilyfemale
      " ``` #### `Table stickScrollbar` ``` -"
      Name
      Column1
      Column2
      Column3
      Column4
      Action
      Johntesttesttesttestaction
      Tomtesttesttesttestaction
      Javeytesttesttesttestaction
      " +"
      Name
      Column1
      Column2
      Column3
      Column4
      Action
      Johntesttesttesttestaction
      Tomtesttesttesttestaction
      Javeytesttesttesttestaction
      " ``` #### `Table title` ``` -"
      自定义表头内容
      表头2
      第一行哈哈2
      第二行哈哈2
      " +"
      自定义表头内容
      表头2
      第一行哈哈2
      第二行哈哈2
      " ``` #### `Table tooltip` ``` -"
      表头1
      表头2
      第一行哈哈1
      第二行哈哈2
      第三行哈哈3
      " +"
      表头1
      表头2
      第一行哈哈1
      第二行哈哈2
      第三行哈哈3
      " ``` #### `Table tree` ``` -"
      Name
      Size
      Audios12MB +"
      Name
      Size
      Audios12MB
      Images14MB
      doc.pdf18MB -

      自定义展开Icon位置

      Name
      Size
      Audios12MB +

      自定义展开Icon位置

      Name
      Size
      Audios12MB
      Images14MB
      doc.pdf18MB
      " @@ -2349,7 +2355,13 @@ #### `Table type` ``` -"
      Type
      Value
      typeborder
      Type
      Value
      typegrid
      " +"
      Type
      Value
      typeborder
      Type
      Value
      typegrid
      " +``` + +#### `Table virtual` + +``` +"

      表格

      Title 1
      Title 2
      Cell 0-1Cell 0-2
      Cell 1-1Cell 1-2
      Cell 2-1Cell 2-2
      Cell 3-1Cell 3-2
      Cell 4-1Cell 4-2
      Cell 5-1Cell 5-2
      Cell 6-1Cell 6-2
      Cell 7-1Cell 7-2
      Cell 8-1Cell 8-2
      Cell 9-1Cell 9-2
      Cell 10-1Cell 10-2
      Cell 11-1Cell 11-2
      Cell 12-1Cell 12-2
      Cell 13-1Cell 13-2
      Cell 14-1Cell 14-2
      Cell 15-1Cell 15-2

      树形表格

      Title 1
      Title 2
      Node-076
      Node-120
      Node-217
      Node-381
      Node-416
      Node-537
      Node-671
      Node-767
      Node-88
      Node-956
      Node-105
      Node-1131
      Node-1238
      Node-1321
      Node-1478
      Node-1533
      " ``` ## `tabs` @@ -2471,37 +2483,37 @@ #### `Timepicker basic` ``` -"
      - You selected:

      请选择时间
      +"
      + You selected:

      请选择时间
      You selected: []
      " ``` #### `Timepicker format` ``` -"
      You selected:

      You selected:

      You selected: null
      " +"
      You selected:

      You selected:

      You selected: null
      " ``` #### `Timepicker range` ``` -"
      - You selected: null

      开始时间 ~ 结束时间
      +"
      + You selected: null

      开始时间 ~ 结束时间
      You selected: []
      " ``` #### `Timepicker step` ``` -"
      - You selected: null

      请选择时间
      +"
      + You selected: null

      请选择时间
      You selected: []
      " ``` #### `Timepicker stepRange` ``` -"
      +"
      You selected: null
      " ``` @@ -2602,7 +2614,7 @@ #### `Tooltip trigger` ``` -"
      " +"
      " ``` #### `Tooltip confirm` @@ -2625,7 +2637,7 @@ ``` "
      0 / - 0
      全部策略
      0 / + 0
      全部策略
      0 / 0

      You selected: []

      " ``` @@ -2647,8 +2659,8 @@ ``` "
      0 / - 5
      0 / - 0
      " + 5
      0 / + 0
      " ``` #### `Transfer leftChecked` @@ -2706,7 +2718,7 @@ #### `Tree filterable` ``` -"
      First floor-1
      First floor-2
      Second floor-2.1
      Third floor-2.1.1
      Third floor-2.1.2
      Second floor-2.2
      " +"
      First floor-1
      First floor-2
      Second floor-2.1
      Third floor-2.1.1
      Third floor-2.1.2
      Second floor-2.2
      " ``` #### `Tree loading` @@ -2744,7 +2756,7 @@ #### `TreeSelect filter` ``` -"
      " +"
      " ``` #### `TreeSelect load` @@ -2797,3 +2809,46 @@ "
      " ``` +## `virtualList` + +#### `VirtualList basic` + +``` +"

      1. 定高元素

      测试0
      测试1
      测试2
      测试3
      测试4
      测试5
      测试6
      测试7
      测试8
      测试9
      测试10
      测试11
      测试12
      测试13
      测试14
      测试15
      测试16
      测试17
      测试18
      测试19
      测试20
      测试21
      测试22
      测试23
      测试24
      测试25
      测试26
      测试27
      测试28
      测试29
      测试30

      2. 不定高元素

      不定高度项 0 +行内容行内容行内容
      不定高度项 1 +行内容行内容行内容
      不定高度项 2 +行内容行内容行内容行内容行内容
      不定高度项 3 +行内容
      不定高度项 4 +行内容
      不定高度项 5 +行内容行内容行内容行内容
      不定高度项 6 +行内容行内容行内容
      不定高度项 7 +行内容行内容行内容行内容
      不定高度项 8 +行内容行内容行内容行内容
      不定高度项 9 +行内容行内容行内容行内容
      不定高度项 10 +行内容行内容
      不定高度项 11 +行内容行内容行内容行内容行内容
      不定高度项 12 +行内容行内容行内容
      不定高度项 13 +行内容
      不定高度项 14 +行内容行内容行内容
      不定高度项 15 +行内容
      不定高度项 16 +行内容行内容
      不定高度项 17 +行内容行内容行内容
      不定高度项 18 +行内容行内容行内容行内容
      不定高度项 19 +行内容行内容
      不定高度项 20 +行内容行内容行内容行内容
      不定高度项 21 +行内容行内容行内容
      不定高度项 22 +行内容行内容
      " +``` + +#### `VirtualList combined` + +``` +"
      • 测试0
      • 测试1
      • 测试2
      • 测试3
      • 测试4
      • 测试5
      • 测试6
      • 测试7
      • 测试8
      • 测试9
      • 测试10
      • 测试11
      • 测试12
      • 测试13
      • 测试14
      • 测试15
      • 测试16
      • 测试17
      • 测试18
      • 测试19
      • 测试20
      • 测试21
      • 测试22
      • 测试23
      • 测试24
      • 测试25
      • 测试26
      • 测试27
      • 测试28
      • 测试29
      • 测试30
      • 测试31
      " +``` + +#### `VirtualList delete` + +``` +"
      测试0
      测试1
      测试2
      测试3
      测试4
      测试5
      测试6
      测试7
      测试8
      测试9
      测试10
      测试11
      测试12
      测试13
      测试14
      测试15
      测试16
      测试17
      测试18
      测试19
      测试20
      测试21
      测试22
      测试23
      测试24
      测试25
      测试26
      测试27
      测试28
      测试29
      测试30
      " +``` + diff --git a/test/__snapshots__/Diagram.md b/test/__snapshots__/Diagram.md index 6b119845b..a95f3f143 100644 --- a/test/__snapshots__/Diagram.md +++ b/test/__snapshots__/Diagram.md @@ -97,11 +97,11 @@ #### `test all layouts` ``` -"
      " +"
      " ``` ``` -"
      " +"
      " ``` ``` diff --git a/test/__snapshots__/Dialog.md b/test/__snapshots__/Dialog.md index f92a4b9e9..9c986b1b3 100644 --- a/test/__snapshots__/Dialog.md +++ b/test/__snapshots__/Dialog.md @@ -31,7 +31,7 @@ #### `demos test` ``` -"
      Customized ok callback
      " +"
      Customized ok callback
      " ``` ``` @@ -45,7 +45,7 @@ ``` ``` -"
      Dialog Title
      " +"
      Dialog Title
      " ``` ``` @@ -53,7 +53,7 @@ ``` ``` -"
      Dialog Title
      " +"
      Dialog Title
      " ``` ``` @@ -71,21 +71,21 @@ #### `static methods` ``` -"
      test
      " +"
      test
      " ``` ``` -"
      test
      " +"
      test
      " ``` ``` -"
      error
      test
      " +"
      error
      test
      " ``` #### `should remove body` ``` -"
      Dialog Title
      " +"
      Dialog Title
      " ``` #### `should not close when click and move outside` diff --git a/test/__snapshots__/Editable.md b/test/__snapshots__/Editable.md index 537a0b84d..b8d57aedb 100644 --- a/test/__snapshots__/Editable.md +++ b/test/__snapshots__/Editable.md @@ -3,7 +3,7 @@ #### `basic test` ``` -"
      editable text

      " +"
      editable text

      " ``` ``` @@ -11,7 +11,7 @@ ``` ``` -"
      test

      " +"
      test

      " ``` ``` @@ -19,7 +19,7 @@ ``` ``` -"
      test

      " +"
      test

      " ``` ``` @@ -29,19 +29,19 @@ #### `validate test` ``` -"
      a

      100

      100
      " +"
      a

      100

      100
      " ``` ``` -"
      a

      a

      100
      " +"
      a

      a

      100
      " ``` ``` -"
      a

      a

      a
      " +"
      a

      a

      a
      " ``` ``` -"
      a

      a

      " +"
      a

      a

      " ``` ``` @@ -66,6 +66,6 @@ #### `should auto use input or textarea` ``` -"


      " +"


      " ``` diff --git a/test/__snapshots__/Form.md b/test/__snapshots__/Form.md index fdcefb685..4f19b97e9 100644 --- a/test/__snapshots__/Form.md +++ b/test/__snapshots__/Form.md @@ -3,7 +3,7 @@ #### `validate` ``` -"
      必须填写
      请选择
      必须选择
      必须选择
      必须选择
      必须选择
      0
      0100
      必须填写
      必须填写
      必须填写
      必须填写
      必须填写
      " +"
      必须填写
      请选择
      必须选择
      必须选择
      必须选择
      必须选择
      0
      0100
      必须填写
      必须填写
      必须填写
      必须填写
      必须填写
      " ``` ``` @@ -13,11 +13,11 @@ Object { ``` ``` -"
      必须填写
      请选择
      必须选择
      必须选择
      必须选择
      必须选择
      0
      0100
      必须填写
      必须填写
      必须填写
      必须填写
      必须填写
      " +"
      必须填写
      请选择
      必须选择
      必须选择
      必须选择
      必须选择
      0
      0100
      必须填写
      必须填写
      必须填写
      必须填写
      必须填写
      " ``` ``` -"
      必须填写
      请选择
      必须选择
      必须选择
      必须选择
      必须选择
      0
      0100
      必须填写
      必须填写
      必须填写
      必须填写
      必须填写
      " +"
      必须填写
      请选择
      必须选择
      必须选择
      必须选择
      必须选择
      0
      0100
      必须填写
      必须填写
      必须填写
      必须填写
      必须填写
      " ``` ``` @@ -35,19 +35,19 @@ Object { #### `custom rules` ``` -"
      只能输入字母
      " +"
      只能输入字母
      " ``` ``` -"
      只能输入字母
      " +"
      只能输入字母
      " ``` ``` -"
      不能相同
      不能相同
      " +"
      不能相同
      不能相同
      " ``` ``` -"
      不能相同
      不能相同
      " +"
      不能相同
      不能相同
      " ``` ``` @@ -66,11 +66,11 @@ Object { #### `validate when rules have changed` ``` -"
      必须填写
      " +"
      必须填写
      " ``` ``` -"
      " +"
      " ``` ``` @@ -81,11 +81,11 @@ Object { #### `validate asynchronously` ``` -"
      用户名 a 已被占用
      " +"
      用户名 a 已被占用
      " ``` ``` -"
      " +"
      " ``` ``` diff --git a/test/__snapshots__/Input.md b/test/__snapshots__/Input.md index 4e50bc47f..5c9b0112f 100644 --- a/test/__snapshots__/Input.md +++ b/test/__snapshots__/Input.md @@ -3,23 +3,23 @@ #### `search input` ``` -"
      " +"
      " ``` ``` -"
      " +"
      " ``` ``` -"
      " +"
      " ``` ``` -"
      " +"
      " ``` ``` -"
      " +"
      " ``` ``` diff --git a/test/__snapshots__/Layout.md b/test/__snapshots__/Layout.md index b66c4d8ab..2c7f1cb80 100644 --- a/test/__snapshots__/Layout.md +++ b/test/__snapshots__/Layout.md @@ -3,6 +3,6 @@ #### `nest Layout in Body with fixed header` ``` -"
      body
      " +"
      body
      " ``` diff --git a/test/__snapshots__/Pagination.md b/test/__snapshots__/Pagination.md index f1a272c13..9b81cd0ff 100644 --- a/test/__snapshots__/Pagination.md +++ b/test/__snapshots__/Pagination.md @@ -29,11 +29,11 @@ #### `goto test` ``` -"
      共 200 条
      10 条 / 页
      前往
      " +"
      共 200 条
      10 条 / 页
      前往
      " ``` ``` -"
      共 200 条
      10 条 / 页
      前往
      " +"
      共 200 条
      10 条 / 页
      前往
      " ``` ``` diff --git a/test/__snapshots__/Select.md b/test/__snapshots__/Select.md index 184f2911d..56f92577f 100644 --- a/test/__snapshots__/Select.md +++ b/test/__snapshots__/Select.md @@ -75,9 +75,9 @@ ``` ``` -"
      +"
      Day: xxx

      - +0
      请输入或选择
      + +0
      请输入或选择
      Days: []" ``` @@ -86,9 +86,9 @@ ``` ``` -"
      +"
      Day: xxx

      - +0
      请输入或选择
      + +0
      请输入或选择
      Days: []" ``` @@ -97,16 +97,16 @@ ``` ``` -"
      +"
      Day: xxx

      - +0
      请输入或选择
      + +0
      请输入或选择
      Days: []" ``` #### `disabled option does not allow clearable and close` ``` -"
      星期二
      星期天
      星期三
      星期五
      +"
      星期二
      星期天
      星期三
      星期五
      You selected: [\"Tuesday\",\"Friday\"]
      " ``` diff --git a/test/__snapshots__/Slider.md b/test/__snapshots__/Slider.md index 8bad25306..6b44bc5e1 100644 --- a/test/__snapshots__/Slider.md +++ b/test/__snapshots__/Slider.md @@ -3,19 +3,19 @@ #### `range test` ``` -"
      76
      80
      0100
      -
      " +"
      76
      80
      0100
      -
      " ``` ``` -"
      60
      76
      0100
      -
      " +"
      60
      76
      0100
      -
      " ``` ``` -"
      50
      76
      0100
      -
      " +"
      50
      76
      0100
      -
      " ``` ``` -"
      50
      70
      0100
      -
      " +"
      50
      70
      0100
      -
      " ``` ``` @@ -34,15 +34,15 @@ #### `keyboard operation for basic` ``` -"
      276
      50500
      " +"
      276
      50500
      " ``` ``` -"
      277
      50500
      " +"
      277
      50500
      " ``` ``` -"
      277
      50500
      " +"
      277
      50500
      " ``` ``` @@ -61,27 +61,27 @@ #### `keyboard operation for range` ``` -"
      51
      51
      0100
      -
      " +"
      51
      51
      0100
      -
      " ``` ``` -"
      51
      52
      0100
      -
      " +"
      51
      52
      0100
      -
      " ``` ``` -"
      51
      53
      0100
      -
      " +"
      51
      53
      0100
      -
      " ``` ``` -"
      50
      50
      0100
      -
      " +"
      50
      50
      0100
      -
      " ``` ``` -"
      49
      50
      0100
      -
      " +"
      49
      50
      0100
      -
      " ``` ``` -"
      48
      50
      0100
      -
      " +"
      48
      50
      0100
      -
      " ``` ``` @@ -108,15 +108,15 @@ #### `disabled` ``` -"
      50
      0100
      " +"
      50
      0100
      " ``` ``` -"
      50
      0100
      " +"
      50
      0100
      " ``` ``` -"
      50
      0100
      " +"
      50
      0100
      " ``` ``` @@ -135,18 +135,18 @@ #### `min/max/step is undefined` ``` -"
      1
      0100
      " +"
      1
      0100
      " ``` #### `should log error when max < min` ``` -"
      0
      200
      " +"
      0
      200
      " ``` #### `should locate at the end if start value is equal to end value` ``` -"
      1
      11
      1
      1
      11
      -
      " +"
      1
      11
      1
      1
      11
      -
      " ``` diff --git a/test/__snapshots__/Spinner.md b/test/__snapshots__/Spinner.md index 402a6bfe4..3ce54ad0d 100644 --- a/test/__snapshots__/Spinner.md +++ b/test/__snapshots__/Spinner.md @@ -3,39 +3,39 @@ #### `step/max/min test` ``` -"
      " +"
      " ``` ``` -"
      " +"
      " ``` ``` -"
      " +"
      " ``` ``` -"
      " +"
      " ``` ``` -"
      " +"
      " ``` ``` -"
      " +"
      " ``` ``` -"
      " +"
      " ``` ``` -"
      " +"
      " ``` ``` -"
      " +"
      " ``` ``` @@ -74,12 +74,12 @@ #### `precision` ``` -"
      " +"
      " ``` #### `should log error when max < min` ``` -"
      " +"
      " ``` diff --git a/test/__snapshots__/Table.md b/test/__snapshots__/Table.md index 50522c2de..8275da862 100644 --- a/test/__snapshots__/Table.md +++ b/test/__snapshots__/Table.md @@ -21,57 +21,57 @@ #### `expand & shrink` ``` -"
      点击整行展开内容
      Javey
      Email: jiawei23716@sina.com
      Jiawei
      " +"
      点击整行展开内容
      Javey
      Email: jiawei23716@sina.com
      Jiawei
      " ``` ``` -"
      点击整行展开内容
      Javey
      Email: jiawei23716@sina.com
      Jiawei
      " +"
      点击整行展开内容
      Javey
      Email: jiawei23716@sina.com
      Jiawei
      " ``` ``` -"
      点击+,展开内容
      Javey
      Jiawei
      " +"
      点击+,展开内容
      Javey
      Jiawei
      " ``` ``` -"
      点击+,展开内容
      Javey
      Email: jiawei23716@sina.com
      Jiawei
      " +"
      点击+,展开内容
      Javey
      Email: jiawei23716@sina.com
      Jiawei
      " ``` ``` -"
      点击+,展开内容
      Javey
      Email: jiawei23716@sina.com
      Jiawei
      " +"
      点击+,展开内容
      Javey
      Email: jiawei23716@sina.com
      Jiawei
      " ``` #### `merge cells` ``` -"
      Weekday
      Forenoon
      Afternoon
      Time
      Class 1
      Class 2
      Class 3
      Class 4
      Class 5
      Class 6
      Class 7
      Time
      Monday08:00 ~ 12:00EnglishMathematicsHistoryEnglishHistoryEnglishHistory14:00 ~ 17:00
      TuesdayEnglishGeopraghyHistoryEnglishHistoryMathematics
      WendesdayHistoryChineseHistoryEnglishChineseHistory
      ThursdayHistoryEnglishGeopraghyMathematicsGeopraghyEnglishMathematics
      FridayGeopraghyEnglishGeopraghyEnglish
      SaturdayGeopraghyEnglishGeopraghyEnglish
      " +"
      Weekday
      Forenoon
      Afternoon
      Time
      Class 1
      Class 2
      Class 3
      Class 4
      Class 5
      Class 6
      Class 7
      Time
      Monday08:00 ~ 12:00EnglishMathematicsHistoryEnglishHistoryEnglishHistory14:00 ~ 17:00
      TuesdayEnglishGeopraghyHistoryEnglishHistoryMathematics
      WendesdayHistoryChineseHistoryEnglishChineseHistory
      ThursdayHistoryEnglishGeopraghyMathematicsGeopraghyEnglishMathematics
      FridayGeopraghyEnglishGeopraghyEnglish
      SaturdayGeopraghyEnglishGeopraghyEnglish
      " ``` ``` -"
      Weekday
      Forenoon
      Afternoon
      Time
      Class 1
      Class 2
      Class 3
      Class 4
      Class 5
      Class 6
      Class 7
      Time
      Monday08:00 ~ 12:00EnglishMathematicsHistoryEnglishHistoryEnglishHistory14:00 ~ 17:00
      TuesdayEnglishGeopraghyHistoryEnglishHistoryMathematics
      WendesdayHistoryChineseHistoryEnglishChineseHistory
      ThursdayHistoryEnglishGeopraghyMathematicsGeopraghyEnglishMathematics
      FridayGeopraghyEnglishGeopraghyEnglish
      SaturdayGeopraghyEnglishGeopraghyEnglish
      " +"
      Weekday
      Forenoon
      Afternoon
      Time
      Class 1
      Class 2
      Class 3
      Class 4
      Class 5
      Class 6
      Class 7
      Time
      Monday08:00 ~ 12:00EnglishMathematicsHistoryEnglishHistoryEnglishHistory14:00 ~ 17:00
      TuesdayEnglishGeopraghyHistoryEnglishHistoryMathematics
      WendesdayHistoryChineseHistoryEnglishChineseHistory
      ThursdayHistoryEnglishGeopraghyMathematicsGeopraghyEnglishMathematics
      FridayGeopraghyEnglishGeopraghyEnglish
      SaturdayGeopraghyEnglishGeopraghyEnglish
      " ``` ``` -"
      Weekday
      Forenoon
      Afternoon
      Time
      Class 1
      Class 2
      Class 3
      Class 4
      Class 5
      Class 6
      Class 7
      Time
      Monday08:00 ~ 12:00EnglishMathematicsHistoryEnglishHistoryEnglishHistory14:00 ~ 17:00
      TuesdayEnglishGeopraghyHistoryEnglishHistoryMathematics
      WendesdayHistoryChineseHistoryEnglishChineseHistory
      ThursdayHistoryEnglishGeopraghyMathematicsGeopraghyEnglishMathematics
      FridayGeopraghyEnglishGeopraghyEnglish
      SaturdayGeopraghyEnglishGeopraghyEnglish
      " +"
      Weekday
      Forenoon
      Afternoon
      Time
      Class 1
      Class 2
      Class 3
      Class 4
      Class 5
      Class 6
      Class 7
      Time
      Monday08:00 ~ 12:00EnglishMathematicsHistoryEnglishHistoryEnglishHistory14:00 ~ 17:00
      TuesdayEnglishGeopraghyHistoryEnglishHistoryMathematics
      WendesdayHistoryChineseHistoryEnglishChineseHistory
      ThursdayHistoryEnglishGeopraghyMathematicsGeopraghyEnglishMathematics
      FridayGeopraghyEnglishGeopraghyEnglish
      SaturdayGeopraghyEnglishGeopraghyEnglish
      " ``` ``` -"
      Weekday
      Forenoon
      Afternoon
      Time
      Class 1
      Class 2
      Class 3
      Class 4
      Class 5
      Class 6
      Class 7
      Time
      Monday08:00 ~ 12:00EnglishMathematicsHistoryEnglishHistoryEnglishHistory14:00 ~ 17:00
      TuesdayEnglishGeopraghyHistoryEnglishHistoryMathematics
      WendesdayHistoryChineseHistoryEnglishChineseHistory
      ThursdayHistoryEnglishGeopraghyMathematicsGeopraghyEnglishMathematics
      FridayGeopraghyEnglishGeopraghyEnglish
      SaturdayGeopraghyEnglishGeopraghyEnglish
      " +"
      Weekday
      Forenoon
      Afternoon
      Time
      Class 1
      Class 2
      Class 3
      Class 4
      Class 5
      Class 6
      Class 7
      Time
      Monday08:00 ~ 12:00EnglishMathematicsHistoryEnglishHistoryEnglishHistory14:00 ~ 17:00
      TuesdayEnglishGeopraghyHistoryEnglishHistoryMathematics
      WendesdayHistoryChineseHistoryEnglishChineseHistory
      ThursdayHistoryEnglishGeopraghyMathematicsGeopraghyEnglishMathematics
      FridayGeopraghyEnglishGeopraghyEnglish
      SaturdayGeopraghyEnglishGeopraghyEnglish
      " ``` ``` -"
      Weekday
      Forenoon
      Afternoon
      Time
      Class 1
      Class 2
      Class 3
      Class 4
      Class 5
      Class 6
      Class 7
      Time
      Monday08:00 ~ 12:00EnglishMathematicsHistoryEnglishHistoryEnglishHistory14:00 ~ 17:00
      TuesdayEnglishGeopraghyHistoryEnglishHistoryMathematics
      WendesdayHistoryChineseHistoryEnglishChineseHistory
      ThursdayHistoryEnglishGeopraghyMathematicsGeopraghyEnglishMathematics
      FridayGeopraghyEnglishGeopraghyEnglish
      SaturdayGeopraghyEnglishGeopraghyEnglish
      " +"
      Weekday
      Forenoon
      Afternoon
      Time
      Class 1
      Class 2
      Class 3
      Class 4
      Class 5
      Class 6
      Class 7
      Time
      Monday08:00 ~ 12:00EnglishMathematicsHistoryEnglishHistoryEnglishHistory14:00 ~ 17:00
      TuesdayEnglishGeopraghyHistoryEnglishHistoryMathematics
      WendesdayHistoryChineseHistoryEnglishChineseHistory
      ThursdayHistoryEnglishGeopraghyMathematicsGeopraghyEnglishMathematics
      FridayGeopraghyEnglishGeopraghyEnglish
      SaturdayGeopraghyEnglishGeopraghyEnglish
      " ``` ``` -"
      Weekday
      Forenoon
      Afternoon
      Time
      Class 1
      Class 2
      Class 3
      Class 4
      Class 5
      Class 6
      Class 7
      Time
      Monday08:00 ~ 12:00EnglishMathematicsHistoryEnglishHistoryEnglishHistory14:00 ~ 17:00
      TuesdayEnglishGeopraghyHistoryEnglishHistoryMathematics
      WendesdayHistoryChineseHistoryEnglishChineseHistory
      ThursdayHistoryEnglishGeopraghyMathematicsGeopraghyEnglishMathematics
      FridayGeopraghyEnglishGeopraghyEnglish
      SaturdayGeopraghyEnglishGeopraghyEnglish
      " +"
      Weekday
      Forenoon
      Afternoon
      Time
      Class 1
      Class 2
      Class 3
      Class 4
      Class 5
      Class 6
      Class 7
      Time
      Monday08:00 ~ 12:00EnglishMathematicsHistoryEnglishHistoryEnglishHistory14:00 ~ 17:00
      TuesdayEnglishGeopraghyHistoryEnglishHistoryMathematics
      WendesdayHistoryChineseHistoryEnglishChineseHistory
      ThursdayHistoryEnglishGeopraghyMathematicsGeopraghyEnglishMathematics
      FridayGeopraghyEnglishGeopraghyEnglish
      SaturdayGeopraghyEnglishGeopraghyEnglish
      " ``` ``` -"
      Weekday
      Forenoon
      Afternoon
      Time
      Class 1
      Class 2
      Class 3
      Class 4
      Class 5
      Class 6
      Class 7
      Time
      Monday08:00 ~ 12:00EnglishMathematicsHistoryEnglishHistoryEnglishHistory14:00 ~ 17:00
      TuesdayEnglishGeopraghyHistoryEnglishHistoryMathematics
      WendesdayHistoryChineseHistoryEnglishChineseHistory
      ThursdayHistoryEnglishGeopraghyMathematicsGeopraghyEnglishMathematics
      FridayGeopraghyEnglishGeopraghyEnglish
      SaturdayGeopraghyEnglishGeopraghyEnglish
      " +"
      Weekday
      Forenoon
      Afternoon
      Time
      Class 1
      Class 2
      Class 3
      Class 4
      Class 5
      Class 6
      Class 7
      Time
      Monday08:00 ~ 12:00EnglishMathematicsHistoryEnglishHistoryEnglishHistory14:00 ~ 17:00
      TuesdayEnglishGeopraghyHistoryEnglishHistoryMathematics
      WendesdayHistoryChineseHistoryEnglishChineseHistory
      ThursdayHistoryEnglishGeopraghyMathematicsGeopraghyEnglishMathematics
      FridayGeopraghyEnglishGeopraghyEnglish
      SaturdayGeopraghyEnglishGeopraghyEnglish
      " ``` ``` -"
      Weekday
      Forenoon
      Afternoon
      Time
      Class 1
      Class 2
      Class 3
      Class 4
      Class 5
      Class 6
      Class 7
      Time
      Monday08:00 ~ 12:00EnglishMathematicsHistoryEnglishHistoryEnglishHistory14:00 ~ 17:00
      TuesdayEnglishGeopraghyHistoryEnglishHistoryMathematics
      WendesdayHistoryChineseHistoryEnglishChineseHistory
      ThursdayHistoryEnglishGeopraghyMathematicsGeopraghyEnglishMathematics
      FridayGeopraghyEnglishGeopraghyEnglish
      SaturdayGeopraghyEnglishGeopraghyEnglish
      " +"
      Weekday
      Forenoon
      Afternoon
      Time
      Class 1
      Class 2
      Class 3
      Class 4
      Class 5
      Class 6
      Class 7
      Time
      Monday08:00 ~ 12:00EnglishMathematicsHistoryEnglishHistoryEnglishHistory14:00 ~ 17:00
      TuesdayEnglishGeopraghyHistoryEnglishHistoryMathematics
      WendesdayHistoryChineseHistoryEnglishChineseHistory
      ThursdayHistoryEnglishGeopraghyMathematicsGeopraghyEnglishMathematics
      FridayGeopraghyEnglishGeopraghyEnglish
      SaturdayGeopraghyEnglishGeopraghyEnglish
      " ``` #### `append a row in merged table` @@ -83,7 +83,7 @@ #### `group` ``` -"
      全部
      运行中
      已关闭
      " +"
      全部
      运行中
      已关闭
      " ``` ``` @@ -91,7 +91,7 @@ ``` ``` -"
      " +"
      " ``` ``` @@ -103,29 +103,29 @@ ``` ``` -"
      " +"
      " ``` ``` -"
      " +"
      " ``` ``` -"
      label
      " +"
      label
      " ``` #### `fix columns` ``` -"
      Name
      Column1
      Column2
      Column3
      Column4
      Action
      Johntesttesttesttestaction
      Tomtesttesttesttestaction
      Javeytesttesttesttestaction
      " +"
      Name
      Column1
      Column2
      Column3
      Column4
      Action
      Johntesttesttesttestaction
      Tomtesttesttesttestaction
      Javeytesttesttesttestaction
      " ``` ``` -"
      Name
      Column1
      Column2
      Column3
      Column4
      Action
      Johntesttesttesttestaction
      Tomtesttesttesttestaction
      Javeytesttesttesttestaction
      " +"
      Name
      Column1
      Column2
      Column3
      Column4
      Action
      Johntesttesttesttestaction
      Tomtesttesttesttestaction
      Javeytesttesttesttestaction
      " ``` ``` -"
      Name
      Column1
      Column2
      Column3
      Column4
      Action
      Johntesttesttesttestaction
      Tomtesttesttesttestaction
      Javeytesttesttesttestaction
      " +"
      Name
      Column1
      Column2
      Column3
      Column4
      Action
      Johntesttesttesttestaction
      Tomtesttesttesttestaction
      Javeytesttesttesttestaction
      " ``` #### `resize` @@ -149,11 +149,11 @@ #### `loading` ``` -"
      表头1
      表头2
      第一行哈哈1
      第二行哈哈2
      第三行哈哈3
      " +"
      表头1
      表头2
      第一行哈哈1
      第二行哈哈2
      第三行哈哈3
      " ``` ``` -"
      表头1
      表头2
      第一行哈哈1
      第二行哈哈2
      第三行哈哈3
      " +"
      表头1
      表头2
      第一行哈哈1
      第二行哈哈2
      第三行哈哈3
      " ``` #### `export` @@ -204,42 +204,48 @@ #### `tree` ``` -"
      Name
      Size
      Audios12MB +"
      Name
      Size
      Audios12MB
      Images14MB
      doc.pdf18MB -

      自定义展开Icon位置

      Name
      Size
      Audios12MB +

      自定义展开Icon位置

      Name
      Size
      Audios12MB
      Images14MB
      doc.pdf18MB
      " ``` ``` -"
      Name
      Size
      Audios12MB -
      Fly.mp37MB -
      Fade.aac5MB -
      Images14MB -
      doc.pdf18MB -
      " +"
      Name
      Size
      Audios12MB +
      Fly.mp37MB +
      Fade.aac5MB +
      Images14MB +
      doc.pdf18MB +

      自定义展开Icon位置

      Name
      Size
      Audios12MB +
      Images14MB +
      doc.pdf18MB +
      " ``` ``` -"
      Name
      Size
      Audios12MB -
      Fly.mp37MB -
      Fade.aac5MB -
      Images14MB -
      doc.pdf18MB -
      " +"
      Name
      Size
      Audios12MB +
      Fly.mp37MB +
      Fade.aac5MB +
      Images14MB +
      doc.pdf18MB +

      自定义展开Icon位置

      Name
      Size
      Audios12MB +
      Images14MB +
      doc.pdf18MB +
      " ``` #### `render a hidden table with minWidth` ``` -"
      暂无数据
      " +"
      暂无数据
      " ``` #### `draggable` ``` -"
      Name
      IP
      Operation
      name 2127.0.0.2Remove
      name 1127.0.0.1Remove
      name 3127.0.0.3Remove
      name 4127.0.0.4Remove
      name 5127.0.0.5Remove
      name 6127.0.0.6Remove
      name 7127.0.0.7Remove
      name 8127.0.0.8Remove
      name 9127.0.0.9Remove
      name 10127.0.0.10Remove
      name 11127.0.0.11Remove
      name 12127.0.0.12Remove
      name 13127.0.0.13Remove
      name 14127.0.0.14Remove
      name 15127.0.0.15Remove
      name 16127.0.0.16Remove
      name 17127.0.0.17Remove
      name 18127.0.0.18Remove
      name 19127.0.0.19Remove
      name 20127.0.0.20Remove
      " +"
      Name
      IP
      Operation
      name 2127.0.0.2Remove
      name 1127.0.0.1Remove
      name 3127.0.0.3Remove
      name 4127.0.0.4Remove
      name 5127.0.0.5Remove
      name 6127.0.0.6Remove
      name 7127.0.0.7Remove
      name 8127.0.0.8Remove
      name 9127.0.0.9Remove
      name 10127.0.0.10Remove
      name 11127.0.0.11Remove
      name 12127.0.0.12Remove
      name 13127.0.0.13Remove
      name 14127.0.0.14Remove
      name 15127.0.0.15Remove
      name 16127.0.0.16Remove
      name 17127.0.0.17Remove
      name 18127.0.0.18Remove
      name 19127.0.0.19Remove
      name 20127.0.0.20Remove
      " ``` diff --git a/test/__snapshots__/Tag.md b/test/__snapshots__/Tag.md index 50b094a80..0d9d9329e 100644 --- a/test/__snapshots__/Tag.md +++ b/test/__snapshots__/Tag.md @@ -18,8 +18,8 @@ ``` ``` -"
      primary
      success
      warning
      - +2
      " +"
      primary
      success
      warning
      danger
      with tooltip
      + +3
      " ``` ``` diff --git a/test/__snapshots__/Timepicker.md b/test/__snapshots__/Timepicker.md index 4579f5151..a4b7095d2 100644 --- a/test/__snapshots__/Timepicker.md +++ b/test/__snapshots__/Timepicker.md @@ -80,7 +80,7 @@ ``` ``` -"
      " +"
      " ``` ``` @@ -88,7 +88,7 @@ ``` ``` -"
      " +"
      " ``` ``` @@ -102,6 +102,6 @@ ``` ``` -"
      " +"
      " ``` diff --git a/test/__snapshots__/Transfer.md b/test/__snapshots__/Transfer.md index 803e33ef8..883d73427 100644 --- a/test/__snapshots__/Transfer.md +++ b/test/__snapshots__/Transfer.md @@ -30,31 +30,31 @@ ``` "
      0 / - 1
      0 / - 0
      " + 1
      0 / + 0
      " ``` ``` "
      1 / - 1
      0 / - 0
      " + 1
      0 / + 0
      " ``` ``` "
      1 / - 5
      0 / - 0
      " + 5
      0 / + 0
      " ``` ``` "
      4 / - 5
      0 / - 0
      " + 5
      0 / + 0
      " ``` ``` "
      4 / - 5
      0 / - 0
      " + 5
      0 / + 0
      " ``` diff --git a/test/__snapshots__/Tree.md b/test/__snapshots__/Tree.md index b3b1535ef..f487a77e9 100644 --- a/test/__snapshots__/Tree.md +++ b/test/__snapshots__/Tree.md @@ -188,11 +188,11 @@ Array [ #### `filterable` ``` -"
      First floor-1
      First floor-2
      " +"
      First floor-1
      First floor-2
      " ``` ``` -"
      First floor-2
      Second floor-2.1
      Third floor-2.1.1
      " +"
      First floor-2
      Second floor-2.1
      Third floor-2.1.1
      " ``` ``` diff --git a/test/__snapshots__/VirtualList.md b/test/__snapshots__/VirtualList.md new file mode 100644 index 000000000..6bcf4ca3a --- /dev/null +++ b/test/__snapshots__/VirtualList.md @@ -0,0 +1,14 @@ +# `VirtualList` + +#### `should render virtual list correctly` + +``` +"
      测试0
      测试1
      测试2
      测试3
      测试4
      测试5
      测试6
      测试7
      测试8
      测试9
      " +``` + +#### `should work with custom container and wrapper` + +``` +"
      • 测试0
      • 测试1
      • 测试2
      • 测试3
      • 测试4
      • 测试5
      • 测试6
      • 测试7
      • 测试8
      • 测试9
      • 测试10
      • 测试11
      • 测试12
      • 测试13
      • 测试14
      • 测试15
      • 测试16
      • 测试17
      • 测试18
      • 测试19
      • 测试20
      • 测试21
      • 测试22
      • 测试23
      • 测试24
      • 测试25
      • 测试26
      • 测试27
      • 测试28
      • 测试29
      • 测试30
      • 测试31
      " +``` +