@v-c/virtual-list 1.0.0 → 1.0.2

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.
package/dist/List.cjs CHANGED
@@ -2,7 +2,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
2
2
  const require_rolldown_runtime = require("./_virtual/rolldown_runtime.cjs");
3
3
  const require_Filler = require("./Filler.cjs");
4
4
  const require_Item = require("./Item.cjs");
5
- const require_useDiffItem = require("./hooks/useDiffItem.cjs");
6
5
  const require_useFrameWheel = require("./hooks/useFrameWheel.cjs");
7
6
  const require_useGetSize = require("./hooks/useGetSize.cjs");
8
7
  const require_useHeights = require("./hooks/useHeights.cjs");
@@ -69,12 +68,18 @@ var List_default = /* @__PURE__ */ (0, vue.defineComponent)({
69
68
  inheritAttrs: false,
70
69
  setup(props, { expose, attrs, slots }) {
71
70
  const itemHeight = (0, vue.computed)(() => props.itemHeight);
71
+ const itemKey = (0, vue.toRef)(props, "itemKey");
72
72
  const getKey = (item) => {
73
- if (typeof props.itemKey === "function") return props.itemKey(item);
74
- return item?.[props.itemKey];
73
+ item = (0, vue.toRaw)(item);
74
+ const _itemKey = itemKey.value;
75
+ if (typeof _itemKey === "function") return _itemKey(item);
76
+ return item?.[_itemKey];
75
77
  };
76
78
  const [setInstanceRef, collectHeight, heights, heightUpdatedMark] = require_useHeights.default(getKey, void 0, void 0);
77
- const mergedData = (0, vue.computed)(() => props.data || EMPTY_DATA);
79
+ const mergedData = (0, vue.shallowRef)(props?.data || EMPTY_DATA);
80
+ (0, vue.watch)(() => props.data, () => {
81
+ mergedData.value = props?.data || EMPTY_DATA;
82
+ });
78
83
  const useVirtual = (0, vue.computed)(() => !!(props.virtual !== false && props.height && props.itemHeight));
79
84
  const containerHeight = (0, vue.computed)(() => Object.values(heights.maps).reduce((total, curr) => total + curr, 0));
80
85
  const inVirtual = (0, vue.computed)(() => {
@@ -105,7 +110,6 @@ var List_default = /* @__PURE__ */ (0, vue.defineComponent)({
105
110
  if (componentRef.value) componentRef.value.scrollTop = alignedTop;
106
111
  offsetTop.value = alignedTop;
107
112
  }
108
- require_useDiffItem.default(mergedData, getKey);
109
113
  (0, vue.watch)([
110
114
  inVirtual,
111
115
  useVirtual,
@@ -121,6 +125,7 @@ var List_default = /* @__PURE__ */ (0, vue.defineComponent)({
121
125
  fillerOffset.value = void 0;
122
126
  return;
123
127
  }
128
+ const { itemHeight: itemHeight$1, height } = props;
124
129
  if (!inVirtual.value) {
125
130
  scrollHeight.value = fillerInnerRef.value?.offsetHeight || 0;
126
131
  start.value = 0;
@@ -133,23 +138,27 @@ var List_default = /* @__PURE__ */ (0, vue.defineComponent)({
133
138
  let startOffset;
134
139
  let endIndex;
135
140
  const dataLen = mergedData.value.length;
136
- const data = mergedData.value;
141
+ const data = (0, vue.toRaw)(mergedData.value);
137
142
  for (let i = 0; i < dataLen; i += 1) {
138
143
  const item = data[i];
139
144
  const key = getKey(item);
140
145
  const cacheHeight = heights.get(key);
141
- const currentItemBottom = itemTop + (cacheHeight === void 0 ? props.itemHeight : cacheHeight);
146
+ const currentItemBottom = itemTop + (cacheHeight === void 0 ? itemHeight$1 : cacheHeight);
142
147
  if (currentItemBottom >= offsetTop.value && startIndex === void 0) {
143
148
  startIndex = i;
144
149
  startOffset = itemTop;
145
150
  }
146
- if (currentItemBottom > offsetTop.value + props.height && endIndex === void 0) endIndex = i;
151
+ if (currentItemBottom > offsetTop.value + height && endIndex === void 0) endIndex = i;
147
152
  itemTop = currentItemBottom;
153
+ if (startIndex !== void 0 && endIndex !== void 0) {
154
+ itemTop = currentItemBottom + (dataLen - 1 - i) * itemHeight$1;
155
+ break;
156
+ }
148
157
  }
149
158
  if (startIndex === void 0) {
150
159
  startIndex = 0;
151
160
  startOffset = 0;
152
- endIndex = Math.ceil(props.height / props.itemHeight);
161
+ endIndex = Math.ceil(height / itemHeight$1);
153
162
  }
154
163
  if (endIndex === void 0) endIndex = mergedData.value.length - 1;
155
164
  endIndex = Math.min(endIndex + 1, mergedData.value.length - 1);
package/dist/List.js CHANGED
@@ -1,6 +1,5 @@
1
1
  import Filler_default from "./Filler.js";
2
2
  import Item_default from "./Item.js";
3
- import useDiffItem from "./hooks/useDiffItem.js";
4
3
  import useFrameWheel from "./hooks/useFrameWheel.js";
5
4
  import { useGetSize } from "./hooks/useGetSize.js";
6
5
  import useHeights from "./hooks/useHeights.js";
@@ -9,7 +8,7 @@ import useScrollDrag from "./hooks/useScrollDrag.js";
9
8
  import useScrollTo from "./hooks/useScrollTo.js";
10
9
  import ScrollBar_default from "./ScrollBar.js";
11
10
  import { getSpinSize } from "./utils/scrollbarUtil.js";
12
- import { computed, createVNode, defineComponent, isVNode, mergeProps, ref, shallowRef, watch } from "vue";
11
+ import { computed, createVNode, defineComponent, isVNode, mergeProps, ref, shallowRef, toRaw, toRef, watch } from "vue";
13
12
  import ResizeObserver from "@v-c/resize-observer";
14
13
  import { pureAttrs } from "@v-c/util/dist/props-util";
15
14
  function _isSlot(s) {
@@ -66,12 +65,18 @@ var List_default = /* @__PURE__ */ defineComponent({
66
65
  inheritAttrs: false,
67
66
  setup(props, { expose, attrs, slots }) {
68
67
  const itemHeight = computed(() => props.itemHeight);
68
+ const itemKey = toRef(props, "itemKey");
69
69
  const getKey = (item) => {
70
- if (typeof props.itemKey === "function") return props.itemKey(item);
71
- return item?.[props.itemKey];
70
+ item = toRaw(item);
71
+ const _itemKey = itemKey.value;
72
+ if (typeof _itemKey === "function") return _itemKey(item);
73
+ return item?.[_itemKey];
72
74
  };
73
75
  const [setInstanceRef, collectHeight, heights, heightUpdatedMark] = useHeights(getKey, void 0, void 0);
74
- const mergedData = computed(() => props.data || EMPTY_DATA);
76
+ const mergedData = shallowRef(props?.data || EMPTY_DATA);
77
+ watch(() => props.data, () => {
78
+ mergedData.value = props?.data || EMPTY_DATA;
79
+ });
75
80
  const useVirtual = computed(() => !!(props.virtual !== false && props.height && props.itemHeight));
76
81
  const containerHeight = computed(() => Object.values(heights.maps).reduce((total, curr) => total + curr, 0));
77
82
  const inVirtual = computed(() => {
@@ -102,7 +107,6 @@ var List_default = /* @__PURE__ */ defineComponent({
102
107
  if (componentRef.value) componentRef.value.scrollTop = alignedTop;
103
108
  offsetTop.value = alignedTop;
104
109
  }
105
- useDiffItem(mergedData, getKey);
106
110
  watch([
107
111
  inVirtual,
108
112
  useVirtual,
@@ -118,6 +122,7 @@ var List_default = /* @__PURE__ */ defineComponent({
118
122
  fillerOffset.value = void 0;
119
123
  return;
120
124
  }
125
+ const { itemHeight: itemHeight$1, height } = props;
121
126
  if (!inVirtual.value) {
122
127
  scrollHeight.value = fillerInnerRef.value?.offsetHeight || 0;
123
128
  start.value = 0;
@@ -130,23 +135,27 @@ var List_default = /* @__PURE__ */ defineComponent({
130
135
  let startOffset;
131
136
  let endIndex;
132
137
  const dataLen = mergedData.value.length;
133
- const data = mergedData.value;
138
+ const data = toRaw(mergedData.value);
134
139
  for (let i = 0; i < dataLen; i += 1) {
135
140
  const item = data[i];
136
141
  const key = getKey(item);
137
142
  const cacheHeight = heights.get(key);
138
- const currentItemBottom = itemTop + (cacheHeight === void 0 ? props.itemHeight : cacheHeight);
143
+ const currentItemBottom = itemTop + (cacheHeight === void 0 ? itemHeight$1 : cacheHeight);
139
144
  if (currentItemBottom >= offsetTop.value && startIndex === void 0) {
140
145
  startIndex = i;
141
146
  startOffset = itemTop;
142
147
  }
143
- if (currentItemBottom > offsetTop.value + props.height && endIndex === void 0) endIndex = i;
148
+ if (currentItemBottom > offsetTop.value + height && endIndex === void 0) endIndex = i;
144
149
  itemTop = currentItemBottom;
150
+ if (startIndex !== void 0 && endIndex !== void 0) {
151
+ itemTop = currentItemBottom + (dataLen - 1 - i) * itemHeight$1;
152
+ break;
153
+ }
145
154
  }
146
155
  if (startIndex === void 0) {
147
156
  startIndex = 0;
148
157
  startOffset = 0;
149
- endIndex = Math.ceil(props.height / props.itemHeight);
158
+ endIndex = Math.ceil(height / itemHeight$1);
150
159
  }
151
160
  if (endIndex === void 0) endIndex = mergedData.value.length - 1;
152
161
  endIndex = Math.min(endIndex + 1, mergedData.value.length - 1);
@@ -9,7 +9,7 @@ function parseNumber(value) {
9
9
  function useHeights(getKey, onItemAdd, onItemRemove) {
10
10
  const updatedMark = (0, vue.ref)(0);
11
11
  const instanceRef = (0, vue.ref)(/* @__PURE__ */ new Map());
12
- const heightsRef = (0, vue.reactive)(new require_CacheMap.default());
12
+ const heightsRef = new require_CacheMap.default();
13
13
  const promiseIdRef = (0, vue.ref)(0);
14
14
  function cancelRaf() {
15
15
  promiseIdRef.value += 1;
@@ -1,5 +1,5 @@
1
1
  import CacheMap_default from "../utils/CacheMap.js";
2
- import { onUnmounted, reactive, ref } from "vue";
2
+ import { onUnmounted, ref } from "vue";
3
3
  function parseNumber(value) {
4
4
  const num = parseFloat(value);
5
5
  return isNaN(num) ? 0 : num;
@@ -7,7 +7,7 @@ function parseNumber(value) {
7
7
  function useHeights(getKey, onItemAdd, onItemRemove) {
8
8
  const updatedMark = ref(0);
9
9
  const instanceRef = ref(/* @__PURE__ */ new Map());
10
- const heightsRef = reactive(new CacheMap_default());
10
+ const heightsRef = new CacheMap_default();
11
11
  const promiseIdRef = ref(0);
12
12
  function cancelRaf() {
13
13
  promiseIdRef.value += 1;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@v-c/virtual-list",
3
3
  "type": "module",
4
- "version": "1.0.0",
4
+ "version": "1.0.2",
5
5
  "description": "Vue Virtual List Component",
6
6
  "author": "",
7
7
  "license": "MIT",
@@ -20,6 +20,10 @@
20
20
  "./package.json": "./package.json"
21
21
  },
22
22
  "main": "./dist/index.js",
23
+ "files": [
24
+ "dist",
25
+ "package.json"
26
+ ],
23
27
  "publishConfig": {
24
28
  "access": "public"
25
29
  },
@@ -28,7 +32,7 @@
28
32
  },
29
33
  "dependencies": {
30
34
  "@v-c/resize-observer": "^1.0.0",
31
- "@v-c/util": "^1.0.1"
35
+ "@v-c/util": "^1.0.2"
32
36
  },
33
37
  "scripts": {
34
38
  "build": "vite build",
package/bump.config.ts DELETED
@@ -1,6 +0,0 @@
1
- import { defineConfig } from 'bumpp'
2
-
3
- export default defineConfig({
4
- commit: 'chore(release): @v-c/virtual-list %s',
5
- tag: '@v-c/virtual-list@%s',
6
- })
package/docs/animate.less DELETED
@@ -1,31 +0,0 @@
1
- .motion {
2
- transition: all 0.3s;
3
- }
4
-
5
- .item {
6
- display: inline-block;
7
- box-sizing: border-box;
8
- margin: 0;
9
- padding: 0 16px;
10
- overflow: hidden;
11
- line-height: 31px;
12
- position: relative;
13
-
14
- &:hover {
15
- background: rgba(255, 0, 0, 0.1);
16
- }
17
-
18
- &::after {
19
- content: '';
20
- border-bottom: 1px solid gray;
21
- position: absolute;
22
- bottom: 0;
23
- left: 0;
24
- right: 0;
25
- }
26
-
27
- button {
28
- vertical-align: text-top;
29
- margin-right: 8px;
30
- }
31
- }
package/docs/animate.vue DELETED
@@ -1,159 +0,0 @@
1
- <script setup lang="ts">
2
- import type { ListRef } from '../src'
3
- import { ref } from 'vue'
4
- import VirtualList from '../src'
5
- import './animate.less'
6
-
7
- interface Item {
8
- id: string
9
- uuid: number
10
- }
11
-
12
- let uuid = 0
13
- function genItem(): Item {
14
- const item = {
15
- id: `key_${uuid}`,
16
- uuid,
17
- }
18
- uuid += 1
19
- return item
20
- }
21
-
22
- const originData: Item[] = []
23
- for (let i = 0; i < 1000; i += 1) {
24
- originData.push(genItem())
25
- }
26
-
27
- const data = ref([...originData])
28
- const closeMap = ref<Record<string, boolean>>({})
29
- const animating = ref(false)
30
- const insertIndex = ref<number>()
31
-
32
- const listRef = ref<ListRef>()
33
-
34
- function onClose(id: string) {
35
- closeMap.value = {
36
- ...closeMap.value,
37
- [id]: true,
38
- }
39
- }
40
-
41
- function onLeave(id: string) {
42
- data.value = data.value.filter(item => item.id !== id)
43
- }
44
-
45
- function onAppear(...args: any[]) {
46
- console.log('Appear:', args)
47
- animating.value = false
48
- }
49
-
50
- function lockForAnimation() {
51
- animating.value = true
52
- }
53
-
54
- function onInsertBefore(id: string) {
55
- const index = data.value.findIndex(item => item.id === id)
56
- const newData = [...data.value.slice(0, index), genItem(), ...data.value.slice(index)]
57
- insertIndex.value = index
58
- data.value = newData
59
- lockForAnimation()
60
- }
61
-
62
- function onInsertAfter(id: string) {
63
- const index = data.value.findIndex(item => item.id === id) + 1
64
- const newData = [...data.value.slice(0, index), genItem(), ...data.value.slice(index)]
65
- insertIndex.value = index
66
- data.value = newData
67
- lockForAnimation()
68
- }
69
-
70
- function getCurrentHeight(el: Element) {
71
- const node = el as HTMLElement
72
- node.style.height = `${node.offsetHeight}px`
73
- node.style.opacity = '1'
74
- }
75
-
76
- function getMaxHeight(el: Element) {
77
- const node = el as HTMLElement
78
- node.style.height = `${node.scrollHeight}px`
79
- node.style.opacity = '1'
80
- }
81
-
82
- function getCollapsedHeight(el: Element) {
83
- const node = el as HTMLElement
84
- node.style.height = '0'
85
- node.style.opacity = '0'
86
- }
87
-
88
- function resetHeight(el: Element) {
89
- const node = el as HTMLElement
90
- node.style.height = ''
91
- node.style.opacity = ''
92
- onAppear()
93
- }
94
- </script>
95
-
96
- <template>
97
- <div>
98
- <h2>Animate</h2>
99
- <p>Current: {{ data.length }} records</p>
100
-
101
- <VirtualList
102
- ref="listRef"
103
- :data="data"
104
- data-id="list"
105
- :height="200"
106
- :item-height="20"
107
- item-key="id"
108
- :style="{
109
- border: '1px solid red',
110
- boxSizing: 'border-box',
111
- }"
112
- >
113
- <template #default="{ item, index }">
114
- <Transition
115
- name="motion"
116
- :appear="animating && insertIndex === index"
117
- @before-enter="getCollapsedHeight"
118
- @enter="getMaxHeight"
119
- @after-enter="resetHeight"
120
- @before-leave="getCurrentHeight"
121
- @leave="getCollapsedHeight"
122
- @after-leave="() => onLeave(item.id)"
123
- >
124
- <div
125
- v-if="!(closeMap as any)[item.id]"
126
- class="item"
127
- :data-id="item.id"
128
- >
129
- <div :style="{ height: item.uuid % 2 ? '100px' : undefined }">
130
- <button type="button" @click="onClose(item.id)">
131
- Close
132
- </button>
133
- <button type="button" @click="onInsertBefore(item.id)">
134
- Insert Before
135
- </button>
136
- <button type="button" @click="onInsertAfter(item.id)">
137
- Insert After
138
- </button>
139
- {{ item.id }}
140
- </div>
141
- </div>
142
- </Transition>
143
- </template>
144
- </VirtualList>
145
- </div>
146
- </template>
147
-
148
- <style scoped>
149
- .motion-enter-active,
150
- .motion-leave-active {
151
- transition: all 0.3s;
152
- }
153
-
154
- .motion-enter-from,
155
- .motion-leave-to {
156
- height: 0;
157
- opacity: 0;
158
- }
159
- </style>
package/docs/basic.vue DELETED
@@ -1,176 +0,0 @@
1
- <script setup lang="ts">
2
- import type { ListRef } from '../src'
3
- import { ref } from 'vue'
4
- import VirtualList from '../src'
5
-
6
- interface Item {
7
- id: number
8
- }
9
-
10
- const data: Item[] = []
11
- for (let i = 0; i < 1000; i += 1) {
12
- data.push({ id: i })
13
- }
14
-
15
- const listRef = ref<ListRef>()
16
-
17
- function scrollToShowBar() {
18
- listRef.value?.scrollTo(null)
19
- }
20
-
21
- function scrollTo100px() {
22
- listRef.value?.scrollTo(500)
23
- }
24
-
25
- function scrollToLarge() {
26
- listRef.value?.scrollTo({
27
- index: 99999999,
28
- align: 'top',
29
- })
30
- }
31
-
32
- function scrollTo50Top() {
33
- listRef.value?.scrollTo({
34
- index: 50,
35
- align: 'top',
36
- })
37
- }
38
-
39
- function scrollTo50Bottom() {
40
- listRef.value?.scrollTo({
41
- index: 50,
42
- align: 'bottom',
43
- })
44
- }
45
-
46
- function scrollTo50Auto() {
47
- listRef.value?.scrollTo({
48
- index: 50,
49
- align: 'auto',
50
- })
51
- }
52
-
53
- function scrollTo50TopOffset() {
54
- listRef.value?.scrollTo({
55
- index: 50,
56
- align: 'top',
57
- offset: 15,
58
- })
59
- }
60
-
61
- function scrollTo50BottomOffset() {
62
- listRef.value?.scrollTo({
63
- index: 50,
64
- align: 'bottom',
65
- offset: 15,
66
- })
67
- }
68
-
69
- function scrollToKey50() {
70
- listRef.value?.scrollTo({
71
- key: 50,
72
- align: 'auto',
73
- })
74
- }
75
-
76
- function scrollToLast() {
77
- listRef.value?.scrollTo({
78
- index: data.length - 2,
79
- align: 'top',
80
- })
81
- }
82
-
83
- function scrollToFirst() {
84
- listRef.value?.scrollTo({
85
- index: 0,
86
- align: 'bottom',
87
- })
88
- }
89
-
90
- const visible = ref(true)
91
-
92
- function onScroll(e: Event) {
93
- const target = e.currentTarget as HTMLElement
94
- console.log('scroll:', target.scrollTop)
95
- }
96
- </script>
97
-
98
- <template>
99
- <div style="height: 200vh">
100
- <h2>Basic</h2>
101
- <div style="margin-bottom: 16px">
102
- <button type="button" style="margin: 4px" @click="scrollToShowBar">
103
- Show scroll bar
104
- </button>
105
- <button type="button" style="margin: 4px" @click="scrollTo100px">
106
- Scroll To 100px
107
- </button>
108
- <button type="button" style="margin: 4px" @click="scrollToLarge">
109
- Scroll To 99999999 (top)
110
- </button>
111
- <button type="button" style="margin: 4px" @click="scrollTo50Top">
112
- Scroll To 50 (top)
113
- </button>
114
- <button type="button" style="margin: 4px" @click="scrollTo50Bottom">
115
- Scroll To 50 (bottom)
116
- </button>
117
- <button type="button" style="margin: 4px" @click="scrollTo50Auto">
118
- Scroll To 50 (auto)
119
- </button>
120
- <br>
121
- <button type="button" style="margin: 4px" @click="scrollTo50TopOffset">
122
- Scroll To 50 (top) + 15 offset
123
- </button>
124
- <button type="button" style="margin: 4px" @click="scrollTo50BottomOffset">
125
- Scroll To 50 (bottom) + 15 offset
126
- </button>
127
- <button type="button" style="margin: 4px" @click="scrollToKey50">
128
- Scroll To key 50 (auto)
129
- </button>
130
- <button type="button" style="margin: 4px" @click="scrollToLast">
131
- Scroll To Last (top)
132
- </button>
133
- <button type="button" style="margin: 4px" @click="scrollToFirst">
134
- Scroll To First (bottom)
135
- </button>
136
- <button type="button" style="margin: 4px" @click="visible = !visible">
137
- Toggle visible
138
- </button>
139
- </div>
140
-
141
- <VirtualList
142
- ref="listRef"
143
- :data="data"
144
- :height="200"
145
- :item-height="20"
146
- item-key="id"
147
- :style="{
148
- border: '1px solid red',
149
- boxSizing: 'border-box',
150
- display: visible ? '' : 'none',
151
- }"
152
- @scroll="onScroll"
153
- >
154
- <template #default="{ item }">
155
- <span
156
- :style="{
157
- height: `${30 + (item.id % 2 ? 0 : 10)}px`,
158
- lineHeight: '30px',
159
- display: 'inline-block',
160
- padding: '0 8px',
161
- }"
162
- @click="() => console.log('Click:', item.id)"
163
- >
164
- {{ item.id }}
165
- </span>
166
- </template>
167
- </VirtualList>
168
- </div>
169
- </template>
170
-
171
- <style scoped>
172
- button {
173
- padding: 4px 8px;
174
- cursor: pointer;
175
- }
176
- </style>
package/docs/height.vue DELETED
@@ -1,48 +0,0 @@
1
- <script setup lang="ts">
2
- import VirtualList from '../src'
3
-
4
- interface Item {
5
- id: number
6
- height: number
7
- }
8
-
9
- const data: Item[] = []
10
- for (let i = 0; i < 100; i += 1) {
11
- data.push({
12
- id: i,
13
- height: 30 + (i % 2 ? 70 : 0),
14
- })
15
- }
16
- </script>
17
-
18
- <template>
19
- <div>
20
- <h2>Dynamic Height</h2>
21
-
22
- <VirtualList
23
- :data="data"
24
- :height="500"
25
- :item-height="30"
26
- item-key="id"
27
- :style="{
28
- border: '1px solid red',
29
- boxSizing: 'border-box',
30
- }"
31
- >
32
- <template #default="{ item }">
33
- <span
34
- :style="{
35
- border: '1px solid gray',
36
- padding: '0 16px',
37
- height: `${item.height}px`,
38
- lineHeight: '30px',
39
- boxSizing: 'border-box',
40
- display: 'inline-block',
41
- }"
42
- >
43
- {{ item.id }}
44
- </span>
45
- </template>
46
- </VirtualList>
47
- </div>
48
- </template>