plain-design 1.0.0-beta.3 → 1.0.0-beta.30

Sign up to get free protection for your applications and to get access to all the features.
Files changed (116) hide show
  1. package/dist/plain-design.commonjs.min.js +2 -2
  2. package/dist/plain-design.min.css +22 -19
  3. package/dist/plain-design.min.js +2 -2
  4. package/dist/report.html +5 -5
  5. package/package.json +6 -6
  6. package/src/packages/build.ts +2 -10
  7. package/src/packages/components/Alert/alert.scss +1 -1
  8. package/src/packages/components/Application/theme/theme.ts +2 -3
  9. package/src/packages/components/ArrowStepGroup/arrow-step.scss +4 -4
  10. package/src/packages/components/AutoTable/use/useTableOption.methods.tsx +2 -0
  11. package/src/packages/components/AutoTable/use/useTableOption.state.tsx +1 -0
  12. package/src/packages/components/AutoTable/utils/TableOption.space.tsx +1 -0
  13. package/src/packages/components/Badge/badge.scss +1 -1
  14. package/src/packages/components/Button/button.scss +1 -1
  15. package/src/packages/components/CarouselGroup/carousel.scss +1 -1
  16. package/src/packages/components/CascadePanel/list/CascadeListPanelItem.tsx +3 -3
  17. package/src/packages/components/CheckboxInner/checkbox-inner.scss +1 -1
  18. package/src/packages/components/ColorPicker/sub/ColorSlider.tsx +8 -5
  19. package/src/packages/components/ColorPicker/sub/ColorSvPanel.tsx +7 -4
  20. package/src/packages/components/DatePicker/date.scss +1 -1
  21. package/src/packages/components/Dialog/dialog.scss +1 -1
  22. package/src/packages/components/Dialog/index.tsx +4 -3
  23. package/src/packages/components/Dialog/useDialogMovable.tsx +7 -4
  24. package/src/packages/components/Dialog/utils/dialog.mouse.ts +4 -2
  25. package/src/packages/components/DropdownOption/index.tsx +3 -3
  26. package/src/packages/components/Image/image.scss +3 -3
  27. package/src/packages/components/Input/useMultipleInput.tsx +5 -2
  28. package/src/packages/components/Input/useTextareaInput.tsx +10 -5
  29. package/src/packages/components/Input/uses/useInputHooks.tsx +11 -11
  30. package/src/packages/components/InputNumber/NumberResize.tsx +6 -2
  31. package/src/packages/components/Layout/index.tsx +31 -0
  32. package/src/packages/components/Layout/layout.scss +227 -0
  33. package/src/packages/components/Layout/layout.utils.ts +3 -0
  34. package/src/packages/components/LayoutSection/index.tsx +67 -0
  35. package/src/packages/components/LayoutSection/useLayoutSectionResizer.tsx +184 -0
  36. package/src/packages/components/LoadingMask/index.tsx +1 -1
  37. package/src/{pages/index/PageThemeUtils.tsx → packages/components/PageThemeUtils/index.tsx} +58 -18
  38. package/src/packages/components/Pagination/pagination.scss +2 -2
  39. package/src/packages/components/Popup/index.tsx +24 -10
  40. package/src/packages/components/ProgressBar/progress-bar.scss +1 -1
  41. package/src/packages/components/Rate/index.tsx +3 -1
  42. package/src/packages/components/Scroll/HorizontalScrollbar.tsx +7 -3
  43. package/src/packages/components/Scroll/VerticalScrollbar.tsx +7 -3
  44. package/src/packages/components/Select/createPublicSelectRender.tsx +1 -1
  45. package/src/packages/components/Slider/slider.scss +1 -1
  46. package/src/packages/components/Slider/useSliderDotDragier.tsx +7 -4
  47. package/src/packages/components/StepGroup/step-group.scss +9 -9
  48. package/src/packages/components/TabGroup/TabsInner.tsx +5 -3
  49. package/src/packages/components/TabGroup/header/horizontal/tabs-header-horizontal.scss +0 -1
  50. package/src/packages/components/TabGroup/index.tsx +5 -1
  51. package/src/packages/components/TabGroup/tabs.scss +3 -0
  52. package/src/packages/components/Table/standard/PlcExpand.tsx +12 -20
  53. package/src/packages/components/Table/table/Table.tsx +10 -3
  54. package/src/packages/components/Table/table/head/useHeadCellResize.ts +8 -3
  55. package/src/packages/components/Table/table/table.scss +1 -0
  56. package/src/packages/components/Table/table/use/useTableDraggier.col.tsx +10 -5
  57. package/src/packages/components/Table/table/use/useTableDraggier.row.tsx +11 -6
  58. package/src/packages/components/Table/table/utils/createTableHooks.ts +3 -1
  59. package/src/packages/components/Table/table/utils/table.utils.ts +6 -1
  60. package/src/packages/components/ThemeEditor/index.tsx +172 -0
  61. package/src/packages/components/ThemeEditor/theme-editor.scss +105 -0
  62. package/src/packages/components/ThemePrimaryColors/index.ts +5 -0
  63. package/src/packages/components/Tree/RenderTreeNode.tsx +6 -2
  64. package/src/packages/components/Tree/index.tsx +3 -3
  65. package/src/packages/components/Tree/tree.scss +42 -9
  66. package/src/packages/components/TreeCore/TreeCore.type.tsx +2 -0
  67. package/src/packages/components/TreeCore/createTreeCore.tsx +5 -1
  68. package/src/packages/components/TreeCore/createTreeDraggier.tsx +70 -56
  69. package/src/packages/components/TreeCore/createTreeMethods.tsx +1 -0
  70. package/src/packages/components/TreeCore/createTreeProps.ts +2 -1
  71. package/src/packages/components/TreeNodeWithMenu/index.tsx +91 -0
  72. package/src/packages/components/TreeNodeWithMenu/tree-node-with-menu.scss +39 -0
  73. package/src/packages/components/TreeNodeWithMenu/treeNodeWithMenu.utils.ts +12 -0
  74. package/src/packages/components/VirtualList/index.tsx +3 -3
  75. package/src/packages/components/VirtualList/useVirtualList.tsx +94 -62
  76. package/src/packages/components/VirtualTable/index.tsx +6 -6
  77. package/src/packages/components/createProvider/index.ts +5 -0
  78. package/src/packages/components/nextPopupId/index.ts +5 -0
  79. package/src/packages/components/useDialog/DialogService.tsx +13 -1
  80. package/src/packages/components/useImage/ImageService.tsx +7 -4
  81. package/src/packages/components/useMessage/Message.tsx +5 -1
  82. package/src/packages/components/useMessage/message.scss +5 -1
  83. package/src/packages/components/useNotice/notice.scss +1 -1
  84. package/src/packages/components/usePopup/PopupItem.tsx +45 -26
  85. package/src/packages/components/usePopup/popup-item.scss +5 -1
  86. package/src/packages/components/usePopup/usePopupManager.tsx +2 -1
  87. package/src/packages/components/usePopup/utils/popup.utils.ts +2 -1
  88. package/src/packages/components/usePopupManager/index.ts +5 -0
  89. package/src/packages/components/usePopupTrigger/index.tsx +5 -0
  90. package/src/packages/components/useReferenceTrigger/index.tsx +5 -0
  91. package/src/packages/components/useTooltip/index.tsx +2 -0
  92. package/src/packages/components/useWatchAutoClear/index.ts +5 -0
  93. package/src/packages/entry.tsx +39 -0
  94. package/src/packages/uses/useEdit.ts +5 -1
  95. package/src/packages/uses/useStyle.tsx +10 -2
  96. package/src/packages/utils/ClientZoom.ts +24 -2
  97. package/src/packages/utils/useMove.tsx +10 -4
  98. package/src/pages/index/App.tsx +3 -2
  99. package/src/pages/index/Demo/DemoRow.scss +9 -6
  100. package/src/pages/index/Demo/DemoRowController.tsx +2 -2
  101. package/src/pages/index/app.scss +5 -0
  102. package/src/pages/index/components/AutoTable/AutoHeightAutoRow.tsx +10 -5
  103. package/src/pages/index/components/normal/DemoDropdown.tsx +1 -1
  104. package/src/pages/index/components/normal/DemoLayout.tsx +144 -0
  105. package/src/pages/index/components/normal/DemoLoading.tsx +3 -0
  106. package/src/pages/index/components/normal/DemoTab.tsx +6 -6
  107. package/src/pages/index/components/normal/DemoTree.tsx +86 -2
  108. package/src/pages/index/components/normal/DemoVirtualList.tsx +9 -9
  109. package/src/pages/index/components/service/DemoDialogService.tsx +1 -0
  110. package/src/pages/index/components/service/DemoPopupService.tsx +6 -3
  111. package/src/pages/index/components/table/DemoTableExpand.tsx +41 -19
  112. package/src/pages/index/components/table/DemoVirtualTable.tsx +4 -4
  113. package/src/pages/index/home/AppHead.tsx +6 -93
  114. package/src/pages/index/home/menus.tsx +1 -0
  115. package/src/pages/index/home/plain-design.png +0 -0
  116. package/src/pages/index/main.tsx +0 -4
@@ -14,6 +14,8 @@ import {enableUserSelect} from "plain-utils/dom/enableUserSelect";
14
14
  import {onParentElementsScroll} from "../../utils/onParentElementsScroll";
15
15
  import {addClass} from "plain-utils/dom/addClass";
16
16
  import {iTreeHooks} from "./createTreeHooks";
17
+ import {ClientZoom} from "../ClientZoom";
18
+ import {delay} from "plain-utils/utils/delay";
17
19
 
18
20
  export function createTreeDraggier(
19
21
  {
@@ -108,6 +110,8 @@ export function createTreeDraggier(
108
110
  */
109
111
  const shadowTreeNode = (() => {
110
112
 
113
+ const reactiveState = reactive({ dragging: false, });
114
+
111
115
  let staticState = null as null | {
112
116
  startNode: iTreeNode, // 拖拽开始的时候节点
113
117
  rowElement: HTMLElement, // 拖拽开始的row对象
@@ -222,71 +226,78 @@ export function createTreeDraggier(
222
226
  if (!rowElement) {
223
227
  throw new Error(`tree draggier: can't find row element with selector: .${rowClass}`);
224
228
  }
229
+ const { clientX, clientY } = ClientZoom.getClientPosition(e);
225
230
  staticState = {
226
231
  startNode: node,
227
232
  rowElement,
228
233
  start: {
229
- x: e.clientX,
230
- y: e.clientY,
234
+ x: clientX,
235
+ y: clientY,
231
236
  },
232
237
  data: null,
233
238
  };
239
+ reactiveState.dragging = true;
234
240
  /**
235
241
  * 拖拽结束之后,处理拖拽数据
236
242
  * @author 韦胜健
237
243
  * @date 2023/6/1 21:13
238
244
  */
239
245
  draggierEffects.push(async () => {
240
- if (!!staticState?.data) {
241
- const { startNode, } = staticState;
242
- const { moveNode, dropType, droppable, isHideDraggier } = staticState.data.move;
243
- if (!droppable || isHideDraggier) {
244
- return;
245
- }
246
- const newParentNode = moveNode.parent;
247
- const oldParentNode = startNode.parent;
248
- const newSiblings = dropType === eTreeDropType.inner ? methods.getReactiveChildrenData(moveNode) : methods.getParentChildrenData(moveNode);
249
- const newSiblingsBak = [...newSiblings];
250
- const oldSiblings = methods.getParentChildrenData(startNode);
251
- const oldSiblingsBak = [...oldSiblings];
252
-
253
- methods.removeSelf(startNode!);
254
- switch (dropType) {
255
- case eTreeDropType.prev:
256
- methods.previousSibling(moveNode!, startNode!);
257
- break;
258
- case eTreeDropType.inner:
259
- methods.unshiftChild(moveNode!, startNode!);
260
- await methods.expand(moveNode!);
261
- break;
262
- case eTreeDropType.next:
263
- methods.nextSibling(moveNode!, startNode!);
264
- await methods.collapse(moveNode);
265
- break;
246
+ await (async () => {
247
+ if (!!staticState?.data) {
248
+ const { startNode, } = staticState;
249
+ const { moveNode, dropType, droppable, isHideDraggier } = staticState.data.move;
250
+ if (!droppable || isHideDraggier) {
251
+ return;
252
+ }
253
+ const newParentNode = dropType === eTreeDropType.inner ? moveNode : moveNode.parent;
254
+ const oldParentNode = startNode.parent;
255
+ const newSiblings = dropType === eTreeDropType.inner ? methods.getReactiveChildrenData(moveNode) : methods.getParentChildrenData(moveNode);
256
+ const newSiblingsBak = [...newSiblings];
257
+ const oldSiblings = methods.getParentChildrenData(startNode);
258
+ const oldSiblingsBak = [...oldSiblings];
259
+
260
+ methods.removeSelf(startNode!);
261
+ switch (dropType) {
262
+ case eTreeDropType.prev:
263
+ methods.previousSibling(moveNode!, startNode!);
264
+ break;
265
+ case eTreeDropType.inner:
266
+ methods.unshiftChild(moveNode!, startNode!);
267
+ await methods.expand(moveNode!);
268
+ break;
269
+ case eTreeDropType.next:
270
+ methods.nextSibling(moveNode!, startNode!);
271
+ await methods.collapse(moveNode);
272
+ break;
273
+ }
274
+ await nextTick();
275
+ await Promise.all([
276
+ methods.refreshCheckStatus(startNode!.parent),
277
+ methods.refreshCheckStatus(moveNode!.parent)
278
+ ]);
279
+ await hooks.onDragChange.exec({
280
+ data: startNode.data,
281
+ newParent: newParentNode?.data,
282
+ oldParent: oldParentNode?.data,
283
+ node: startNode,
284
+ newParentNode,
285
+ oldParentNode,
286
+ newSiblings,
287
+ oldSiblings,
288
+ newSiblingsBak,
289
+ oldSiblingsBak,
290
+ undo: () => {
291
+ newSiblings.splice(0, newSiblings.length, ...newSiblingsBak);
292
+ oldSiblings.splice(0, oldSiblings.length, ...oldSiblingsBak);
293
+ },
294
+ });
266
295
  }
267
- await nextTick();
268
- await Promise.all([
269
- methods.refreshCheckStatus(startNode!.parent),
270
- methods.refreshCheckStatus(moveNode!.parent)
271
- ]);
272
- await hooks.onDragChange.exec({
273
- data: startNode.data,
274
- newParent: newParentNode?.data,
275
- oldParent: oldParentNode?.data,
276
- node: startNode,
277
- newParentNode,
278
- oldParentNode,
279
- newSiblings,
280
- oldSiblings,
281
- newSiblingsBak,
282
- oldSiblingsBak,
283
- undo: () => {
284
- newSiblings.splice(0, newSiblings.length, ...newSiblingsBak);
285
- oldSiblings.splice(0, oldSiblings.length, ...oldSiblingsBak);
286
- },
287
- });
288
- }
289
- staticState = null;
296
+ })();
297
+ delay().then(() => {
298
+ staticState = null;
299
+ reactiveState.dragging = false;
300
+ });
290
301
  });
291
302
  };
292
303
 
@@ -302,7 +313,8 @@ export function createTreeDraggier(
302
313
 
303
314
  if (!isDragging) {
304
315
  /*拖拽超过一定距离的时候才开始拖拽,避免与点击事件冲突*/
305
- if (!!e && Math.abs(e.clientY - staticState.start.y) > 5) {
316
+ const { clientY } = ClientZoom.getClientPosition(e);
317
+ if (!!e && Math.abs(clientY - staticState.start.y) > 5) {
306
318
  isDragging = true;
307
319
  initDraggierState();
308
320
  }
@@ -323,9 +335,10 @@ export function createTreeDraggier(
323
335
  staticState.data.move.isHideDraggier = true;
324
336
  staticState.data.cloneTreeNode.style.opacity = '0';
325
337
  } else {
338
+ const { clientX, clientY } = ClientZoom.getClientPosition(e);
326
339
  staticState.data.move.isHideDraggier = false;
327
- staticState.data.move.x = e.clientX;
328
- staticState.data.move.y = e.clientY;
340
+ staticState.data.move.x = clientX;
341
+ staticState.data.move.y = clientY;
329
342
  staticState.data.move.moveNode = node;
330
343
  staticState.data.move.contentRect = (e.currentTarget as HTMLElement).querySelector(`.${contentClass}`)!.getBoundingClientRect();
331
344
  const percent = (staticState.data.move.y - staticState.data.move.contentRect.top) / staticState.data.move.contentRect.height;
@@ -366,7 +379,7 @@ export function createTreeDraggier(
366
379
  indicatorHandler.update();
367
380
  };
368
381
 
369
- const isDragging = () => !staticState;
382
+ const isDragging = () => reactiveState.dragging;
370
383
 
371
384
  const getState = () => staticState;
372
385
 
@@ -384,7 +397,8 @@ export function createTreeDraggier(
384
397
  document.removeEventListener('mouseup', draggierEffects.clear);
385
398
  });
386
399
  },
387
- onMousemoveRow: null as null | ((e: iMouseEvent, node: iTreeNode) => void)
400
+ onMousemoveRow: null as null | ((e: iMouseEvent, node: iTreeNode) => void),
401
+ isDragging: () => shadowTreeNode.isDragging(),
388
402
  });
389
403
 
390
404
  return ret;
@@ -254,6 +254,7 @@ export function createTreeMethods(
254
254
  * @date 2022/9/22 19:56
255
255
  */
256
256
  refreshCheckStatus: async (keyOrNode: iTreeKeyOrNode, emitChange = true) => {
257
+ if (!props.multiple) {return;}
257
258
  const checkKeys: string[] = [];
258
259
  const unCheckKeys: string[] = [];
259
260
  await nextTick();
@@ -29,9 +29,10 @@ export const TreePropsOptions = {
29
29
  defaultExpandAll: { type: Boolean as PropType<iTreeProps["defaultExpandAll"]> },
30
30
  getNodeIcon: { type: Function as PropType<iTreeProps["getNodeIcon"]> },
31
31
  draggable: { type: Boolean as PropType<iTreeProps["draggable"]> },
32
+ customDraggier: { type: Boolean as PropType<iTreeProps["customDraggier"]> },
32
33
  isAllowDraggable: { type: Function as PropType<iTreeProps["isAllowDraggable"]> },
33
34
  isAllowDroppable: { type: Function as PropType<iTreeProps["isAllowDroppable"]> },
34
- leafIcon: { type: String as PropType<iTreeProps["leafIcon"]>, default: 'pi-file' as string },
35
+ leafIcon: { type: String as PropType<iTreeProps["leafIcon"]>, default: 'pi-drive-file' as string },
35
36
  folderCollapseIcon: { type: String as PropType<iTreeProps["folderCollapseIcon"]>, default: 'pi-caret-right' as string },
36
37
  folderExpandIcon: { type: String as PropType<iTreeProps["folderExpandIcon"]>, default: 'pi-down' as string },
37
38
  expanderCover: { type: Boolean as PropType<iTreeProps["expanderCover"]>, default: true },
@@ -0,0 +1,91 @@
1
+ import {cacheComputed, ComponentPropsType, computed, designComponent, getComponentCls, iHTMLDivElement, iMouseEvent, PropType, useClassCache, useRefs} from "plain-design-composition";
2
+ import {Icon} from "../Icon";
3
+ import {iTreeNode} from "../TreeCore/createTreeNode";
4
+ import './tree-node-with-menu.scss';
5
+ import {Tree} from "../Tree";
6
+ import {useTooltip} from "../useTooltip";
7
+ import {iTreeNodeWithMenuOption} from "./treeNodeWithMenu.utils";
8
+ import {Dropdown} from "../Dropdown";
9
+ import {DropdownOption} from "../DropdownOption";
10
+
11
+ export const TreeNodeWithMenu = designComponent({
12
+ name: 'tree-node-with-menu',
13
+ props: {
14
+ treeNode: { type: Object as PropType<iTreeNode>, required: true },
15
+ dropdownOptions: { type: Array as PropType<iTreeNodeWithMenuOption[]> },
16
+ dropdownAttrs: { type: Object as PropType<ComponentPropsType<typeof Dropdown>> },
17
+ },
18
+ setup({ props }) {
19
+
20
+ const { refs, onRef } = useRefs({ content: iHTMLDivElement });
21
+
22
+ const tree = Tree.use.inject();
23
+
24
+ const isDraggable = cacheComputed(() => tree.props.draggable && tree.utils.isAllowDraggable(props.treeNode));
25
+
26
+ const classes = useClassCache(() => [
27
+ getComponentCls('tree-node-with-menu'),
28
+ {
29
+ 'tree-node-with-menu-draggable': isDraggable.value,
30
+ }
31
+ ]);
32
+
33
+ const dropdownAttrs = computed(() => {
34
+ return {
35
+ trigger: 'hover' as const,
36
+ placement: 'right-start' as const,
37
+ ...props.dropdownAttrs,
38
+ };
39
+ });
40
+
41
+ const handler = {
42
+ onMousedownDraggier: computed(() => !tree.draggier.value ? undefined : ((e: iMouseEvent) => {
43
+ tree.draggier.value!.onMousedownDraggier(e, props.treeNode);
44
+ })),
45
+ };
46
+
47
+ useTooltip({
48
+ overflow: true,
49
+ tooltip: () => ({
50
+ reference: refs.content,
51
+ message: props.treeNode.label()
52
+ }),
53
+ });
54
+
55
+ return () => (
56
+ <div className={classes.value}>
57
+ <div className="tree-node-with-menu-content" ref={onRef.content}>{props.treeNode.label()}</div>
58
+ {(() => {
59
+ let btn = (
60
+ <div className="tree-node-with-menu-button" onMouseDown={handler.onMousedownDraggier.value}>
61
+ <Icon icon="pi-drag-dot-vertical"/>
62
+ </div>
63
+ );
64
+ let ret = btn;
65
+ if (!!props.dropdownOptions?.length && !tree.draggier.value?.isDragging()) {
66
+ ret = (
67
+ <Dropdown
68
+ {...dropdownAttrs.value}
69
+ v-slots={{
70
+ default: () => btn,
71
+ popper: () => (
72
+ props.dropdownOptions?.map((option, index) => {
73
+ const { renderDefault, renderContent, ...leftOption } = option;
74
+ const vSlots = {} as any;
75
+ if (!!renderDefault) {vSlots.default = () => renderDefault();}
76
+ if (!!renderContent) {vSlots.content = () => renderContent();}
77
+ return <DropdownOption key={index} {...leftOption} v-slots={vSlots}/>;
78
+ })
79
+ )
80
+ }}
81
+ />
82
+ );
83
+ }
84
+ return ret;
85
+ })()}
86
+ </div>
87
+ );
88
+ },
89
+ });
90
+
91
+ export default TreeNodeWithMenu;
@@ -0,0 +1,39 @@
1
+ @include comp(tree-node-with-menu) {
2
+ display: flex;
3
+ align-items: center;
4
+
5
+ .tree-node-with-menu-content {
6
+ flex: 1;
7
+ overflow: hidden;
8
+ text-overflow: ellipsis;
9
+ white-space: nowrap;
10
+ }
11
+
12
+ .tree-node-with-menu-button {
13
+ height: 1em;
14
+ width: 1em;
15
+ display: flex;
16
+ align-items: center;
17
+ justify-content: center;
18
+ cursor: pointer;
19
+ user-select: none;
20
+ //transition: all ease 0.3s;
21
+ }
22
+
23
+ &.tree-node-with-menu-draggable {
24
+ .tree-node-with-menu-button {
25
+ cursor: row-resize;
26
+ padding-right: 6px;
27
+ }
28
+ }
29
+ }
30
+
31
+ @include comp(tree-node) {
32
+ &:not(:hover) {
33
+ .tree-node-with-menu-button {
34
+ opacity: 0;
35
+ width: 0;
36
+ pointer-events: none;
37
+ }
38
+ }
39
+ }
@@ -0,0 +1,12 @@
1
+ import {iMouseEvent, RenderNode} from "plain-design-composition";
2
+
3
+ export interface iTreeNodeWithMenuOption {
4
+ label?: string,
5
+ disabled?: boolean,
6
+ readonly?: boolean,
7
+ icon?: string,
8
+ val?: any,
9
+ onClick?: (e: iMouseEvent) => void
10
+ renderDefault?: () => RenderNode,
11
+ renderContent?: () => RenderNode,
12
+ }
@@ -19,7 +19,7 @@ export const VirtualList = designComponent({
19
19
  onPageIndexChange: (data: { pageIndex: number, start: number }) => true,
20
20
  },
21
21
  scopeSlots: {
22
- default: (scope: { item: any, index: number, virtualIndex: number }) => {},
22
+ default: (scope: { item: any, index: number, vid: string, vIndex: number }) => {},
23
23
  content: (scope: { data: any[] }) => {},
24
24
  },
25
25
  setup({ props, scopeSlots, event: { emit } }) {
@@ -48,8 +48,8 @@ export const VirtualList = designComponent({
48
48
  <div className="virtual-list-content" style={virtual.contentStyles.value} ref={onRef.content}>
49
49
  {scopeSlots.content.isExist() ?
50
50
  scopeSlots.content({ data: list }, null) :
51
- list.map((node, virtualIndex) =>
52
- scopeSlots.default({ item: node.item, index: node.index, virtualIndex })
51
+ list.map((node, vIndex) =>
52
+ scopeSlots.default({ item: node.item, index: node.index, vid: node.id, vIndex })
53
53
  )}
54
54
  </div>
55
55
  </div>
@@ -1,18 +1,23 @@
1
- import {computed, getComponentCls, onMounted, onUpdated, reactive, useClasses, useStyles, watch} from "plain-design-composition";
1
+ import {computed, createCounter, getComponentCls, onMounted, onUpdated, reactive, useClasses, useStyles, watch} from "plain-design-composition";
2
2
  import {delay} from "plain-utils/utils/delay";
3
3
  import './virtual-list.scss';
4
4
  import Scroll from "../Scroll";
5
5
  import {unit} from "plain-utils/string/unit";
6
+ import {createEnum} from "plain-utils/utils/createEnum";
6
7
 
7
8
  interface DataNode {
8
9
  top: number;
9
10
  bottom: number;
10
11
  height: number;
11
-
12
- data: any;
13
- index: number;
12
+ item: any;
13
+ id: string;
14
+ index: number,
14
15
  }
15
16
 
17
+ const ScrollDirection = createEnum(['up', 'down', 'none'] as const);
18
+
19
+ const nextRowId = createCounter('__v__');
20
+
16
21
  export function useVirtualList(
17
22
  {
18
23
  props,
@@ -50,9 +55,30 @@ export function useVirtualList(
50
55
  };
51
56
  /*响应式属性*/
52
57
  const state = reactive({
53
- nodes: [] as DataNode[], // 格式化的data数组数据
54
58
  scrollTop: 0, // 当前滚动scrollTop(非实时的)
55
59
  pageSize: 0, // 页大小
60
+ scrollDirection: ScrollDirection.none as typeof ScrollDirection.TYPE,
61
+ id2height: {} as Record<string, number | undefined>,
62
+ avgHeight: props.size
63
+ });
64
+
65
+ const idMap = new WeakMap<object, string>();
66
+ let prevBottom = 0;
67
+
68
+ const nodes = computed(() => {
69
+ prevBottom = 0;
70
+ return props.data.map((item, index): DataNode => {
71
+ let id = idMap.get(item);
72
+ if (!id) {
73
+ id = nextRowId();
74
+ idMap.set(item, id);
75
+ }
76
+ const height: number = state.id2height[id] || state.avgHeight;
77
+ let top = prevBottom;
78
+ let bottom = top + height;
79
+ prevBottom = bottom;
80
+ return { id: id!, item, height, top, bottom, index };
81
+ });
56
82
  });
57
83
 
58
84
  /**
@@ -60,22 +86,21 @@ export function useVirtualList(
60
86
  * @author 韦胜健
61
87
  * @date 2020/11/15 9:28
62
88
  */
63
- const offsetData = computed((): { list: { item: any, index: number }[], startPageIndex: number, start: number } => {
89
+ const offsetData = computed((): { list: { item: any, id: string, index: number }[], startPageIndex: number, start: number } => {
64
90
  const { pageSize, scrollTop } = state;
65
- const data = props.data || [];
66
91
  if (!pageSize) {
67
92
  return { list: [], startPageIndex: 0, start: 0 };
68
93
  }
69
94
  if (props.disabled) {
70
95
  return {
71
- list: data.map((item, index) => ({ item, index })),
96
+ list: nodes.value,
72
97
  start: 0,
73
98
  startPageIndex: 0,
74
99
  };
75
100
  }
76
101
  const { start, end, pageIndex } = utils.getPageIndex(scrollTop, pageSize);
77
102
  return {
78
- list: data.map((item, index) => ({ item, index })).slice(start, end),
103
+ list: nodes.value.slice(start, end),
79
104
  startPageIndex: pageIndex,
80
105
  start,
81
106
  };
@@ -112,7 +137,7 @@ export function useVirtualList(
112
137
  if (!props.dynamicSize) {
113
138
  style.height = `${(props.data || []).length * props.size}px`;
114
139
  } else {
115
- style.height = `${state.nodes[state.nodes.length - 1].bottom}px`;
140
+ style.height = `${nodes.value[nodes.value.length - 1].bottom}px`;
116
141
  }
117
142
  });
118
143
 
@@ -136,7 +161,7 @@ export function useVirtualList(
136
161
  if (!props.dynamicSize) {
137
162
  top = start * props.size;
138
163
  } else {
139
- top = state.nodes[start].top;
164
+ top = nodes.value[start].top;
140
165
  }
141
166
 
142
167
  if (transform !== false) {
@@ -161,13 +186,13 @@ export function useVirtualList(
161
186
  // console.log('not dynamic》》》getIndex', top / props.size)
162
187
  return Math.floor(top / props.size);
163
188
  } else {
164
- const { nodes } = state;
189
+ const _nodes = nodes.value;
165
190
  let start = 0;
166
- let end = nodes.length - 1;
191
+ let end = _nodes.length - 1;
167
192
  let temp = 0;
168
193
  while (start <= end) {
169
194
  let middle = Math.floor((start + end) / 2);
170
- let middleBottom = nodes[middle].bottom;
195
+ let middleBottom = _nodes[middle].bottom;
171
196
  if (middleBottom === top) {
172
197
  return middle + 1;
173
198
  } else if (middleBottom < top) {
@@ -189,18 +214,37 @@ export function useVirtualList(
189
214
  * @date 2020/12/14 22:44
190
215
  */
191
216
  getPageIndex: (scrollTop: number, pageSize: number) => {
217
+ if (scrollTop < 0) {
218
+ scrollTop = 0;
219
+ }
192
220
  const data = props.data || [];
193
221
  /*当前scrollTop对应的数据中数据的索引*/
194
222
  let scrollIndex = utils.getIndex(scrollTop);
195
223
  let pageIndex = Math.floor(scrollIndex / pageSize);
196
- let start = pageIndex === 0 ? 0 : (pageIndex - 1) * pageSize;
197
- let end = start + pageSize * 3;
224
+ let start = (() => {
225
+ if (state.scrollDirection === 'none') {
226
+ return scrollIndex - Math.floor(pageSize * 0.5);
227
+ } else if (state.scrollDirection === 'up') {
228
+ return scrollIndex - pageSize;
229
+ } else {
230
+ return scrollIndex;
231
+ }
232
+ })();
233
+ let end = start + pageSize * 2;
234
+ start -= 1;
235
+ end += 1;
236
+ if (start < 0) {
237
+ end -= start;
238
+ start = 0;
239
+ }
198
240
  /*console.log({
199
- scrollIndex,
200
- pageIndex,
201
- start,
202
- end,
203
- })*/
241
+ scrollIndex,
242
+ pageIndex,
243
+ start,
244
+ end,
245
+ pageSize,
246
+ scrollDirection: state.scrollDirection
247
+ });*/
204
248
  const exceed = end - data.length;
205
249
  if (exceed > 0) {
206
250
  end = data.length;
@@ -217,21 +261,6 @@ export function useVirtualList(
217
261
  pageIndex,
218
262
  };
219
263
  },
220
- /**
221
- * 格式化props.data为 DataNode数组
222
- * @author 韦胜健
223
- * @date 2020/11/15 9:29
224
- */
225
- resetData: (data: any[]) => {
226
- // console.log('resetData')
227
- state.nodes = data.map((item, index) => ({
228
- data: item,
229
- index,
230
- top: props.size * index,
231
- height: props.size,
232
- bottom: props.size * (index + 1),
233
- }));
234
- }
235
264
  };
236
265
 
237
266
  /*---------------------------------------handler-------------------------------------------*/
@@ -242,13 +271,13 @@ export function useVirtualList(
242
271
  if (props.disabled) {
243
272
  return;
244
273
  }
245
- freezeState.scrollTop = (e.target as HTMLDivElement).scrollTop;
274
+ const newScrollTop = (e.target as HTMLDivElement).scrollTop;
275
+ state.scrollDirection = newScrollTop > freezeState.scrollTop ? ScrollDirection.down : ScrollDirection.up;
276
+ freezeState.scrollTop = newScrollTop;
246
277
  const current = utils.getPageIndex((e.target as HTMLDivElement).scrollTop, state.pageSize);
247
- if (freezeState.current.pageIndex != current.pageIndex || freezeState.current.start != current.start) {
248
- state.scrollTop = (e.target as HTMLDivElement).scrollTop;
249
- freezeState.current = current;
250
- emit.onPageIndexChange(current);
251
- }
278
+ state.scrollTop = (e.target as HTMLDivElement).scrollTop;
279
+ freezeState.current = current;
280
+ emit.onPageIndexChange(current);
252
281
  }
253
282
  };
254
283
 
@@ -259,17 +288,22 @@ export function useVirtualList(
259
288
  if (!props.dynamicSize) {
260
289
  return;
261
290
  }
262
- !!refs.scroll && refs.scroll.methods.scrollTop(0, 0);
263
- /*如果是动态高度,则刷state.nodes*/
264
- utils.resetData(data);
291
+ // !!refs.scroll && refs.scroll.methods.scrollTop(0, 0);
265
292
  });
266
293
 
267
- onMounted(() => {
268
- const hostHeight = refs.scroll!.refs.host!.offsetHeight;
269
- state.pageSize = Math.ceil(hostHeight / props.size);
270
- if (props.dynamicSize && !props.disabled) {
271
- utils.resetData(props.data);
294
+ onMounted(async () => {
295
+ await delay(23);
296
+ let hostHeight = refs.scroll!.refs.host!.offsetHeight;
297
+ const headEl = refs.scroll!.refs.host!.querySelector('[data-virtual-head]') as undefined | HTMLDivElement;
298
+ const footEl = refs.scroll!.refs.host!.querySelector('[data-virtual-foot]') as undefined | HTMLDivElement;
299
+ if (!!headEl) {
300
+ hostHeight -= headEl.offsetHeight;
272
301
  }
302
+ if (!!footEl) {
303
+ hostHeight -= footEl.offsetHeight;
304
+ }
305
+
306
+ state.pageSize = Math.floor(hostHeight / props.size);
273
307
  });
274
308
 
275
309
  onUpdated(async () => {
@@ -281,7 +315,9 @@ export function useVirtualList(
281
315
  }
282
316
  await delay();
283
317
  // console.log('dynamic scan height')
284
- const elNodes = (Array.from(refs.content!.childNodes || []) as HTMLElement[]).filter(node => node.nodeType !== 3);
318
+ const elNodes = (refs.content?.querySelectorAll('[data-vid],[vid]') || []) as HTMLElement[];
319
+ let newAvgHeight = state.avgHeight;
320
+
285
321
  for (let i = 0; i < elNodes.length; i++) {
286
322
  const el = elNodes[i];
287
323
  const { offsetHeight: height } = el;
@@ -289,19 +325,15 @@ export function useVirtualList(
289
325
  if (vid == null) {
290
326
  throw new Error('Each item of the virtual-list must have an attribute named "vid", please set :vid="index" or :data-vid="index"');
291
327
  }
292
- vid = Number(vid);
293
- const prevNode = state.nodes[vid];
294
- const prevHeight = prevNode.height;
295
- let deltaHeight = prevHeight - height;
296
- if (deltaHeight !== 0) {
297
- prevNode.height = height;
298
- prevNode.bottom = prevNode.bottom - deltaHeight;
299
- for (let j = vid + 1; j < state.nodes!.length; j++) {
300
- state.nodes![j].top = state.nodes![j - 1].bottom;
301
- state.nodes![j].bottom = state.nodes![j].bottom - deltaHeight;
302
- }
328
+ if (state.id2height[vid] != height) {
329
+ state.id2height[vid] = height;
330
+ newAvgHeight = Number(((state.avgHeight + height) / 2).toFixed(0));
303
331
  }
304
332
  }
333
+
334
+ if (newAvgHeight != state.avgHeight) {
335
+ state.avgHeight = newAvgHeight;
336
+ }
305
337
  });
306
338
 
307
339
  return {