Skip to content

Commit 03e46c7

Browse files
committed
fix: multiple sub nodes
1 parent 249dfb1 commit 03e46c7

File tree

1 file changed

+82
-89
lines changed

1 file changed

+82
-89
lines changed

src/components/common/TreeExplorer.vue

Lines changed: 82 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,11 @@
4242
<ContextMenu ref="menu" :model="menuItems" />
4343
</template>
4444
<script setup lang="ts">
45-
import { useElementSize, useScroll, whenever } from '@vueuse/core'
46-
import { clamp } from 'es-toolkit/compat'
45+
import { useElementSize, useScroll } from '@vueuse/core'
4746
import ContextMenu from 'primevue/contextmenu'
4847
import type { MenuItem, MenuItemCommandEvent } from 'primevue/menuitem'
4948
import Tree from 'primevue/tree'
50-
import { computed, provide, ref, watch } from 'vue'
49+
import { computed, provide, ref } from 'vue'
5150
import { useI18n } from 'vue-i18n'
5251
5352
import TreeExplorerTreeNode from '@/components/common/TreeExplorerTreeNode.vue'
@@ -98,125 +97,124 @@ const {
9897
}
9998
)
10099
101-
const BUFFER_ROWS = 10
102100
const DEFAULT_NODE_HEIGHT = 32
103101
const SCROLL_THROTTLE = 64
104102
105-
const parentWindowRanges = ref<Record<string, WindowRange>>({})
106103
const treeContainerRef = ref<HTMLDivElement | null>(null)
107104
const menu = ref<InstanceType<typeof ContextMenu> | null>(null)
108105
const menuTargetNode = ref<RenderedTreeExplorerNode | null>(null)
109106
const renameEditingNode = ref<RenderedTreeExplorerNode | null>(null)
110-
const bufferRowsRef = ref(BUFFER_ROWS)
111107
112108
const { height: containerHeight } = useElementSize(treeContainerRef)
113109
const { y: scrollY } = useScroll(treeContainerRef, {
114110
throttle: SCROLL_THROTTLE,
115111
eventListenerOptions: { passive: true }
116112
})
117113
118-
// Reset window ranges when nodes are collapsed
119-
watch(
120-
expandedKeys,
121-
(newKeys, oldKeys) => {
122-
if (!oldKeys) return
123-
for (const key in oldKeys) {
124-
if (oldKeys[key] && !newKeys[key]) {
125-
delete parentWindowRanges.value[key]
114+
// Computed values for window calculation
115+
const viewRows = computed(() =>
116+
containerHeight.value
117+
? Math.ceil(containerHeight.value / DEFAULT_NODE_HEIGHT)
118+
: 0
119+
)
120+
const bufferRows = computed(() => Math.max(1, Math.floor(viewRows.value / 3)))
121+
const windowSize = computed(() => viewRows.value + bufferRows.value * 2)
122+
123+
// Compute window ranges for all nodes based on scroll position
124+
// Each node's window is calculated relative to its children list
125+
const parentWindowRanges = computed<Record<string, WindowRange>>(() => {
126+
if (!containerHeight.value || !renderedRoot.value.children) {
127+
return {}
128+
}
129+
130+
const ranges: Record<string, WindowRange> = {}
131+
const scrollTop = scrollY.value
132+
const scrollBottom = scrollTop + containerHeight.value
133+
134+
// Calculate cumulative positions for nodes in the tree
135+
const nodePositions = new Map<string, number>()
136+
let currentPos = 0
137+
138+
const calculatePositions = (node: RenderedTreeExplorerNode): number => {
139+
const nodeStart = currentPos
140+
nodePositions.set(node.key, nodeStart)
141+
currentPos += DEFAULT_NODE_HEIGHT
142+
143+
if (node.children && !node.leaf && expandedKeys.value?.[node.key]) {
144+
for (const child of node.children) {
145+
currentPos = calculatePositions(child)
126146
}
127147
}
128-
},
129-
{ deep: true }
130-
)
131148
132-
// Update windows for all nodes based on current scroll position
133-
const updateWindows = () => {
134-
if (!treeContainerRef.value || !containerHeight.value) return
149+
return currentPos
150+
}
135151
136-
const viewRows = Math.ceil(containerHeight.value / DEFAULT_NODE_HEIGHT)
137-
const offsetRows = Math.floor(scrollY.value / DEFAULT_NODE_HEIGHT)
138-
bufferRowsRef.value = viewRows / 3
152+
for (const child of renderedRoot.value.children) {
153+
currentPos = calculatePositions(child)
154+
}
139155
140-
const updateNodeWindow = (node: RenderedTreeExplorerNode) => {
156+
// Compute windows for each node based on scroll position
157+
const computeNodeWindow = (node: RenderedTreeExplorerNode) => {
141158
if (!node.children || node.leaf) return
142159
143160
const isExpanded = expandedKeys.value?.[node.key] ?? false
144-
if (!isExpanded) {
145-
delete parentWindowRanges.value[node.key]
146-
return
147-
}
161+
if (!isExpanded) return
162+
163+
const nodeStart = nodePositions.get(node.key) ?? 0
164+
const childrenStart = nodeStart + DEFAULT_NODE_HEIGHT
165+
const childrenEnd =
166+
childrenStart + node.children.length * DEFAULT_NODE_HEIGHT
167+
168+
// Check if this node's children are in the visible range
169+
const isVisible =
170+
childrenEnd >= scrollTop - bufferRows.value * DEFAULT_NODE_HEIGHT &&
171+
childrenStart <= scrollBottom + bufferRows.value * DEFAULT_NODE_HEIGHT
148172
149173
const totalChildren = node.children.length
150-
const currentRange = parentWindowRanges.value[node.key]
151-
152-
if (currentRange) {
153-
const fromRow = Math.max(0, offsetRows - bufferRowsRef.value)
154-
const toRow = offsetRows + bufferRowsRef.value + viewRows
155-
const newStart = clamp(fromRow, 0, totalChildren)
156-
const newEnd = clamp(toRow, newStart, totalChildren)
157-
158-
if (
159-
Math.abs(currentRange.start - newStart) > bufferRowsRef.value ||
160-
Math.abs(currentRange.end - newEnd) > bufferRowsRef.value
161-
) {
162-
parentWindowRanges.value[node.key] = {
163-
start: newStart,
164-
end: newEnd
165-
}
174+
175+
if (isVisible && totalChildren > 0) {
176+
// Calculate which children should be visible based on scroll position
177+
const relativeScrollTop = Math.max(0, scrollTop - childrenStart)
178+
const relativeScrollBottom = Math.max(0, scrollBottom - childrenStart)
179+
180+
const fromRow = Math.max(
181+
0,
182+
Math.floor(relativeScrollTop / DEFAULT_NODE_HEIGHT) - bufferRows.value
183+
)
184+
const toRow = Math.min(
185+
totalChildren,
186+
Math.ceil(relativeScrollBottom / DEFAULT_NODE_HEIGHT) + bufferRows.value
187+
)
188+
189+
ranges[node.key] = {
190+
start: Math.max(0, fromRow),
191+
end: Math.min(
192+
totalChildren,
193+
Math.max(fromRow + windowSize.value, toRow)
194+
)
166195
}
167196
} else {
168-
const windowSize = viewRows + bufferRowsRef.value * 2
169-
parentWindowRanges.value[node.key] = createInitialWindowRange(
197+
// Node is outside visible range, use minimal window
198+
ranges[node.key] = createInitialWindowRange(
170199
totalChildren,
171-
windowSize
200+
windowSize.value
172201
)
173202
}
174203
175-
const range = parentWindowRanges.value[node.key]
204+
// Recursively compute windows for children
205+
const range = ranges[node.key]
176206
for (let i = range.start; i < range.end && i < node.children.length; i++) {
177-
updateNodeWindow(node.children[i])
207+
computeNodeWindow(node.children[i])
178208
}
179209
}
180210
181-
for (const child of renderedRoot.value.children || []) {
182-
updateNodeWindow(child)
211+
for (const child of renderedRoot.value.children) {
212+
computeNodeWindow(child)
183213
}
184-
}
185214
186-
// Watch scroll position and update windows reactively
187-
watch([scrollY, containerHeight, expandedKeys], updateWindows, {
188-
immediate: true,
189-
flush: 'post'
215+
return ranges
190216
})
191217
192-
// Reset windows to top when scroll reaches top
193-
whenever(
194-
() => scrollY.value === 0,
195-
() => {
196-
const resetNodeWindow = (node: RenderedTreeExplorerNode) => {
197-
if (!node.children || node.leaf) return
198-
const isExpanded = expandedKeys.value?.[node.key] ?? false
199-
if (!isExpanded) return
200-
201-
const totalChildren = node.children.length
202-
parentWindowRanges.value[node.key] = createInitialWindowRange(
203-
totalChildren,
204-
Math.ceil((containerHeight.value / DEFAULT_NODE_HEIGHT) * 2)
205-
)
206-
207-
for (const child of node.children) {
208-
if (expandedKeys.value?.[child.key]) {
209-
resetNodeWindow(child)
210-
}
211-
}
212-
}
213-
214-
for (const parent of renderedRoot.value.children || []) {
215-
resetNodeWindow(parent)
216-
}
217-
}
218-
)
219-
220218
const getTreeNodeIcon = (node: TreeExplorerNode): string => {
221219
if (node.getIcon) {
222220
const icon = node.getIcon()
@@ -266,11 +264,6 @@ const nodeKeyMap = computed<Record<string, RenderedTreeExplorerNode>>(() => {
266264
return map
267265
})
268266
269-
const windowSize = computed(() => {
270-
if (!containerHeight.value) return 60
271-
return Math.ceil((containerHeight.value / DEFAULT_NODE_HEIGHT) * 2)
272-
})
273-
274267
const displayRoot = computed<RenderedTreeExplorerNode>(() => ({
275268
...renderedRoot.value,
276269
children: (renderedRoot.value.children || []).map((node) =>

0 commit comments

Comments
 (0)