@king-design/intact 3.5.1-beta.0 → 3.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,6 +3,7 @@ import {useState} from '../../hooks/useState';
3
3
  import {useReceive} from '../../hooks/useReceive';
4
4
  import type {Tree} from './';
5
5
  import type {Node, DataItem} from './useNodes';
6
+ import { isEqualArray } from '../utils';
6
7
 
7
8
  export function useChecked(getNodes: () => Node<Key>[]) {
8
9
  const instance = useInstance() as Tree;
@@ -38,9 +39,7 @@ export function useChecked(getNodes: () => Node<Key>[]) {
38
39
  }
39
40
 
40
41
  node.checked = checked;
41
- if (checked) {
42
- node.indeterminate = false;
43
- }
42
+ node.indeterminate = false;
44
43
 
45
44
  if (shouldUpdateCheckedKeys) {
46
45
  updateCheckedKeys(node);
@@ -56,6 +55,12 @@ export function useChecked(getNodes: () => Node<Key>[]) {
56
55
  needRecheckNodes.forEach(node => {
57
56
  updateUpward(node);
58
57
  });
58
+
59
+ const oldCheckedKeys = instance.get('checkedKeys');
60
+ const newCheckedKeys = Array.from(checkedKeys);
61
+ if (!isEqualArray(oldCheckedKeys, newCheckedKeys)) {
62
+ instance.set('checkedKeys', newCheckedKeys);
63
+ }
59
64
  }
60
65
 
61
66
  function updateCheckedKeys(node: Node<Key>) {
@@ -67,7 +72,7 @@ export function useChecked(getNodes: () => Node<Key>[]) {
67
72
  }
68
73
 
69
74
  function toggle(node: Node<Key>) {
70
- const uncorrelated = instance.get('uncorrelated');
75
+ // const uncorrelated = instance.get('uncorrelated');
71
76
  updateDownward(node, !node.checked);
72
77
  updateUpward(node.parent);
73
78
 
@@ -110,4 +110,51 @@ describe('TreeSelect', () => {
110
110
  await wait(500);
111
111
  expect(element.querySelector('.k-form-error')).not.to.be.null;
112
112
  });
113
+
114
+ it('should handle parent node correctly when all children are checked initially', async () => {
115
+ const [instance, element] = mount(CheckboxDemo);
116
+
117
+ // init values
118
+ instance.set('values', ['2.1.1', '2.1.2']);
119
+ await wait();
120
+
121
+ // because 2.2 is disabled, so it will select the outermost parent node 2
122
+ expect(instance.get('values')).to.eql(['2']);
123
+
124
+ element.click();
125
+ await wait();
126
+ const dropdown = getElement('.k-tree-select-menu')!;
127
+ // verify parent node is selected
128
+ const parentCheckboxes = dropdown.querySelectorAll('.k-checkbox');
129
+ expect(parentCheckboxes[3].classList.contains('k-checked')).to.be.true; // First floor-2
130
+ expect(parentCheckboxes[4].classList.contains('k-checked')).to.be.true; // Second floor-2.1
131
+ });
132
+
133
+ it('should clear parent node state when setting values to empty array', async () => {
134
+ const [instance, element] = mount(CheckboxDemo);
135
+
136
+ // select a parent node first
137
+ instance.set('values', ['2.1.1']);
138
+ await wait();
139
+
140
+ element.click();
141
+ await wait();
142
+ let dropdown = getElement('.k-tree-select-menu')!;
143
+ // verify related nodes are selected
144
+ const checkboxes = dropdown.querySelectorAll('.k-checkbox');
145
+ expect(checkboxes[3].classList.contains('k-indeterminate')).to.be.true; // First floor-2
146
+ expect(checkboxes[4].classList.contains('k-indeterminate')).to.be.true; // Second floor-2.1
147
+ expect(checkboxes[5].classList.contains('k-checkbox')).to.be.true; // Third floor-2.1.1
148
+ // expect(checkboxes[6].classList.contains('k-checked')).to.be.true; // Third floor-2.1.2
149
+
150
+ // set to empty array
151
+ instance.set('values', []);
152
+ await wait();
153
+
154
+ // verify all nodes are cleared selected state
155
+ checkboxes.forEach(checkbox => {
156
+ expect(checkbox.classList.contains('k-checked')).to.be.false;
157
+ expect(checkbox.classList.contains('k-indeterminate')).to.be.false;
158
+ });
159
+ });
113
160
  });
@@ -1,9 +1,10 @@
1
- import {useInstance, Key, createRef} from 'intact';
1
+ import {useInstance, Key, createRef, nextTick} from 'intact';
2
2
  import type {TreeSelect} from './';
3
3
  import {useState} from '../../hooks/useState';
4
4
  import {isNullOrUndefined} from 'intact-shared';
5
5
  import type {Tree} from '../tree';
6
6
  import type {Node} from '../tree/useNodes';
7
+ import { isEqualArray } from '../utils';
7
8
 
8
9
  export function useValue() {
9
10
  const instance = useInstance() as TreeSelect<Key, boolean, boolean>;
@@ -23,10 +24,12 @@ export function useValue() {
23
24
  checkedKeys.set(v as Key[]);
24
25
  }
25
26
 
26
- function onChangeCheckedKeys() {
27
+ function onChangeCheckedKeys(allKeys: Key[]) {
28
+ checkedKeys.set(allKeys);
27
29
  const keys = getAllCheckedKeys();
28
- instance.set('value', keys);
29
- checkedKeys.set(keys);
30
+ if (!isEqualArray(keys, instance.get('value'))) {
31
+ instance.set('value', keys);
32
+ }
30
33
  }
31
34
 
32
35
  function getAllCheckedKeys() {
@@ -13,6 +13,7 @@ describe('VirtualList', () => {
13
13
 
14
14
  // check basic structure
15
15
  const container = element.querySelector('.k-virtual-container')!;
16
+ await wait();
16
17
  expect(container.outerHTML).to.matchSnapshot();
17
18
 
18
19
  const wrapper = element.querySelector('.k-virtual-wrapper')!;
@@ -259,5 +260,165 @@ describe('VirtualList', () => {
259
260
  const isAtBottom = Math.abs((containerRect.bottom - lastItemRect.bottom)) <= 1;
260
261
  expect(isAtBottom).to.be.true;
261
262
  });
263
+
264
+ it('should handle async data correctly', async () => {
265
+ class AsyncDemo extends Component<{list: number[]}> {
266
+ static template = `
267
+ const VirtualList = this.VirtualList;
268
+ <VirtualList style="height: 300px">
269
+ <div v-for={this.get('list')} key={$value}>Item {$value}</div>
270
+ </VirtualList>
271
+ `;
272
+ static defaults() {
273
+ return {
274
+ list: []
275
+ }
276
+ }
277
+ private VirtualList = VirtualList;
278
+ }
279
+
280
+ const [instance] = mount(AsyncDemo);
281
+ await wait();
282
+
283
+ const container = getElement('.k-virtual-container')!;
284
+ const wrapper = getElement('.k-virtual-wrapper')!;
285
+ const phantom = getElement('.k-virtual-phantom')!;
286
+
287
+ // check initial state
288
+ expect(wrapper.children.length).to.equal(0);
289
+ expect(phantom.style.height).to.equal('0px');
290
+
291
+ // simulate async data loading
292
+ instance.set('list', Array.from({length: 100}, (_, i) => i));
293
+ await wait(50);
294
+
295
+ expect(wrapper.children.length).to.be.equal(23);
296
+ expect(phantom.style.height).to.be.equal('1800px');
297
+
298
+ // check render content
299
+ expect(wrapper.firstElementChild!.textContent).to.equal('Item 0');
300
+ });
301
+
302
+ it('should handle empty data and re-adding data correctly', async () => {
303
+ class EmptyDemo extends Component<{list: number[]}> {
304
+ static template = `
305
+ const VirtualList = this.VirtualList;
306
+ <VirtualList style="height: 300px">
307
+ <div v-for={this.get('list')} key={$value}>Item {$value}</div>
308
+ </VirtualList>
309
+ `;
310
+ static defaults() {
311
+ return {
312
+ list: Array.from({length: 100}, (_, i) => i)
313
+ }
314
+ }
315
+ private VirtualList = VirtualList;
316
+ }
317
+
318
+ const [instance] = mount(EmptyDemo);
319
+ await wait();
320
+
321
+ const container = getElement('.k-virtual-container')!;
322
+ const wrapper = getElement('.k-virtual-wrapper')!;
323
+ const phantom = getElement('.k-virtual-phantom')!;
324
+
325
+ // record initial state
326
+ const initialHeight = phantom.style.height;
327
+ const initialChildrenCount = wrapper.children.length;
328
+
329
+ // clear data
330
+ instance.set('list', []);
331
+ await wait(50);
332
+
333
+ // check empty state
334
+ expect(wrapper.children.length).to.equal(0);
335
+ expect(phantom.style.height).to.equal('0px');
336
+
337
+ // re-add data
338
+ instance.set('list', Array.from({length: 50}, (_, i) => i));
339
+ await wait(50);
340
+
341
+ // check re-add data state
342
+ expect(wrapper.children.length).to.be.equal(23);
343
+ expect(parseInt(phantom.style.height)).to.be.equal(900);
344
+ expect(wrapper.firstElementChild!.textContent).to.equal('Item 0');
345
+
346
+ // 滚动测试
347
+ // container.scrollTop = 100;
348
+ // await wait(50);
349
+
350
+ // // 检查滚动后的渲染是否正确
351
+ // expect(wrapper.firstElementChild!.textContent).to.not.equal('Item 0');
352
+ });
353
+
354
+ it('should handle extreme height differences correctly', async () => {
355
+ class ExtremeHeightDemo extends Component<{list: number[]}> {
356
+ static template = `
357
+ const VirtualList = this.VirtualList;
358
+ <VirtualList style="height: 300px">
359
+ <div v-for={this.get('list')}
360
+ key={$value}
361
+ style={$value < 5 ? 'height: 100px' : 'height: 30px'}
362
+ >
363
+ Item {$value}
364
+ </div>
365
+ </VirtualList>
366
+ `;
367
+ static defaults() {
368
+ return {
369
+ list: Array.from({length: 100}, (_, i) => i)
370
+ }
371
+ }
372
+ private VirtualList = VirtualList;
373
+ }
374
+
375
+ const [instance] = mount(ExtremeHeightDemo);
376
+ await wait();
377
+
378
+ const container = getElement('.k-virtual-container')!;
379
+ const wrapper = getElement('.k-virtual-wrapper')!;
380
+ const phantom = getElement('.k-virtual-phantom')!;
381
+
382
+ const initialItems = wrapper.children;
383
+ const initialLength = initialItems.length;
384
+
385
+ // check first 5 elements height
386
+ const firstItem = initialItems[0] as HTMLElement;
387
+ expect(firstItem.offsetHeight).to.equal(100);
388
+
389
+ // record initial average height (calculate by total height and render count)
390
+ const initialTotalHeight = parseInt(phantom.style.height);
391
+ const initialAvgHeight = initialTotalHeight / 100;
392
+ expect(initialAvgHeight).to.be.greaterThan(30);
393
+
394
+ // scroll to small height area
395
+ container.scrollTop = 800;
396
+ await wait(50);
397
+
398
+ // check new render elements
399
+ const newItems = wrapper.children;
400
+ const newLength = newItems.length;
401
+ const firstNewItem = newItems[0] as HTMLElement;
402
+
403
+ // check
404
+ // new render elements should be 30px height
405
+ expect(firstNewItem.offsetHeight).to.equal(30);
406
+
407
+ // because average height is smaller, render elements count should be more
408
+ expect(newLength).to.be.greaterThan(initialLength);
409
+
410
+ // check scroll position is correct
411
+ const firstVisibleIndex = parseInt(firstNewItem.textContent!.replace('Item ', ''));
412
+ expect(firstVisibleIndex).to.be.greaterThan(5);
413
+
414
+ container.scrollTop = 1200;
415
+ await wait();
416
+
417
+ const finalItems = wrapper.children;
418
+ const finalFirstItem = finalItems[0] as HTMLElement;
419
+ expect(finalFirstItem.offsetHeight).to.equal(30);
420
+ expect(finalItems.length).to.equal(newLength); // render count should be stable
421
+ });
262
422
  });
263
423
 
424
+
@@ -8,11 +8,12 @@ const rows = this.rows;
8
8
  <VirtualRowsContext.Consumer>
9
9
  {({ notifyRows, startIndex, length, disabled }) => {
10
10
  if (!disabled) {
11
- children = rows.value.slice(startIndex, startIndex + length);
12
11
  notifyRows(rows.value);
12
+ children = rows.value.slice(startIndex, startIndex + length);
13
13
  }
14
14
 
15
15
  if (isNullOrUndefined(tagName)) {
16
+ if (!children.length) return;
16
17
  return createFragment(children, 8 /* ChildrenTypes.HasKeyedChildren */);
17
18
  }
18
19
  return createVNode(tagName, null, children);
@@ -1,4 +1,4 @@
1
- import { useInstance, VNode, createRef, createFragment } from 'intact';
1
+ import { useInstance, VNode, createRef, createFragment, Children, isText } from 'intact';
2
2
  import { VirtualListRows } from './rows';
3
3
 
4
4
  export function useRows() {
@@ -12,11 +12,25 @@ export function useRows() {
12
12
  // convert to array if it has only one child
13
13
  const childrenType = vNode.childrenType;
14
14
  if (childrenType & 2 /* ChildrenTypes.HasVNodeChildren */) {
15
- rows.value = [vNode.children as unknown as VNode];
15
+ const children = vNode.children as unknown as VNode;
16
+ if (isText(children)) {
17
+ // ignore void and text vnode
18
+ rows.value = [];
19
+ } else {
20
+ rows.value = [children];
21
+ }
16
22
  } else if (childrenType & 1 /* ChildrenTypes.HasInvalidChildren */) {
17
23
  rows.value = [];
18
24
  } else {
19
25
  rows.value = vNode.children as VNode[];
26
+
27
+ if (process.env.NODE_ENV !== 'production') {
28
+ rows.value.forEach(row => {
29
+ if (isText(row)) {
30
+ console.warn('VirtualList: Text node can not been used as children.');
31
+ }
32
+ });
33
+ }
20
34
  }
21
35
  });
22
36
 
@@ -22,46 +22,51 @@ export function useVirtualRows() {
22
22
  const length = useState(MIN_LENGTH);
23
23
 
24
24
  let calculatedHeight = 0;
25
- let rowAvgHeight = 0;
25
+ let rowAvgHeight = 30;
26
26
  let rows: VNode[] = [];
27
27
  function notifyRows(_rows: VNode[]) {
28
+ if (_rows === rows) return;
29
+
28
30
  const oldRows = rows;
29
31
  const oldLength = rows.length;
30
32
  rows = _rows;
33
+
31
34
  // diff oldRows, newRows
32
- const newKeys = new Set(_rows.map(row => row.key));
33
-
34
- for (let i = 0; i < oldLength; i++) {
35
- const oldKey = oldRows[i].key!;
36
- if (!newKeys.has(oldKey)) {
37
- const height = rowsHeightMap.get(oldKey);
38
- if (!isNullOrUndefined(height)) {
39
- calculatedHeight -= height;
40
- rowsHeightMap.delete(oldKey);
35
+ if (!_rows.length) {
36
+ calculatedHeight = 0;
37
+ rowsHeightMap.clear();
38
+ } else {
39
+ const newKeys = new Set(_rows.map(row => row.key));
40
+
41
+ for (let i = 0; i < oldLength; i++) {
42
+ const oldKey = oldRows[i].key!;
43
+ if (!newKeys.has(oldKey)) {
44
+ const height = rowsHeightMap.get(oldKey);
45
+ if (!isNullOrUndefined(height)) {
46
+ calculatedHeight -= height;
47
+ rowsHeightMap.delete(oldKey);
48
+ }
41
49
  }
42
50
  }
43
51
  }
44
-
45
- // update rowAvgHeight
46
- if (rowsHeightMap.size === 0) {
47
- rowAvgHeight = calculatedHeight = 0;
48
- } else {
49
- rowAvgHeight = calculatedHeight / rowsHeightMap.size;
50
- }
51
-
52
+
52
53
  if (_rows.length < oldLength) {
53
54
  const maxStartIndex = Math.max(0, _rows.length - length.value);
54
55
  if (startIndex.value > maxStartIndex) {
55
56
  startIndex.set(maxStartIndex);
56
- // 重新计算位置
57
57
  nextTick(() => {
58
58
  handleScroll();
59
59
  });
60
60
  }
61
61
  }
62
+
63
+ instance.forceUpdate(() => {
64
+ calculateRowsHeight();
65
+ });
62
66
  }
63
67
 
64
68
  function calculateRowsHeight() {
69
+ if (!rows.length) return;
65
70
  for (
66
71
  let i = startIndex.value;
67
72
  i < startIndex.value + length.value && i < rows.length;
@@ -79,6 +84,10 @@ export function useVirtualRows() {
79
84
 
80
85
  // use the average height to estimate the row height
81
86
  rowAvgHeight = calculatedHeight / rowsHeightMap.size;
87
+
88
+ const containerHeight = containerDom.offsetHeight;
89
+ // calculate the length of rows we should render
90
+ length.set(Math.max(Math.ceil(containerHeight / rowAvgHeight) + BUFFER_SIZE * 2, MIN_LENGTH));
82
91
  }
83
92
 
84
93
  watchState(startIndex, () => {
@@ -89,13 +98,8 @@ export function useVirtualRows() {
89
98
  onMounted(() => {
90
99
  // get contains height
91
100
  containerDom = findDomFromVNode(instance.$lastInput!, true) as HTMLElement;
92
- const containerHeight = containerDom.offsetHeight;
93
101
 
94
102
  calculateRowsHeight();
95
-
96
- // calculate the length of rows we should render
97
- length.set(Math.max(Math.ceil(containerHeight / rowAvgHeight) + BUFFER_SIZE * 2, MIN_LENGTH));
98
-
99
103
  containerDom.addEventListener('scroll', handleScroll);
100
104
  });
101
105
 
@@ -2,6 +2,7 @@ import _Set from "@babel/runtime-corejs3/core-js/set";
2
2
  import _Array$from from "@babel/runtime-corejs3/core-js/array/from";
3
3
  import { useInstance } from 'intact';
4
4
  import { useReceive } from '../../hooks/useReceive';
5
+ import { isEqualArray } from '../utils';
5
6
  export function useChecked(getNodes) {
6
7
  var instance = useInstance();
7
8
  var checkedKeys = new _Set();
@@ -32,9 +33,7 @@ export function useChecked(getNodes) {
32
33
  }
33
34
  }
34
35
  node.checked = checked;
35
- if (checked) {
36
- node.indeterminate = false;
37
- }
36
+ node.indeterminate = false;
38
37
  if (shouldUpdateCheckedKeys) {
39
38
  updateCheckedKeys(node);
40
39
  }
@@ -47,6 +46,11 @@ export function useChecked(getNodes) {
47
46
  needRecheckNodes.forEach(function (node) {
48
47
  updateUpward(node);
49
48
  });
49
+ var oldCheckedKeys = instance.get('checkedKeys');
50
+ var newCheckedKeys = _Array$from(checkedKeys);
51
+ if (!isEqualArray(oldCheckedKeys, newCheckedKeys)) {
52
+ instance.set('checkedKeys', newCheckedKeys);
53
+ }
50
54
  }
51
55
  function updateCheckedKeys(node) {
52
56
  if (node.checked) {
@@ -56,7 +60,7 @@ export function useChecked(getNodes) {
56
60
  }
57
61
  }
58
62
  function toggle(node) {
59
- var uncorrelated = instance.get('uncorrelated');
63
+ // const uncorrelated = instance.get('uncorrelated');
60
64
  updateDownward(node, !node.checked);
61
65
  updateUpward(node.parent);
62
66
  instance.set('checkedKeys', _Array$from(checkedKeys));
@@ -168,4 +168,66 @@ describe('TreeSelect', function () {
168
168
  }
169
169
  }, _callee5);
170
170
  })));
171
+ it('should handle parent node correctly when all children are checked initially', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee6() {
172
+ var _mount6, instance, element, dropdown, parentCheckboxes;
173
+ return _regeneratorRuntime.wrap(function _callee6$(_context7) {
174
+ while (1) switch (_context7.prev = _context7.next) {
175
+ case 0:
176
+ _mount6 = mount(CheckboxDemo), instance = _mount6[0], element = _mount6[1]; // init values
177
+ instance.set('values', ['2.1.1', '2.1.2']);
178
+ _context7.next = 4;
179
+ return wait();
180
+ case 4:
181
+ // because 2.2 is disabled, so it will select the outermost parent node 2
182
+ expect(instance.get('values')).to.eql(['2']);
183
+ element.click();
184
+ _context7.next = 8;
185
+ return wait();
186
+ case 8:
187
+ dropdown = getElement('.k-tree-select-menu'); // verify parent node is selected
188
+ parentCheckboxes = dropdown.querySelectorAll('.k-checkbox');
189
+ expect(parentCheckboxes[3].classList.contains('k-checked')).to.be.true; // First floor-2
190
+ expect(parentCheckboxes[4].classList.contains('k-checked')).to.be.true; // Second floor-2.1
191
+ case 12:
192
+ case "end":
193
+ return _context7.stop();
194
+ }
195
+ }, _callee6);
196
+ })));
197
+ it('should clear parent node state when setting values to empty array', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee7() {
198
+ var _mount7, instance, element, dropdown, checkboxes;
199
+ return _regeneratorRuntime.wrap(function _callee7$(_context8) {
200
+ while (1) switch (_context8.prev = _context8.next) {
201
+ case 0:
202
+ _mount7 = mount(CheckboxDemo), instance = _mount7[0], element = _mount7[1]; // select a parent node first
203
+ instance.set('values', ['2.1.1']);
204
+ _context8.next = 4;
205
+ return wait();
206
+ case 4:
207
+ element.click();
208
+ _context8.next = 7;
209
+ return wait();
210
+ case 7:
211
+ dropdown = getElement('.k-tree-select-menu'); // verify related nodes are selected
212
+ checkboxes = dropdown.querySelectorAll('.k-checkbox');
213
+ expect(checkboxes[3].classList.contains('k-indeterminate')).to.be.true; // First floor-2
214
+ expect(checkboxes[4].classList.contains('k-indeterminate')).to.be.true; // Second floor-2.1
215
+ expect(checkboxes[5].classList.contains('k-checkbox')).to.be.true; // Third floor-2.1.1
216
+ // expect(checkboxes[6].classList.contains('k-checked')).to.be.true; // Third floor-2.1.2
217
+ // set to empty array
218
+ instance.set('values', []);
219
+ _context8.next = 15;
220
+ return wait();
221
+ case 15:
222
+ // verify all nodes are cleared selected state
223
+ checkboxes.forEach(function (checkbox) {
224
+ expect(checkbox.classList.contains('k-checked')).to.be.false;
225
+ expect(checkbox.classList.contains('k-indeterminate')).to.be.false;
226
+ });
227
+ case 16:
228
+ case "end":
229
+ return _context8.stop();
230
+ }
231
+ }, _callee7);
232
+ })));
171
233
  });
@@ -4,6 +4,6 @@ export declare function useValue(): {
4
4
  selectedKeys: import("../../hooks/useState").State<Key[]>;
5
5
  checkedKeys: import("../../hooks/useState").State<Key[]>;
6
6
  treeRef: import("intact").RefObject<Tree<Key>>;
7
- onChangeCheckedKeys: () => void;
7
+ onChangeCheckedKeys: (allKeys: Key[]) => void;
8
8
  onChangeSelectedKeys: (keys: Key[]) => void;
9
9
  };
@@ -1,6 +1,7 @@
1
1
  import { useInstance, createRef } from 'intact';
2
2
  import { useState } from '../../hooks/useState';
3
3
  import { isNullOrUndefined } from 'intact-shared';
4
+ import { isEqualArray } from '../utils';
4
5
  export function useValue() {
5
6
  var instance = useInstance();
6
7
  var selectedKeys = useState([]);
@@ -16,10 +17,12 @@ export function useValue() {
16
17
  selectedKeys.set(v);
17
18
  checkedKeys.set(v);
18
19
  }
19
- function onChangeCheckedKeys() {
20
+ function onChangeCheckedKeys(allKeys) {
21
+ checkedKeys.set(allKeys);
20
22
  var keys = getAllCheckedKeys();
21
- instance.set('value', keys);
22
- checkedKeys.set(keys);
23
+ if (!isEqualArray(keys, instance.get('value'))) {
24
+ instance.set('value', keys);
25
+ }
23
26
  }
24
27
  function getAllCheckedKeys() {
25
28
  // if the parent has been checked, ignore the children
@@ -21,13 +21,16 @@ describe('VirtualList', function () {
21
21
  case 0:
22
22
  _mount = mount(BasicDemo), instance = _mount[0], element = _mount[1]; // check basic structure
23
23
  container = element.querySelector('.k-virtual-container');
24
+ _context.next = 4;
25
+ return wait();
26
+ case 4:
24
27
  expect(container.outerHTML).to.matchSnapshot();
25
28
  wrapper = element.querySelector('.k-virtual-wrapper');
26
29
  expect(wrapper).to.exist;
27
30
  // check render items is less than total
28
31
  items = wrapper.children;
29
32
  expect(items.length).to.be.lessThan(100);
30
- case 7:
33
+ case 9:
31
34
  case "end":
32
35
  return _context.stop();
33
36
  }
@@ -369,4 +372,200 @@ describe('VirtualList', function () {
369
372
  }
370
373
  }, _callee8);
371
374
  })));
375
+ it('should handle async data correctly', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee9() {
376
+ var AsyncDemo, _mount9, instance, container, wrapper, phantom;
377
+ return _regeneratorRuntime.wrap(function _callee9$(_context16) {
378
+ while (1) switch (_context16.prev = _context16.next) {
379
+ case 0:
380
+ AsyncDemo = /*#__PURE__*/function (_Component5) {
381
+ _inheritsLoose(AsyncDemo, _Component5);
382
+ function AsyncDemo() {
383
+ var _context15;
384
+ var _this5;
385
+ for (var _len5 = arguments.length, args = new Array(_len5), _key5 = 0; _key5 < _len5; _key5++) {
386
+ args[_key5] = arguments[_key5];
387
+ }
388
+ _this5 = _Component5.call.apply(_Component5, _concatInstanceProperty(_context15 = [this]).call(_context15, args)) || this;
389
+ _this5.VirtualList = VirtualList;
390
+ return _this5;
391
+ }
392
+ AsyncDemo.defaults = function defaults() {
393
+ return {
394
+ list: []
395
+ };
396
+ };
397
+ return AsyncDemo;
398
+ }(Component);
399
+ AsyncDemo.template = "\n const VirtualList = this.VirtualList;\n <VirtualList style=\"height: 300px\">\n <div v-for={this.get('list')} key={$value}>Item {$value}</div>\n </VirtualList>\n ";
400
+ _mount9 = mount(AsyncDemo), instance = _mount9[0];
401
+ _context16.next = 5;
402
+ return wait();
403
+ case 5:
404
+ container = getElement('.k-virtual-container');
405
+ wrapper = getElement('.k-virtual-wrapper');
406
+ phantom = getElement('.k-virtual-phantom'); // check initial state
407
+ expect(wrapper.children.length).to.equal(0);
408
+ expect(phantom.style.height).to.equal('0px');
409
+ // simulate async data loading
410
+ instance.set('list', _Array$from({
411
+ length: 100
412
+ }, function (_, i) {
413
+ return i;
414
+ }));
415
+ _context16.next = 13;
416
+ return wait(50);
417
+ case 13:
418
+ expect(wrapper.children.length).to.be.equal(23);
419
+ expect(phantom.style.height).to.be.equal('1800px');
420
+ // check render content
421
+ expect(wrapper.firstElementChild.textContent).to.equal('Item 0');
422
+ case 16:
423
+ case "end":
424
+ return _context16.stop();
425
+ }
426
+ }, _callee9);
427
+ })));
428
+ it('should handle empty data and re-adding data correctly', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee10() {
429
+ var EmptyDemo, _mount10, instance, container, wrapper, phantom, initialHeight, initialChildrenCount;
430
+ return _regeneratorRuntime.wrap(function _callee10$(_context18) {
431
+ while (1) switch (_context18.prev = _context18.next) {
432
+ case 0:
433
+ EmptyDemo = /*#__PURE__*/function (_Component6) {
434
+ _inheritsLoose(EmptyDemo, _Component6);
435
+ function EmptyDemo() {
436
+ var _context17;
437
+ var _this6;
438
+ for (var _len6 = arguments.length, args = new Array(_len6), _key6 = 0; _key6 < _len6; _key6++) {
439
+ args[_key6] = arguments[_key6];
440
+ }
441
+ _this6 = _Component6.call.apply(_Component6, _concatInstanceProperty(_context17 = [this]).call(_context17, args)) || this;
442
+ _this6.VirtualList = VirtualList;
443
+ return _this6;
444
+ }
445
+ EmptyDemo.defaults = function defaults() {
446
+ return {
447
+ list: _Array$from({
448
+ length: 100
449
+ }, function (_, i) {
450
+ return i;
451
+ })
452
+ };
453
+ };
454
+ return EmptyDemo;
455
+ }(Component);
456
+ EmptyDemo.template = "\n const VirtualList = this.VirtualList;\n <VirtualList style=\"height: 300px\">\n <div v-for={this.get('list')} key={$value}>Item {$value}</div>\n </VirtualList>\n ";
457
+ _mount10 = mount(EmptyDemo), instance = _mount10[0];
458
+ _context18.next = 5;
459
+ return wait();
460
+ case 5:
461
+ container = getElement('.k-virtual-container');
462
+ wrapper = getElement('.k-virtual-wrapper');
463
+ phantom = getElement('.k-virtual-phantom'); // record initial state
464
+ initialHeight = phantom.style.height;
465
+ initialChildrenCount = wrapper.children.length; // clear data
466
+ instance.set('list', []);
467
+ _context18.next = 13;
468
+ return wait(50);
469
+ case 13:
470
+ // check empty state
471
+ expect(wrapper.children.length).to.equal(0);
472
+ expect(phantom.style.height).to.equal('0px');
473
+ // re-add data
474
+ instance.set('list', _Array$from({
475
+ length: 50
476
+ }, function (_, i) {
477
+ return i;
478
+ }));
479
+ _context18.next = 18;
480
+ return wait(50);
481
+ case 18:
482
+ // check re-add data state
483
+ expect(wrapper.children.length).to.be.equal(23);
484
+ expect(parseInt(phantom.style.height)).to.be.equal(900);
485
+ expect(wrapper.firstElementChild.textContent).to.equal('Item 0');
486
+ // 滚动测试
487
+ // container.scrollTop = 100;
488
+ // await wait(50);
489
+ // // 检查滚动后的渲染是否正确
490
+ // expect(wrapper.firstElementChild!.textContent).to.not.equal('Item 0');
491
+ case 21:
492
+ case "end":
493
+ return _context18.stop();
494
+ }
495
+ }, _callee10);
496
+ })));
497
+ it('should handle extreme height differences correctly', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee11() {
498
+ var ExtremeHeightDemo, _mount11, instance, container, wrapper, phantom, initialItems, initialLength, firstItem, initialTotalHeight, initialAvgHeight, newItems, newLength, firstNewItem, firstVisibleIndex, finalItems, finalFirstItem;
499
+ return _regeneratorRuntime.wrap(function _callee11$(_context20) {
500
+ while (1) switch (_context20.prev = _context20.next) {
501
+ case 0:
502
+ ExtremeHeightDemo = /*#__PURE__*/function (_Component7) {
503
+ _inheritsLoose(ExtremeHeightDemo, _Component7);
504
+ function ExtremeHeightDemo() {
505
+ var _context19;
506
+ var _this7;
507
+ for (var _len7 = arguments.length, args = new Array(_len7), _key7 = 0; _key7 < _len7; _key7++) {
508
+ args[_key7] = arguments[_key7];
509
+ }
510
+ _this7 = _Component7.call.apply(_Component7, _concatInstanceProperty(_context19 = [this]).call(_context19, args)) || this;
511
+ _this7.VirtualList = VirtualList;
512
+ return _this7;
513
+ }
514
+ ExtremeHeightDemo.defaults = function defaults() {
515
+ return {
516
+ list: _Array$from({
517
+ length: 100
518
+ }, function (_, i) {
519
+ return i;
520
+ })
521
+ };
522
+ };
523
+ return ExtremeHeightDemo;
524
+ }(Component);
525
+ ExtremeHeightDemo.template = "\n const VirtualList = this.VirtualList;\n <VirtualList style=\"height: 300px\">\n <div v-for={this.get('list')} \n key={$value} \n style={$value < 5 ? 'height: 100px' : 'height: 30px'}\n >\n Item {$value}\n </div>\n </VirtualList>\n ";
526
+ _mount11 = mount(ExtremeHeightDemo), instance = _mount11[0];
527
+ _context20.next = 5;
528
+ return wait();
529
+ case 5:
530
+ container = getElement('.k-virtual-container');
531
+ wrapper = getElement('.k-virtual-wrapper');
532
+ phantom = getElement('.k-virtual-phantom');
533
+ initialItems = wrapper.children;
534
+ initialLength = initialItems.length; // check first 5 elements height
535
+ firstItem = initialItems[0];
536
+ expect(firstItem.offsetHeight).to.equal(100);
537
+ // record initial average height (calculate by total height and render count)
538
+ initialTotalHeight = parseInt(phantom.style.height);
539
+ initialAvgHeight = initialTotalHeight / 100;
540
+ expect(initialAvgHeight).to.be.greaterThan(30);
541
+ // scroll to small height area
542
+ container.scrollTop = 800;
543
+ _context20.next = 18;
544
+ return wait(50);
545
+ case 18:
546
+ // check new render elements
547
+ newItems = wrapper.children;
548
+ newLength = newItems.length;
549
+ firstNewItem = newItems[0]; // check
550
+ // new render elements should be 30px height
551
+ expect(firstNewItem.offsetHeight).to.equal(30);
552
+ // because average height is smaller, render elements count should be more
553
+ expect(newLength).to.be.greaterThan(initialLength);
554
+ // check scroll position is correct
555
+ firstVisibleIndex = parseInt(firstNewItem.textContent.replace('Item ', ''));
556
+ expect(firstVisibleIndex).to.be.greaterThan(5);
557
+ container.scrollTop = 1200;
558
+ _context20.next = 28;
559
+ return wait();
560
+ case 28:
561
+ finalItems = wrapper.children;
562
+ finalFirstItem = finalItems[0];
563
+ expect(finalFirstItem.offsetHeight).to.equal(30);
564
+ expect(finalItems.length).to.equal(newLength); // render count should be stable
565
+ case 32:
566
+ case "end":
567
+ return _context20.stop();
568
+ }
569
+ }, _callee11);
570
+ })));
372
571
  });
@@ -19,10 +19,11 @@ export default function ($props, $blocks, $__proto__) {
19
19
  disabled = _ref.disabled;
20
20
  if (!disabled) {
21
21
  var _context;
22
- _children = _sliceInstanceProperty(_context = rows.value).call(_context, startIndex, startIndex + length);
23
22
  notifyRows(rows.value);
23
+ _children = _sliceInstanceProperty(_context = rows.value).call(_context, startIndex, startIndex + length);
24
24
  }
25
25
  if (isNullOrUndefined(tagName)) {
26
+ if (!_children.length) return;
26
27
  return createFragment(_children, 8 /* ChildrenTypes.HasKeyedChildren */);
27
28
  }
28
29
  return createVNode(tagName, null, _children);
@@ -1,4 +1,4 @@
1
- import { useInstance, createRef, createFragment } from 'intact';
1
+ import { useInstance, createRef, createFragment, isText } from 'intact';
2
2
  export function useRows() {
3
3
  var instance = useInstance();
4
4
  var rows = createRef([]);
@@ -8,11 +8,24 @@ export function useRows() {
8
8
  // convert to array if it has only one child
9
9
  var childrenType = vNode.childrenType;
10
10
  if (childrenType & 2 /* ChildrenTypes.HasVNodeChildren */) {
11
- rows.value = [vNode.children];
11
+ var _children = vNode.children;
12
+ if (isText(_children)) {
13
+ // ignore void and text vnode
14
+ rows.value = [];
15
+ } else {
16
+ rows.value = [_children];
17
+ }
12
18
  } else if (childrenType & 1 /* ChildrenTypes.HasInvalidChildren */) {
13
19
  rows.value = [];
14
20
  } else {
15
21
  rows.value = vNode.children;
22
+ if (process.env.NODE_ENV !== 'production') {
23
+ rows.value.forEach(function (row) {
24
+ if (isText(row)) {
25
+ console.warn('VirtualList: Text node can not been used as children.');
26
+ }
27
+ });
28
+ }
16
29
  }
17
30
  });
18
31
  return rows;
@@ -14,44 +14,47 @@ export function useVirtualRows() {
14
14
  var startIndex = useState(0);
15
15
  var length = useState(MIN_LENGTH);
16
16
  var calculatedHeight = 0;
17
- var rowAvgHeight = 0;
17
+ var rowAvgHeight = 30;
18
18
  var rows = [];
19
19
  function notifyRows(_rows) {
20
+ if (_rows === rows) return;
20
21
  var oldRows = rows;
21
22
  var oldLength = rows.length;
22
23
  rows = _rows;
23
24
  // diff oldRows, newRows
24
- var newKeys = new _Set(_mapInstanceProperty(_rows).call(_rows, function (row) {
25
- return row.key;
26
- }));
27
- for (var i = 0; i < oldLength; i++) {
28
- var oldKey = oldRows[i].key;
29
- if (!newKeys.has(oldKey)) {
30
- var height = rowsHeightMap.get(oldKey);
31
- if (!isNullOrUndefined(height)) {
32
- calculatedHeight -= height;
33
- rowsHeightMap.delete(oldKey);
25
+ if (!_rows.length) {
26
+ calculatedHeight = 0;
27
+ rowsHeightMap.clear();
28
+ } else {
29
+ var newKeys = new _Set(_mapInstanceProperty(_rows).call(_rows, function (row) {
30
+ return row.key;
31
+ }));
32
+ for (var i = 0; i < oldLength; i++) {
33
+ var oldKey = oldRows[i].key;
34
+ if (!newKeys.has(oldKey)) {
35
+ var height = rowsHeightMap.get(oldKey);
36
+ if (!isNullOrUndefined(height)) {
37
+ calculatedHeight -= height;
38
+ rowsHeightMap.delete(oldKey);
39
+ }
34
40
  }
35
41
  }
36
42
  }
37
- // update rowAvgHeight
38
- if (rowsHeightMap.size === 0) {
39
- rowAvgHeight = calculatedHeight = 0;
40
- } else {
41
- rowAvgHeight = calculatedHeight / rowsHeightMap.size;
42
- }
43
43
  if (_rows.length < oldLength) {
44
44
  var maxStartIndex = Math.max(0, _rows.length - length.value);
45
45
  if (startIndex.value > maxStartIndex) {
46
46
  startIndex.set(maxStartIndex);
47
- // 重新计算位置
48
47
  nextTick(function () {
49
48
  handleScroll();
50
49
  });
51
50
  }
52
51
  }
52
+ instance.forceUpdate(function () {
53
+ calculateRowsHeight();
54
+ });
53
55
  }
54
56
  function calculateRowsHeight() {
57
+ if (!rows.length) return;
55
58
  for (var i = startIndex.value; i < startIndex.value + length.value && i < rows.length; i++) {
56
59
  var row = rows[i];
57
60
  var key = row.key;
@@ -64,6 +67,9 @@ export function useVirtualRows() {
64
67
  }
65
68
  // use the average height to estimate the row height
66
69
  rowAvgHeight = calculatedHeight / rowsHeightMap.size;
70
+ var containerHeight = containerDom.offsetHeight;
71
+ // calculate the length of rows we should render
72
+ length.set(Math.max(Math.ceil(containerHeight / rowAvgHeight) + BUFFER_SIZE * 2, MIN_LENGTH));
67
73
  }
68
74
  watchState(startIndex, function () {
69
75
  nextTick(calculateRowsHeight);
@@ -72,10 +78,7 @@ export function useVirtualRows() {
72
78
  onMounted(function () {
73
79
  // get contains height
74
80
  containerDom = findDomFromVNode(instance.$lastInput, true);
75
- var containerHeight = containerDom.offsetHeight;
76
81
  calculateRowsHeight();
77
- // calculate the length of rows we should render
78
- length.set(Math.max(Math.ceil(containerHeight / rowAvgHeight) + BUFFER_SIZE * 2, MIN_LENGTH));
79
82
  containerDom.addEventListener('scroll', handleScroll);
80
83
  });
81
84
  onUnmounted(function () {
package/es/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * @king-design v3.5.1-beta.0
2
+ * @king-design v3.5.1
3
3
  *
4
4
  * Copyright (c) Kingsoft Cloud
5
5
  * Released under the MIT License
@@ -65,4 +65,4 @@ export * from './components/upload';
65
65
  export * from './components/view';
66
66
  export * from './components/virtualList';
67
67
  export * from './components/wave';
68
- export declare const version = "3.5.1-beta.0";
68
+ export declare const version = "3.5.1";
package/es/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * @king-design v3.5.1-beta.0
2
+ * @king-design v3.5.1
3
3
  *
4
4
  * Copyright (c) Kingsoft Cloud
5
5
  * Released under the MIT License
@@ -66,5 +66,5 @@ export * from './components/upload';
66
66
  export * from './components/view';
67
67
  export * from './components/virtualList';
68
68
  export * from './components/wave';
69
- export var version = '3.5.1-beta.0';
69
+ export var version = '3.5.1';
70
70
  /* generate end */
@@ -16,7 +16,7 @@ var default_1 = /*#__PURE__*/function (_Component) {
16
16
  var _proto = default_1.prototype;
17
17
  _proto.init = function init() {
18
18
  var arr = [];
19
- for (var index = 0; index < 50; index++) {
19
+ for (var index = 0; index < 10000; index++) {
20
20
  arr.push({
21
21
  value: index,
22
22
  label: "\u6D4B\u8BD5" + index
@@ -21,7 +21,7 @@ var Demo = /*#__PURE__*/function (_React$Component) {
21
21
  var _proto = Demo.prototype;
22
22
  _proto.componentDidMount = function componentDidMount() {
23
23
  var arr = [];
24
- for (var index = 0; index < 50; index++) {
24
+ for (var index = 0; index < 10000; index++) {
25
25
  arr.push({
26
26
  value: index,
27
27
  label: "\u6D4B\u8BD5" + index
@@ -41,8 +41,7 @@ var Demo = /*#__PURE__*/function (_React$Component) {
41
41
  day: day
42
42
  });
43
43
  },
44
- virtual: true,
45
- filterable: true
44
+ virtual: true
46
45
  }, _mapInstanceProperty(_context2 = this.state.data).call(_context2, function ($value, $key) {
47
46
  return /*#__PURE__*/React.createElement(Option, {
48
47
  value: $value.value
package/index.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * @king-design v3.5.1-beta.0
2
+ * @king-design v3.5.1
3
3
  *
4
4
  * Copyright (c) Kingsoft Cloud
5
5
  * Released under the MIT License
@@ -70,6 +70,6 @@ export * from './components/view';
70
70
  export * from './components/virtualList';
71
71
  export * from './components/wave';
72
72
 
73
- export const version = '3.5.1-beta.0';
73
+ export const version = '3.5.1';
74
74
 
75
75
  /* generate end */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@king-design/intact",
3
- "version": "3.5.1-beta.0",
3
+ "version": "3.5.1",
4
4
  "description": "A component library written in Intact for Intact, Vue, React and Angular",
5
5
  "main": "es/index.js",
6
6
  "engines": {